MDL-54796 forms: Editor restore event created.
[moodle.git] / lib / editor / atto / yui / src / editor / js / autosave.js
CommitLineData
2ba6706d
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
ad3f8cd1
DP
15/* global NOTIFY_WARNING, NOTIFY_INFO */
16/* eslint-disable no-unused-vars */
2ba6706d
DW
17
18/**
19 * A autosave function for the Atto editor.
20 *
21 * @module moodle-editor_atto-autosave
22 * @submodule autosave-base
23 * @package editor_atto
24 * @copyright 2014 Damyon Wiese
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
6bfd450a 28var SUCCESS_MESSAGE_TIMEOUT = 5000,
19549f8b
DW
29 RECOVER_MESSAGE_TIMEOUT = 60000,
30 LOGNAME_AUTOSAVE = 'moodle-editor_atto-editor-autosave';
2ba6706d
DW
31
32function EditorAutosave() {}
33
34EditorAutosave.ATTRS= {
6bfd450a
DW
35 /**
36 * Enable/Disable auto save for this instance.
37 *
38 * @attribute autosaveEnabled
39 * @type Boolean
40 * @writeOnce
41 */
42 autosaveEnabled: {
43 value: true,
44 writeOnce: true
45 },
46
47 /**
48 * The time between autosaves (in seconds).
49 *
50 * @attribute autosaveFrequency
19549f8b 51 * @type Number
6bfd450a
DW
52 * @default 60
53 * @writeOnce
54 */
55 autosaveFrequency: {
56 value: 60,
57 writeOnce: true
56579fb6
DW
58 },
59
c07f86ce
DW
60 /**
61 * Unique hash for this page instance. Calculated from $PAGE->url in php.
62 *
63 * @attribute pageHash
64 * @type String
65 * @writeOnce
66 */
67 pageHash: {
68 value: '',
69 writeOnce: true
6bfd450a 70 }
2ba6706d
DW
71};
72
73EditorAutosave.prototype = {
74
75 /**
76 * The text that was auto saved in the last request.
77 *
78 * @property lastText
79 * @type string
80 */
e924e1b3 81 lastText: "",
2ba6706d
DW
82
83 /**
84 * Autosave instance.
85 *
86 * @property autosaveInstance
87 * @type string
88 */
89 autosaveInstance: null,
90
b803df81
DW
91 /**
92 * Autosave Timer.
93 *
94 * @property autosaveTimer
95 * @type object
96 */
97 autosaveTimer: null,
98
2ba6706d
DW
99 /**
100 * Initialize the autosave process
101 *
102 * @method setupAutosave
103 * @chainable
104 */
105 setupAutosave: function() {
6bfd450a 106 var draftid = -1,
c4e2c671 107 form,
6bfd450a 108 optiontype = null,
557f44d9 109 options = this.get('filepickeroptions'),
240a9de4 110 params;
2ba6706d
DW
111
112 if (!this.get('autosaveEnabled')) {
113 // Autosave disabled for this instance.
114 return;
115 }
116
2ba6706d 117 this.autosaveInstance = Y.stamp(this);
2ba6706d
DW
118 for (optiontype in options) {
119 if (typeof options[optiontype].itemid !== "undefined") {
120 draftid = options[optiontype].itemid;
121 }
122 }
123
124 // First see if there are any saved drafts.
125 // Make an ajax request.
2ba6706d 126 params = {
2ba6706d
DW
127 contextid: this.get('contextid'),
128 action: 'resume',
2ba6706d
DW
129 draftid: draftid,
130 elementid: this.get('elementid'),
131 pageinstance: this.autosaveInstance,
c07f86ce 132 pagehash: this.get('pageHash')
2ba6706d
DW
133 };
134
240a9de4
FM
135 this.autosaveIo(params, this, {
136 success: function(response) {
137 if (response === null) {
138 // This can happen when there is nothing to resume from.
139 return;
140 } else if (!response) {
141 Y.log('Invalid response received.', 'debug', LOGNAME_AUTOSAVE);
142 return;
143 }
e924e1b3 144
240a9de4
FM
145 // Revert untouched editor contents to an empty string.
146 // Check for FF and Chrome.
147 if (response.result === '<p></p>' || response.result === '<p><br></p>' ||
148 response.result === '<br>') {
149 response.result = '';
150 }
e924e1b3 151
240a9de4
FM
152 // Check for IE 9 and 10.
153 if (response.result === '<p>&nbsp;</p>' || response.result === '<p><br>&nbsp;</p>') {
154 response.result = '';
155 }
7b62567e 156
240a9de4
FM
157 if (response.error || typeof response.result === 'undefined') {
158 Y.log('Error occurred recovering draft text: ' + response.error, 'debug', LOGNAME_AUTOSAVE);
557f44d9
AN
159 this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
160 NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
240a9de4
FM
161 } else if (response.result !== this.textarea.get('value') &&
162 response.result !== '') {
163 Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
164 this.recoverText(response.result);
2ba6706d 165 }
240a9de4
FM
166 this._fireSelectionChanged();
167
168 },
169 failure: function() {
170 this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
171 NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
19549f8b 172 }
2ba6706d
DW
173 });
174
175 // Now setup the timer for periodic saves.
6bfd450a 176 var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
b803df81 177 this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
2ba6706d
DW
178
179 // Now setup the listener for form submission.
c4e2c671
AN
180 form = this.textarea.ancestor('form');
181 if (form) {
240a9de4
FM
182 this.autosaveIoOnSubmit(form, {
183 action: 'reset',
184 contextid: this.get('contextid'),
185 elementid: this.get('elementid'),
186 pageinstance: this.autosaveInstance,
187 pagehash: this.get('pageHash')
188 });
c4e2c671 189 }
2ba6706d
DW
190 return this;
191 },
192
2ba6706d 193 /**
6bfd450a 194 * Recover a previous version of this text and show a message.
2ba6706d
DW
195 *
196 * @method recoverText
197 * @param {String} text
198 * @chainable
199 */
200 recoverText: function(text) {
6bfd450a
DW
201 this.editor.setHTML(text);
202 this.saveSelection();
203 this.updateOriginal();
204 this.lastText = text;
205
557f44d9
AN
206 this.showMessage(M.util.get_string('textrecovered', 'editor_atto'),
207 NOTIFY_INFO, RECOVER_MESSAGE_TIMEOUT);
2ba6706d 208
b5594973
AG
209 // Fire an event that the editor content has changed.
210 require(['core/event'], function(event) {
211 event.notifyEditorContentRestored();
212 });
213
2ba6706d
DW
214 return this;
215 },
216
217 /**
218 * Save a single draft via ajax.
219 *
220 * @method saveDraft
221 * @chainable
222 */
223 saveDraft: function() {
557f44d9 224 var url, params;
b803df81
DW
225
226 if (!this.editor.getDOMNode()) {
227 // Stop autosaving if the editor was removed from the page.
228 this.autosaveTimer.cancel();
229 return;
230 }
3a7887ba
DW
231 // Only copy the text from the div to the textarea if the textarea is not currently visible.
232 if (!this.editor.get('hidden')) {
233 this.updateOriginal();
234 }
2ba6706d
DW
235 var newText = this.textarea.get('value');
236
237 if (newText !== this.lastText) {
19549f8b 238 Y.log('Autosave text', 'debug', LOGNAME_AUTOSAVE);
2ba6706d
DW
239
240 // Make an ajax request.
56579fb6 241 url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
2ba6706d
DW
242 params = {
243 sesskey: M.cfg.sesskey,
244 contextid: this.get('contextid'),
245 action: 'save',
246 drafttext: newText,
247 elementid: this.get('elementid'),
c07f86ce 248 pagehash: this.get('pageHash'),
2ba6706d
DW
249 pageinstance: this.autosaveInstance
250 };
19549f8b
DW
251
252 // Reusable error handler - must be passed the correct context.
240a9de4 253 var ajaxErrorFunction = function(response) {
19549f8b 254 var errorDuration = parseInt(this.get('autosaveFrequency'), 10) * 1000;
240a9de4 255 Y.log('Error while autosaving text', 'warn', LOGNAME_AUTOSAVE);
19549f8b
DW
256 Y.log(response, 'warn', LOGNAME_AUTOSAVE);
257 this.showMessage(M.util.get_string('autosavefailed', 'editor_atto'), NOTIFY_WARNING, errorDuration);
258 };
2ba6706d 259
240a9de4
FM
260 this.autosaveIo(params, this, {
261 failure: ajaxErrorFunction,
262 success: function(response) {
263 if (response && response.error) {
264 Y.soon(Y.bind(ajaxErrorFunction, this, [response]));
265 } else {
266 // All working.
267 this.lastText = newText;
268 this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'),
269 NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
2ba6706d 270 }
19549f8b 271 }
2ba6706d 272 });
2ba6706d
DW
273 }
274 return this;
275 }
276};
277
278Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosave]);