1313// limitations under the License.
1414import { ContextLevel } from '@/core/constants' ;
1515import { CoreSharedModule } from '@/core/shared.module' ;
16- import { ADDON_BLOG_ENTRY_UPDATED } from '@addons/blog/constants' ;
16+ import { ADDON_BLOG_ENTRY_UPDATED , ADDON_BLOG_SYNC_ID } from '@addons/blog/constants' ;
1717import {
1818 AddonBlog ,
1919 AddonBlogAddEntryOption ,
@@ -22,26 +22,31 @@ import {
2222 AddonBlogProvider ,
2323 AddonBlogPublishState ,
2424} from '@addons/blog/services/blog' ;
25- import { Component , ElementRef , OnInit , ViewChild } from '@angular/core' ;
25+ import { AddonBlogOffline } from '@addons/blog/services/blog-offline' ;
26+ import { Component , ElementRef , OnDestroy , OnInit , ViewChild } from '@angular/core' ;
27+ import { AddonBlogSync } from '@addons/blog/services/blog-sync' ;
2628import { FormControl , FormGroup , Validators } from '@angular/forms' ;
2729import { CoreError } from '@classes/errors/error' ;
2830import { CoreCommentsComponentsModule } from '@features/comments/components/components.module' ;
2931import { CoreCourse } from '@features/course/services/course' ;
3032import { CoreCourseHelper , CoreCourseModuleData } from '@features/course/services/course-helper' ;
3133import { CoreCourseBasicData } from '@features/courses/services/courses' ;
3234import { CoreEditorComponentsModule } from '@features/editor/components/components.module' ;
33- import { CoreFileUploader } from '@features/fileuploader/services/fileuploader' ;
35+ import { CoreFileUploader , CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader' ;
3436import { CoreTagComponentsModule } from '@features/tag/components/components.module' ;
3537import { CanLeave } from '@guards/can-leave' ;
3638import { CoreLoadings } from '@services/loadings' ;
3739import { CoreNavigator } from '@services/navigator' ;
40+ import { CoreNetwork } from '@services/network' ;
3841import { CoreSites , CoreSitesReadingStrategy } from '@services/sites' ;
42+ import { CoreSync } from '@services/sync' ;
3943import { CoreDomUtils } from '@services/utils/dom' ;
4044import { CoreUtils } from '@services/utils/utils' ;
41- import { CoreWSFile } from '@services/ws' ;
4245import { Translate } from '@singletons' ;
4346import { CoreEvents } from '@singletons/events' ;
4447import { CoreForms } from '@singletons/form' ;
48+ import { CoreFileEntry } from '@services/file-helper' ;
49+ import { CoreTimeUtils } from '@services/utils/time' ;
4550
4651@Component ( {
4752 selector : 'addon-blog-edit-entry' ,
@@ -54,7 +59,7 @@ import { CoreForms } from '@singletons/form';
5459 CoreTagComponentsModule ,
5560 ] ,
5661} )
57- export class AddonBlogEditEntryPage implements CanLeave , OnInit {
62+ export class AddonBlogEditEntryPage implements CanLeave , OnInit , OnDestroy {
5863
5964 @ViewChild ( 'editEntryForm' ) formElement ! : ElementRef ;
6065
@@ -70,11 +75,11 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
7075 associateWithModule : new FormControl < boolean > ( false , { nonNullable : true , validators : [ Validators . required ] } ) ,
7176 } ) ;
7277
73- entry ?: AddonBlogPost ;
78+ entry ?: AddonBlogPost | AddonBlogEditEntryFormattedOfflinePost ;
7479 loaded = false ;
7580 maxFiles = 99 ;
76- initialFiles : CoreWSFile [ ] = [ ] ;
77- files : CoreWSFile [ ] = [ ] ;
81+ initialFiles : CoreFileEntry [ ] = [ ] ;
82+ files : CoreFileEntry [ ] = [ ] ;
7883 courseId ?: number ;
7984 modId ?: number ;
8085 userId ?: number ;
@@ -88,6 +93,7 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
8893 component = AddonBlogProvider . COMPONENT ;
8994 siteHomeId ?: number ;
9095 forceLeave = false ;
96+ isOfflineEntry = false ;
9197
9298 /**
9399 * Gives if the form is not pristine. (only for existing entries)
@@ -130,15 +136,17 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
130136 return CoreNavigator . back ( ) ;
131137 }
132138
133- const entryId = CoreNavigator . getRouteNumberParam ( 'id' ) ;
139+ const entryId = CoreNavigator . getRouteParam ( 'id' ) ;
134140 const lastModified = CoreNavigator . getRouteNumberParam ( 'lastModified' ) ;
135141 const filters : AddonBlogFilter | undefined = CoreNavigator . getRouteParam ( 'filters' ) ;
136142 const courseId = CoreNavigator . getRouteNumberParam ( 'courseId' ) ;
137143 const cmId = CoreNavigator . getRouteNumberParam ( 'cmId' ) ;
138144 this . userId = CoreNavigator . getRouteNumberParam ( 'userId' ) ;
139145 this . siteHomeId = CoreSites . getCurrentSiteHomeId ( ) ;
146+ this . isOfflineEntry = entryId ?. startsWith ( 'new-' ) ?? false ;
147+ const entryIdParsed = Number ( entryId ) ;
140148
141- if ( ! entryId ) {
149+ if ( entryIdParsed === 0 ) {
142150 this . loaded = true ;
143151
144152 try {
@@ -162,11 +170,27 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
162170 }
163171
164172 try {
165- this . entry = await this . getEntry ( { filters, lastModified, entryId } ) ;
166- this . files = this . entry . attachmentfiles ?? [ ] ;
173+ await AddonBlogSync . waitForSync ( ADDON_BLOG_SYNC_ID ) ;
174+
175+ if ( ! this . isOfflineEntry ) {
176+ const offlineContent = await this . getFormattedBlogOfflineEntry ( { id : entryIdParsed } ) ;
177+ this . entry = offlineContent ?? await this . getEntry ( { filters, lastModified, entryId : entryIdParsed } ) ;
178+ } else {
179+ this . entry = await this . getFormattedBlogOfflineEntry ( { created : Number ( entryId ?. slice ( 4 ) ) } ) ;
180+
181+ if ( ! this . entry ) {
182+ throw new CoreError ( 'This offline entry no longer exists.' ) ;
183+ }
184+ }
185+
186+ this . files = [ ...( this . entry . attachmentfiles ?? [ ] ) ] ;
167187 this . initialFiles = [ ...this . files ] ;
168- this . courseId = this . courseId || this . entry . courseid ;
169- this . modId = CoreNavigator . getRouteNumberParam ( 'cmId' ) || this . entry . coursemoduleid ;
188+
189+ if ( this . entry ) {
190+ CoreSync . blockOperation ( AddonBlogProvider . COMPONENT , this . entry . id ?? this . entry . created ) ;
191+ this . courseId = this . courseId || this . entry . courseid ;
192+ this . modId = CoreNavigator . getRouteNumberParam ( 'cmId' ) || this . entry . coursemoduleid ;
193+ }
170194
171195 if ( this . courseId ) {
172196 this . form . controls . associateWithCourse . setValue ( true ) ;
@@ -198,6 +222,17 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
198222 this . loaded = true ;
199223 }
200224
225+ /**
226+ * @inheritdoc
227+ */
228+ ngOnDestroy ( ) : void {
229+ if ( ! this . entry ) {
230+ return ;
231+ }
232+
233+ CoreSync . unblockOperation ( AddonBlogProvider . COMPONENT , this . entry . id ?? this . entry . created ) ;
234+ }
235+
201236 /**
202237 * Retrieves blog entry.
203238 *
@@ -270,45 +305,93 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
270305
271306 const loading = await CoreLoadings . show ( 'core.sending' , true ) ;
272307
273- if ( this . entry ) {
308+ if ( this . entry ?. id ) {
274309 try {
310+ if ( ! CoreNetwork . isOnline ( ) ) {
311+ const attachmentsId = await this . uploadOrStoreFiles ( { entryId : this . entry . id } ) ;
312+
313+ return await this . saveEntry ( { attachmentsId } ) ;
314+ }
315+
275316 if ( ! CoreFileUploader . areFileListDifferent ( this . files , this . initialFiles ) ) {
276- return await this . saveEntry ( ) ;
317+ return await this . saveEntry ( { } ) ;
277318 }
278319
279320 const { attachmentsid } = await AddonBlog . prepareEntryForEdition ( { entryid : this . entry . id } ) ;
280- const removedFiles = CoreFileUploader . getFilesToDelete ( this . initialFiles , this . files ) ;
321+
322+ const lastModified = CoreNavigator . getRouteNumberParam ( 'lastModified' ) ;
323+ const filters : AddonBlogFilter | undefined = CoreNavigator . getRouteParam ( 'filters' ) ;
324+ const entry = this . entry && 'attachment' in this . entry
325+ ? this . entry
326+ : await CoreUtils . ignoreErrors ( this . getEntry ( { filters, lastModified, entryId : this . entry . id } ) ) ;
327+
328+ const removedFiles = CoreFileUploader . getFilesToDelete ( entry ?. attachmentfiles ?? [ ] , this . files ) ;
281329
282330 if ( removedFiles . length ) {
283331 await CoreFileUploader . deleteDraftFiles ( attachmentsid , removedFiles ) ;
284332 }
285333
286334 await CoreFileUploader . uploadFiles ( attachmentsid , this . files ) ;
287335
288- return await this . saveEntry ( attachmentsid ) ;
336+ return await this . saveEntry ( { attachmentsId : attachmentsid } ) ;
289337 } catch ( error ) {
290- CoreDomUtils . showErrorModalDefault ( error , 'Error updating entry.' ) ;
338+ if ( CoreUtils . isWebServiceError ( error ) ) {
339+ // It's a WebService error, the user cannot send the message so don't store it.
340+ CoreDomUtils . showErrorModalDefault ( error , 'Error updating entry.' ) ;
341+
342+ return ;
343+ }
344+
345+ const attachmentsId = await this . uploadOrStoreFiles ( { entryId : this . entry . id , forceStorage : true } ) ;
346+
347+ return await this . saveEntry ( { attachmentsId, forceOffline : true } ) ;
291348 } finally {
292349 await loading . dismiss ( ) ;
293350 }
294-
295- return ;
296351 }
297352
353+ const created = this . entry ?. created ?? CoreTimeUtils . timestamp ( ) ;
354+
298355 try {
299356 if ( ! this . files . length ) {
300- return await this . saveEntry ( ) ;
357+ return await this . saveEntry ( { created } ) ;
301358 }
302359
303- const attachmentId = await CoreFileUploader . uploadOrReuploadFiles ( this . files , this . component ) ;
304- await this . saveEntry ( attachmentId ) ;
360+ const attachmentsId = await this . uploadOrStoreFiles ( { created } ) ;
361+ await this . saveEntry ( { created , attachmentsId } ) ;
305362 } catch ( error ) {
306- CoreDomUtils . showErrorModalDefault ( error , 'Error creating entry.' ) ;
363+ if ( CoreUtils . isWebServiceError ( error ) ) {
364+ // It's a WebService error, the user cannot send the message so don't store it.
365+ CoreDomUtils . showErrorModalDefault ( error , 'Error creating entry.' ) ;
366+
367+ return ;
368+ }
369+
370+ const attachmentsId = await this . uploadOrStoreFiles ( { created, forceStorage : true } ) ;
371+
372+ return await this . saveEntry ( { attachmentsId, forceOffline : true } ) ;
307373 } finally {
308374 await loading . dismiss ( ) ;
309375 }
310376 }
311377
378+ /**
379+ * Upload or store locally files.
380+ *
381+ * @param param Folder where files will be located.
382+ * @returns folder where files will be located.
383+ */
384+ async uploadOrStoreFiles ( param : AddonBlogEditEntryUploadOrStoreFilesParam ) : Promise < number | CoreFileUploaderStoreFilesResult > {
385+ if ( CoreNetwork . isOnline ( ) && ! param . forceStorage ) {
386+ return await CoreFileUploader . uploadOrReuploadFiles ( this . files , this . component ) ;
387+ }
388+
389+ const folder = 'entryId' in param ? { id : param . entryId } : { created : param . created } ;
390+ const folderPath = await AddonBlogOffline . getOfflineEntryFilesFolderPath ( folder ) ;
391+
392+ return await CoreFileUploader . storeFilesToUpload ( folderPath , this . files ) ;
393+ }
394+
312395 /**
313396 * Expand or collapse associations.
314397 */
@@ -336,27 +419,13 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
336419 return true ;
337420 }
338421
339- /**
340- * Add attachment to options list.
341- *
342- * @param attachmentsId Attachment ID.
343- * @param options Options list.
344- */
345- addAttachments ( attachmentsId : number | undefined , options : AddonBlogAddEntryOption [ ] ) : void {
346- if ( attachmentsId === undefined ) {
347- return ;
348- }
349-
350- options . push ( { name : 'attachmentsid' , value : attachmentsId } ) ;
351- }
352-
353422 /**
354423 * Create or update entry.
355424 *
356- * @param attachmentsId Attachments .
425+ * @param params Creation date and attachments ID .
357426 * @returns Promise resolved when done.
358427 */
359- async saveEntry ( attachmentsId ?: number ) : Promise < void > {
428+ async saveEntry ( params : AddonBlogEditEntrySaveEntryParams ) : Promise < void > {
360429 const { summary, subject, publishState } = this . form . value ;
361430
362431 if ( ! summary || ! subject || ! publishState ) {
@@ -369,11 +438,30 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
369438 { name : 'modassoc' , value : this . form . controls . associateWithModule . value && this . modId ? this . modId : 0 } ,
370439 ] ;
371440
372- this . addAttachments ( attachmentsId , options ) ;
441+ if ( params . attachmentsId ) {
442+ options . push ( { name : 'attachmentsid' , value : params . attachmentsId } ) ;
443+ }
373444
374- this . entry
375- ? await AddonBlog . updateEntry ( { subject, summary, summaryformat : 1 , options , entryid : this . entry . id } )
376- : await AddonBlog . addEntry ( { subject, summary, summaryformat : 1 , options } ) ;
445+ if ( ! this . entry ?. id ) {
446+ await AddonBlog . addEntry ( {
447+ subject,
448+ summary,
449+ summaryformat : 1 ,
450+ options,
451+ created : params . created ?? CoreTimeUtils . timestamp ( ) ,
452+ forceOffline : params . forceOffline ,
453+ } ) ;
454+ } else {
455+ await AddonBlog . updateEntry ( {
456+ subject,
457+ summary,
458+ summaryformat : 1 ,
459+ options,
460+ forceOffline : params . forceOffline ,
461+ entryid : this . entry . id ,
462+ created : this . entry . created ,
463+ } ) ;
464+ }
377465
378466 CoreEvents . trigger ( ADDON_BLOG_ENTRY_UPDATED ) ;
379467 this . forceLeave = true ;
@@ -382,10 +470,36 @@ export class AddonBlogEditEntryPage implements CanLeave, OnInit {
382470 return CoreNavigator . back ( ) ;
383471 }
384472
473+ /**
474+ * Retrieves a formatted blog offline entry.
475+ *
476+ * @param params Entry creation date or entry ID.
477+ * @returns Formatted entry.
478+ */
479+ async getFormattedBlogOfflineEntry (
480+ params : AddonBlogEditGetFormattedBlogOfflineEntryParams ,
481+ ) : Promise < AddonBlogEditEntryFormattedOfflinePost | undefined > {
482+ const entryRecord = await AddonBlogOffline . getOfflineEntry ( params ) ;
483+
484+ return entryRecord ? await AddonBlog . formatOfflineEntry ( entryRecord ) : undefined ;
485+ }
486+
385487}
386488
387- type AddonBlogEditEntryGetEntryParams = {
388- entryId : number ;
389- filters ?: AddonBlogFilter ;
390- lastModified ?: number ;
489+ type AddonBlogEditGetFormattedBlogOfflineEntryParams = { id : number } | { created : number } ;
490+
491+ type AddonBlogEditEntryUploadOrStoreFilesParam = ( { entryId : number } | { created : number } ) & { forceStorage ?: boolean } ;
492+
493+ type AddonBlogEditEntryGetEntryParams = { entryId : number ; filters ?: AddonBlogFilter ; lastModified ?: number } ;
494+
495+ type AddonBlogEditEntryPost = Omit < AddonBlogPost , 'id' > & { id ?: number } ;
496+
497+ type AddonBlogEditEntrySaveEntryParams = {
498+ created ?: number ;
499+ attachmentsId ?: number | CoreFileUploaderStoreFilesResult ;
500+ forceOffline ?: boolean ;
391501} ;
502+
503+ type AddonBlogEditEntryFormattedOfflinePost = Omit <
504+ AddonBlogEditEntryPost , | 'attachment' | 'attachmentfiles' | 'rating' | 'format' | 'usermodified' | 'module'
505+ > & { attachmentfiles ?: CoreFileEntry [ ] } ;
0 commit comments