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 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<void> {
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 == '<p></p>' || draftText == '<p><br></p>' || draftText == '<br>' ||
draftText == '<p>&nbsp;</p>' || draftText == '<p><br>&nbsp;</p>') {
@ -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;

View File

@ -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<string> {
pageInstance: string, originalContent?: string, siteId?: string): Promise<CoreEditorDraft> {
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<void> {
pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise<void> {
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.
};