MOBILE-3323 editor: Store editor original content to detect changes
parent
0fcdd494de
commit
90f96b762b
|
@ -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> </p>' || draftText == '<p><br> </p>') {
|
draftText == '<p> </p>' || draftText == '<p><br> </p>') {
|
||||||
|
@ -760,10 +776,13 @@ 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;
|
||||||
|
|
||||||
|
if (entry.drafttext != entry.originalcontent) {
|
||||||
// Notify the user.
|
// Notify the user.
|
||||||
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME);
|
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;
|
||||||
|
|
|
@ -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,12 +226,19 @@ export class CoreEditorOfflineProvider {
|
||||||
// No draft already stored.
|
// No draft already stored.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry && entry.pageinstance != pageInstance) {
|
if (entry) {
|
||||||
|
if (entry.pageinstance != pageInstance) {
|
||||||
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
|
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
|
||||||
`element '${elementId}'`);
|
`element '${elementId}'`);
|
||||||
throw null;
|
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);
|
||||||
|
|
||||||
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||||
|
@ -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.
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue