From 90f96b762be4feec141699b3d8003619e694dd37 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 7 Feb 2020 10:22:59 +0100 Subject: [PATCH] MOBILE-3323 editor: Store editor original content to detect changes --- .../rich-text-editor/rich-text-editor.ts | 33 +++++++++--- src/core/editor/providers/editor-offline.ts | 51 ++++++++++++++----- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/core/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/editor/components/rich-text-editor/rich-text-editor.ts index c619aaf00..2fa0bc613 100644 --- a/src/core/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/editor/components/rich-text-editor/rich-text-editor.ts @@ -103,6 +103,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe protected hideMessageTimeout: NodeJS.Timer; protected lastDraft = ''; protected draftWasRestored = false; + protected originalContent: string; constructor( protected domUtils: CoreDomUtilsProvider, @@ -133,6 +134,8 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe // Setup the editor. this.editorElement = this.editor.nativeElement as HTMLDivElement; this.setContent(this.control.value); + this.originalContent = this.control.value; + this.lastDraft = this.control.value; this.editorElement.onchange = this.onChange.bind(this); this.editorElement.onkeyup = this.onChange.bind(this); this.editorElement.onpaste = this.onChange.bind(this); @@ -141,8 +144,19 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe // Listen for changes on the control to update the editor (if it is updated from outside of this component). this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => { - if (!this.draftWasRestored) { + if (!this.draftWasRestored || this.originalContent != param) { + // Apply the new content. this.setContent(param); + this.originalContent = param; + this.infoMessage = null; + + // Save a draft so the original content is saved. + this.lastDraft = param; + this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams, this.pageInstance, param, param); + } else { + // A draft was restored and the content hasn't changed in the site. Use the draft value instead of this one. + this.control.setValue(this.lastDraft, {emitEvent: false}); } }); @@ -740,14 +754,16 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe */ protected async restoreDraft(): Promise { try { - let draftText = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId, - this.draftExtraParams, this.pageInstance); + const entry = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams, this.pageInstance, this.originalContent); - if (typeof draftText == 'undefined') { + if (typeof entry == 'undefined') { // No draft found. return; } + let draftText = entry.drafttext; + // Revert untouched editor contents to an empty string. if (draftText == '

' || draftText == '


' || draftText == '
' || draftText == '

 

' || draftText == '


 

') { @@ -760,9 +776,12 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe this.setContent(draftText); this.lastDraft = draftText; this.draftWasRestored = true; + this.originalContent = entry.originalcontent; - // Notify the user. - this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME); + if (entry.drafttext != entry.originalcontent) { + // Notify the user. + this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME); + } } } catch (error) { // Ignore errors, shouldn't happen. @@ -783,7 +802,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe try { await this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId, - this.draftExtraParams, this.pageInstance, newText); + this.draftExtraParams, this.pageInstance, newText, this.originalContent); // Draft saved, notify the user. this.lastDraft = newText; diff --git a/src/core/editor/providers/editor-offline.ts b/src/core/editor/providers/editor-offline.ts index 0f0b5e28c..17d8b44ce 100644 --- a/src/core/editor/providers/editor-offline.ts +++ b/src/core/editor/providers/editor-offline.ts @@ -53,25 +53,29 @@ export class CoreEditorOfflineProvider { { name: 'drafttext', type: 'TEXT', - notNull: true + notNull: true, }, { name: 'pageinstance', type: 'TEXT', - notNull: true + notNull: true, }, { name: 'timecreated', type: 'INTEGER', - notNull: true + notNull: true, }, { name: 'timemodified', type: 'INTEGER', - notNull: true + notNull: true, + }, + { + name: 'originalcontent', + type: 'TEXT', }, ], - primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'] + primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'], }, ], }; @@ -158,11 +162,12 @@ export class CoreEditorOfflineProvider { * @param elementId Element ID. * @param extraParams Object with extra params to identify the draft. * @param pageInstance Unique identifier to prevent storing data from several sources at the same time. + * @param originalContent Original content of the editor. * @param siteId Site ID. If not defined, current site. - * @return Promise resolved with the draft text. Undefined if no draft stored. + * @return Promise resolved with the draft data. Undefined if no draft stored. */ async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, - pageInstance: string, siteId?: string): Promise { + pageInstance: string, originalContent?: string, siteId?: string): Promise { try { // Check if there is a draft stored. @@ -175,15 +180,21 @@ export class CoreEditorOfflineProvider { entry.pageinstance = pageInstance; entry.timemodified = Date.now(); + if (originalContent && entry.originalcontent != originalContent) { + entry.originalcontent = originalContent; + entry.drafttext = ''; // "Discard" the draft. + } + await db.insertRecord(this.DRAFT_TABLE, entry); } catch (error) { // Ignore errors saving the draft. It shouldn't happen. } - return entry.drafttext; + return entry; } catch (error) { // No draft stored. Store an empty draft to save the pageinstance. - await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', siteId); + await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', originalContent, + siteId); } } @@ -196,11 +207,12 @@ export class CoreEditorOfflineProvider { * @param extraParams Object with extra params to identify the draft. * @param pageInstance Unique identifier to prevent storing data from several sources at the same time. * @param draftText The text to store. + * @param originalContent Original content of the editor. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, - pageInstance: string, draftText: string, siteId?: string): Promise { + pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise { let timecreated = Date.now(); let entry: CoreEditorDraft; @@ -214,10 +226,17 @@ export class CoreEditorOfflineProvider { // No draft already stored. } - if (entry && entry.pageinstance != pageInstance) { - this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` + - `element '${elementId}'`); - throw null; + if (entry) { + if (entry.pageinstance != pageInstance) { + this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` + + `element '${elementId}'`); + throw null; + } + + if (!originalContent) { + // Original content not set, use the one in the entry. + originalContent = entry.originalcontent; + } } const db = await this.sitesProvider.getSiteDb(siteId); @@ -228,6 +247,9 @@ export class CoreEditorOfflineProvider { data.pageinstance = pageInstance; data.timecreated = timecreated; data.timemodified = Date.now(); + if (originalContent) { + data.originalcontent = originalContent; + } await db.insertRecord(this.DRAFT_TABLE, data); } @@ -251,4 +273,5 @@ type CoreEditorDraft = CoreEditorDraftPrimaryData & { pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time. timecreated?: number; // Time created. timemodified?: number; // Time modified. + originalcontent?: string; // Original content of the editor. };