Skip to content

Commit 90f96b7

Browse files
committed
MOBILE-3323 editor: Store editor original content to detect changes
1 parent 0fcdd49 commit 90f96b7

File tree

2 files changed

+63
-21
lines changed

2 files changed

+63
-21
lines changed

src/core/editor/components/rich-text-editor/rich-text-editor.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
103103
protected hideMessageTimeout: NodeJS.Timer;
104104
protected lastDraft = '';
105105
protected draftWasRestored = false;
106+
protected originalContent: string;
106107

107108
constructor(
108109
protected domUtils: CoreDomUtilsProvider,
@@ -133,6 +134,8 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
133134
// Setup the editor.
134135
this.editorElement = this.editor.nativeElement as HTMLDivElement;
135136
this.setContent(this.control.value);
137+
this.originalContent = this.control.value;
138+
this.lastDraft = this.control.value;
136139
this.editorElement.onchange = this.onChange.bind(this);
137140
this.editorElement.onkeyup = this.onChange.bind(this);
138141
this.editorElement.onpaste = this.onChange.bind(this);
@@ -141,8 +144,19 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
141144

142145
// Listen for changes on the control to update the editor (if it is updated from outside of this component).
143146
this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => {
144-
if (!this.draftWasRestored) {
147+
if (!this.draftWasRestored || this.originalContent != param) {
148+
// Apply the new content.
145149
this.setContent(param);
150+
this.originalContent = param;
151+
this.infoMessage = null;
152+
153+
// Save a draft so the original content is saved.
154+
this.lastDraft = param;
155+
this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId,
156+
this.draftExtraParams, this.pageInstance, param, param);
157+
} else {
158+
// A draft was restored and the content hasn't changed in the site. Use the draft value instead of this one.
159+
this.control.setValue(this.lastDraft, {emitEvent: false});
146160
}
147161
});
148162

@@ -740,14 +754,16 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
740754
*/
741755
protected async restoreDraft(): Promise<void> {
742756
try {
743-
let draftText = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId,
744-
this.draftExtraParams, this.pageInstance);
757+
const entry = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId,
758+
this.draftExtraParams, this.pageInstance, this.originalContent);
745759

746-
if (typeof draftText == 'undefined') {
760+
if (typeof entry == 'undefined') {
747761
// No draft found.
748762
return;
749763
}
750764

765+
let draftText = entry.drafttext;
766+
751767
// Revert untouched editor contents to an empty string.
752768
if (draftText == '<p></p>' || draftText == '<p><br></p>' || draftText == '<br>' ||
753769
draftText == '<p>&nbsp;</p>' || draftText == '<p><br>&nbsp;</p>') {
@@ -760,9 +776,12 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
760776
this.setContent(draftText);
761777
this.lastDraft = draftText;
762778
this.draftWasRestored = true;
779+
this.originalContent = entry.originalcontent;
763780

764-
// Notify the user.
765-
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME);
781+
if (entry.drafttext != entry.originalcontent) {
782+
// Notify the user.
783+
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME);
784+
}
766785
}
767786
} catch (error) {
768787
// Ignore errors, shouldn't happen.
@@ -783,7 +802,7 @@ export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDe
783802

784803
try {
785804
await this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId,
786-
this.draftExtraParams, this.pageInstance, newText);
805+
this.draftExtraParams, this.pageInstance, newText, this.originalContent);
787806

788807
// Draft saved, notify the user.
789808
this.lastDraft = newText;

