MOBILE-3323 editor: Store editor original content to detect changes

main
Dani Palou 2020-02-07 10:22:59 +01:00
parent 0fcdd494de
commit 90f96b762b
2 changed files with 63 additions and 21 deletions

View File

@ -103,6 +103,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
protected hideMessageTimeout: NodeJS.Timer; protected hideMessageTimeout: NodeJS.Timer;
protected lastDraft = ''; protected lastDraft = '';
protected draftWasRestored = false; protected draftWasRestored = false;
protected originalContent: string;
constructor( constructor(
protected domUtils: CoreDomUtilsProvider, protected domUtils: CoreDomUtilsProvider,
@ -133,6 +134,8 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
// Setup the editor. // Setup the editor.
this.editorElement = this.editor.nativeElement as HTMLDivElement; this.editorElement = this.editor.nativeElement as HTMLDivElement;
this.setContent(this.control.value); this.setContent(this.control.value);
this.originalContent = this.control.value;
this.lastDraft = this.control.value;
this.editorElement.onchange = this.onChange.bind(this); this.editorElement.onchange = this.onChange.bind(this);
this.editorElement.onkeyup = this.onChange.bind(this); this.editorElement.onkeyup = this.onChange.bind(this);
this.editorElement.onpaste = 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). // 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) => { this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => {
if (!this.draftWasRestored) { if (!this.draftWasRestored || this.originalContent != param) {
// Apply the new content.
this.setContent(param); 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<void> { protected async restoreDraft(): Promise<void> {
try { try {
let draftText = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId, const entry = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId,
this.draftExtraParams, this.pageInstance); this.draftExtraParams, this.pageInstance, this.originalContent);
if (typeof draftText == 'undefined') { if (typeof entry == 'undefined') {
// No draft found. // No draft found.
return; return;
} }
let draftText = entry.drafttext;
// Revert untouched editor contents to an empty string. // Revert untouched editor contents to an empty string.
if (draftText == '<p></p>' || draftText == '<p><br></p>' || draftText == '<br>' || if (draftText == '<p></p>' || draftText == '<p><br></p>' || draftText == '<br>' ||
draftText == '<p>&nbsp;</p>' || draftText == '<p><br>&nbsp;</p>') { draftText == '<p>&nbsp;</p>' || draftText == '<p><br>&nbsp;</p>') {
@ -760,9 +776,12 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
this.setContent(draftText); this.setContent(draftText);
this.lastDraft = draftText; this.lastDraft = draftText;
this.draftWasRestored = true; this.draftWasRestored = true;
this.originalContent = entry.originalcontent;
// Notify the user. if (entry.drafttext != entry.originalcontent) {
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME); // Notify the user.
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME);
}
} }
} catch (error) { } catch (error) {
// Ignore errors, shouldn't happen. // Ignore errors, shouldn't happen.
@ -783,7 +802,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
try { try {
await this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId, 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. // Draft saved, notify the user.
this.lastDraft = newText; this.lastDraft = newText;

View File

@ -53,25 +53,29 @@ export class CoreEditorOfflineProvider {
{ {
name: 'drafttext', name: 'drafttext',
type: 'TEXT', type: 'TEXT',
notNull: true notNull: true,
}, },
{ {
name: 'pageinstance', name: 'pageinstance',
type: 'TEXT', type: 'TEXT',
notNull: true notNull: true,
}, },
{ {
name: 'timecreated', name: 'timecreated',
type: 'INTEGER', type: 'INTEGER',
notNull: true notNull: true,
}, },
{ {
name: 'timemodified', name: 'timemodified',
type: 'INTEGER', 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 elementId Element ID.
* @param extraParams Object with extra params to identify the draft. * @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 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. * @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}, async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
pageInstance: string, siteId?: string): Promise<string> { pageInstance: string, originalContent?: string, siteId?: string): Promise<CoreEditorDraft> {
try { try {
// Check if there is a draft stored. // Check if there is a draft stored.
@ -175,15 +180,21 @@ export class CoreEditorOfflineProvider {
entry.pageinstance = pageInstance; entry.pageinstance = pageInstance;
entry.timemodified = Date.now(); entry.timemodified = Date.now();
if (originalContent && entry.originalcontent != originalContent) {
entry.originalcontent = originalContent;
entry.drafttext = ''; // "Discard" the draft.
}
await db.insertRecord(this.DRAFT_TABLE, entry); await db.insertRecord(this.DRAFT_TABLE, entry);
} catch (error) { } catch (error) {
// Ignore errors saving the draft. It shouldn't happen. // Ignore errors saving the draft. It shouldn't happen.
} }
return entry.drafttext; return entry;
} catch (error) { } catch (error) {
// No draft stored. Store an empty draft to save the pageinstance. // 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 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 pageInstance Unique identifier to prevent storing data from several sources at the same time.
* @param draftText The text to store. * @param draftText The text to store.
* @param originalContent Original content of the editor.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
pageInstance: string, draftText: string, siteId?: string): Promise<void> { pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise<void> {
let timecreated = Date.now(); let timecreated = Date.now();
let entry: CoreEditorDraft; let entry: CoreEditorDraft;
@ -214,10 +226,17 @@ export class CoreEditorOfflineProvider {
// No draft already stored. // No draft already stored.
} }
if (entry && entry.pageinstance != pageInstance) { if (entry) {
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` + if (entry.pageinstance != pageInstance) {
`element '${elementId}'`); this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
throw null; `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); const db = await this.sitesProvider.getSiteDb(siteId);
@ -228,6 +247,9 @@ export class CoreEditorOfflineProvider {
data.pageinstance = pageInstance; data.pageinstance = pageInstance;
data.timecreated = timecreated; data.timecreated = timecreated;
data.timemodified = Date.now(); data.timemodified = Date.now();
if (originalContent) {
data.originalcontent = originalContent;
}
await db.insertRecord(this.DRAFT_TABLE, data); 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. pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
timecreated?: number; // Time created. timecreated?: number; // Time created.
timemodified?: number; // Time modified. timemodified?: number; // Time modified.
originalcontent?: string; // Original content of the editor.
}; };