MDL-54796 forms: Editor restore event created.
[moodle.git] / lib / editor / atto / yui / src / editor / js / autosave.js
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/>.
15 /* global NOTIFY_WARNING, NOTIFY_INFO */
16 /* eslint-disable no-unused-vars */
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  */
28 var SUCCESS_MESSAGE_TIMEOUT = 5000,
29     RECOVER_MESSAGE_TIMEOUT = 60000,
30     LOGNAME_AUTOSAVE = 'moodle-editor_atto-editor-autosave';
32 function EditorAutosave() {}
34 EditorAutosave.ATTRS= {
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     },
47     /**
48      * The time between autosaves (in seconds).
49      *
50      * @attribute autosaveFrequency
51      * @type Number
52      * @default 60
53      * @writeOnce
54      */
55     autosaveFrequency: {
56         value: 60,
57         writeOnce: true
58     },
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
70     }
71 };
73 EditorAutosave.prototype = {
75     /**
76      * The text that was auto saved in the last request.
77      *
78      * @property lastText
79      * @type string
80      */
81     lastText: "",
83     /**
84      * Autosave instance.
85      *
86      * @property autosaveInstance
87      * @type string
88      */
89     autosaveInstance: null,
91     /**
92      * Autosave Timer.
93      *
94      * @property autosaveTimer
95      * @type object
96      */
97     autosaveTimer: null,
99     /**
100      * Initialize the autosave process
101      *
102      * @method setupAutosave
103      * @chainable
104      */
105     setupAutosave: function() {
106         var draftid = -1,
107             form,
108             optiontype = null,
109             options = this.get('filepickeroptions'),
110             params;
112         if (!this.get('autosaveEnabled')) {
113             // Autosave disabled for this instance.
114             return;
115         }
117         this.autosaveInstance = Y.stamp(this);
118         for (optiontype in options) {
119             if (typeof options[optiontype].itemid !== "undefined") {
120                 draftid = options[optiontype].itemid;
121             }
122         }
124         // First see if there are any saved drafts.
125         // Make an ajax request.
126         params = {
127             contextid: this.get('contextid'),
128             action: 'resume',
129             draftid: draftid,
130             elementid: this.get('elementid'),
131             pageinstance: this.autosaveInstance,
132             pagehash: this.get('pageHash')
133         };
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                 }
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                 }
152                 // Check for IE 9 and 10.
153                 if (response.result === '<p>&nbsp;</p>' || response.result === '<p><br>&nbsp;</p>') {
154                     response.result = '';
155                 }
157                 if (response.error || typeof response.result === 'undefined') {
158                     Y.log('Error occurred recovering draft text: ' + response.error, 'debug', LOGNAME_AUTOSAVE);
159                     this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
160                             NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
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);
165                 }
166                 this._fireSelectionChanged();
168             },
169             failure: function() {
170                 this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
171                         NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
172             }
173         });
175         // Now setup the timer for periodic saves.
176         var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
177         this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
179         // Now setup the listener for form submission.
180         form = this.textarea.ancestor('form');
181         if (form) {
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             });
189         }
190         return this;
191     },
193     /**
194      * Recover a previous version of this text and show a message.
195      *
196      * @method recoverText
197      * @param {String} text
198      * @chainable
199      */
200     recoverText: function(text) {
201         this.editor.setHTML(text);
202         this.saveSelection();
203         this.updateOriginal();
204         this.lastText = text;
206         this.showMessage(M.util.get_string('textrecovered', 'editor_atto'),
207                 NOTIFY_INFO, RECOVER_MESSAGE_TIMEOUT);
209         // Fire an event that the editor content has changed.
210         require(['core/event'], function(event) {
211             event.notifyEditorContentRestored();
212         });
214         return this;
215     },
217     /**
218      * Save a single draft via ajax.
219      *
220      * @method saveDraft
221      * @chainable
222      */
223     saveDraft: function() {
224         var url, params;
226         if (!this.editor.getDOMNode()) {
227             // Stop autosaving if the editor was removed from the page.
228             this.autosaveTimer.cancel();
229             return;
230         }
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         }
235         var newText = this.textarea.get('value');
237         if (newText !== this.lastText) {
238             Y.log('Autosave text', 'debug', LOGNAME_AUTOSAVE);
240             // Make an ajax request.
241             url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
242             params = {
243                 sesskey: M.cfg.sesskey,
244                 contextid: this.get('contextid'),
245                 action: 'save',
246                 drafttext: newText,
247                 elementid: this.get('elementid'),
248                 pagehash: this.get('pageHash'),
249                 pageinstance: this.autosaveInstance
250             };
252             // Reusable error handler - must be passed the correct context.
253             var ajaxErrorFunction = function(response) {
254                 var errorDuration = parseInt(this.get('autosaveFrequency'), 10) * 1000;
255                 Y.log('Error while autosaving text', 'warn', LOGNAME_AUTOSAVE);
256                 Y.log(response, 'warn', LOGNAME_AUTOSAVE);
257                 this.showMessage(M.util.get_string('autosavefailed', 'editor_atto'), NOTIFY_WARNING, errorDuration);
258             };
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);
270                     }
271                 }
272             });
273         }
274         return this;
275     }
276 };
278 Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosave]);