src/core/editor/providers/editor-offline.ts

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,29 @@ export class CoreEditorOfflineProvider {
5353
{
5454
name: 'drafttext',
5555
type: 'TEXT',
56-
notNull: true
56+
notNull: true,
5757
},
5858
{
5959
name: 'pageinstance',
6060
type: 'TEXT',
61-
notNull: true
61+
notNull: true,
6262
},
6363
{
6464
name: 'timecreated',
6565
type: 'INTEGER',
66-
notNull: true
66+
notNull: true,
6767
},
6868
{
6969
name: 'timemodified',
7070
type: 'INTEGER',
71-
notNull: true
71+
notNull: true,
72+
},
73+
{
74+
name: 'originalcontent',
75+
type: 'TEXT',
7276
},
7377
],
74-
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams']
78+
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'],
7579
},
7680
],
7781
};
@@ -158,11 +162,12 @@ export class CoreEditorOfflineProvider {
158162
* @param elementId Element ID.
159163
* @param extraParams Object with extra params to identify the draft.
160164
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
165+
* @param originalContent Original content of the editor.
161166
* @param siteId Site ID. If not defined, current site.
162-
* @return Promise resolved with the draft text. Undefined if no draft stored.
167+
* @return Promise resolved with the draft data. Undefined if no draft stored.
163168
*/
164169
async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
165-
pageInstance: string, siteId?: string): Promise<string> {
170+
pageInstance: string, originalContent?: string, siteId?: string): Promise<CoreEditorDraft> {
166171

167172
try {
168173
// Check if there is a draft stored.
@@ -175,15 +180,21 @@ export class CoreEditorOfflineProvider {
175180
entry.pageinstance = pageInstance;
176181
entry.timemodified = Date.now();
177182

183+
if (originalContent && entry.originalcontent != originalContent) {
184+
entry.originalcontent = originalContent;
185+
entry.drafttext = ''; // "Discard" the draft.
186+
}
187+
178188
await db.insertRecord(this.DRAFT_TABLE, entry);
179189
} catch (error) {
180190
// Ignore errors saving the draft. It shouldn't happen.
181191
}
182192

183-
return entry.drafttext;
193+
return entry;
184194
} catch (error) {
185195
// No draft stored. Store an empty draft to save the pageinstance.
186-
await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', siteId);
196+
await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', originalContent,
197+
siteId);
187198
}
188199
}
189200

@@ -196,11 +207,12 @@ export class CoreEditorOfflineProvider {
196207
* @param extraParams Object with extra params to identify the draft.
197208
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
198209
* @param draftText The text to store.
210+
* @param originalContent Original content of the editor.
199211
* @param siteId Site ID. If not defined, current site.
200212
* @return Promise resolved when done.
201213
*/
202214
async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
203-
pageInstance: string, draftText: string, siteId?: string): Promise<void> {
215+
pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise<void> {
204216

205217
let timecreated = Date.now();
206218
let entry: CoreEditorDraft;
@@ -214,10 +226,17 @@ export class CoreEditorOfflineProvider {
214226
// No draft already stored.
215227
}
216228

217-
if (entry && entry.pageinstance != pageInstance) {
218-
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
219-
`element '${elementId}'`);
220-
throw null;
229+
if (entry) {
230+
if (entry.pageinstance != pageInstance) {
231+
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
232+
`element '${elementId}'`);
233+
throw null;
234+
}
235+
236+
if (!originalContent) {
237+
// Original content not set, use the one in the entry.
238+
originalContent = entry.originalcontent;
239+
}
221240
}
222241

223242
const db = await this.sitesProvider.getSiteDb(siteId);
@@ -228,6 +247,9 @@ export class CoreEditorOfflineProvider {
228247
data.pageinstance = pageInstance;
229248
data.timecreated = timecreated;
230249
data.timemodified = Date.now();
250+
if (originalContent) {
251+
data.originalcontent = originalContent;
252+
}
231253

232254
await db.insertRecord(this.DRAFT_TABLE, data);
233255
}
@@ -251,4 +273,5 @@ type CoreEditorDraft = CoreEditorDraftPrimaryData & {
251273
pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
252274
timecreated?: number; // Time created.
253275
timemodified?: number; // Time modified.
276+
originalcontent?: string; // Original content of the editor.
254277
};

0 commit comments

Comments
 (0)