Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
16
/**
17
 * @component  atto_undo
18
 * @copyright  2014 Jerome Mouneyrac
19
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
20
 */
21
 
22
/**
23
 * @module moodle-atto_undo-button
24
 */
25
 
26
/**
27
 * Atto text editor undo plugin.
28
 *
29
 * @namespace M.atto_undo
30
 * @class button
31
 * @extends M.editor_atto.EditorPlugin
32
 */
33
 
34
Y.namespace('M.atto_undo').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
35
    /**
36
     * The maximum saved number of undo steps.
37
     *
38
     * @property _maxUndos
39
     * @type {Number} The maximum number of saved undos.
40
     * @default 40
41
     * @private
42
     */
43
    _maxUndos: 40,
44
 
45
    /**
46
     * History of edits.
47
     *
48
     * @property _undoStack
49
     * @type {Array} The elements of the array are the html strings that make a snapshot
50
     * @private
51
     */
52
    _undoStack: null,
53
 
54
    /**
55
     * History of edits.
56
     *
57
     * @property _redoStack
58
     * @type {Array} The elements of the array are the html strings that make a snapshot
59
     * @private
60
     */
61
    _redoStack: null,
62
 
63
    /**
64
     * Add the buttons to the toolbar
65
     *
66
     * @method initializer
67
     */
68
    initializer: function() {
69
        // Initialise the undo and redo stacks.
70
        this._undoStack = [];
71
        this._redoStack = [];
72
 
73
        this.addButton({
74
            title: 'undo',
75
            icon: 'e/undo',
76
            callback: this._undoHandler,
77
            buttonName: 'undo',
78
            keys: 90
79
        });
80
 
81
        this.addButton({
82
            title: 'redo',
83
            icon: 'e/redo',
84
            callback: this._redoHandler,
85
            buttonName: 'redo',
86
            keys: 89
87
        });
88
 
89
        // Enable the undo once everything has loaded.
90
        this.get('host').on('pluginsloaded', function() {
91
            // Adds the current value to the stack.
92
            this._addToUndo(this._getHTML());
93
            this.get('host').on('atto:selectionchanged', this._changeListener, this);
94
        }, this);
95
 
96
        this._updateButtonsStates();
97
    },
98
 
99
    /**
100
     * Adds an element to the redo stack.
101
     *
102
     * @method _addToRedo
103
     * @private
104
     * @param {String} html The HTML content to save.
105
     */
106
    _addToRedo: function(html) {
107
        this._redoStack.push(html);
108
    },
109
 
110
    /**
111
     * Adds an element to the undo stack.
112
     *
113
     * @method _addToUndo
114
     * @private
115
     * @param {String} html The HTML content to save.
116
     * @param {Boolean} [clearRedo=false] Whether or not we should clear the redo stack.
117
     */
118
    _addToUndo: function(html, clearRedo) {
119
        var last = this._undoStack[this._undoStack.length - 1];
120
 
121
        if (typeof clearRedo === 'undefined') {
122
            clearRedo = false;
123
        }
124
 
125
        if (last !== html) {
126
            this._undoStack.push(html);
127
            if (clearRedo) {
128
                this._redoStack = [];
129
            }
130
        }
131
 
132
        while (this._undoStack.length > this._maxUndos) {
133
            this._undoStack.shift();
134
        }
135
    },
136
 
137
    /**
138
     * Get the editor HTML.
139
     *
140
     * @method _getHTML
141
     * @private
142
     * @return {String} The HTML.
143
     */
144
    _getHTML: function() {
145
        return this.get('host').getCleanHTML();
146
    },
147
 
148
    /**
149
     * Get an element on the redo stack.
150
     *
151
     * @method _getRedo
152
     * @private
153
     * @return {String} The HTML to restore, or undefined.
154
     */
155
    _getRedo: function() {
156
        return this._redoStack.pop();
157
    },
158
 
159
    /**
160
     * Get an element on the undo stack.
161
     *
162
     * @method _getUndo
163
     * @private
164
     * @param {String} current The current HTML.
165
     * @return {String} The HTML to restore.
166
     */
167
    _getUndo: function(current) {
168
        if (this._undoStack.length === 1) {
169
            return this._undoStack[0];
170
        }
171
 
172
        var last = this._undoStack.pop();
173
        if (last === current) {
174
            // Oops, the latest undo step is the current content, we should unstack once more.
175
            // There is no need to do that in a loop as the same stack should never contain duplicates.
176
            last = this._undoStack.pop();
177
        }
178
 
179
        // We always need to keep the first element of the stack.
180
        if (this._undoStack.length === 0) {
181
            this._addToUndo(last);
182
        }
183
 
184
        return last;
185
    },
186
 
187
    /**
188
     * Restore a value from a stack.
189
     *
190
     * @method _restoreValue
191
     * @private
192
     * @param {String} html The HTML to restore in the editor.
193
     */
194
    _restoreValue: function(html) {
195
        this.editor.setHTML(html);
196
        // We always add the restored value to the stack, otherwise an event could think that
197
        // the content has changed and clear the redo stack.
198
        this._addToUndo(html);
199
    },
200
 
201
    /**
202
     * Update the states of the buttons.
203
     *
204
     * @method _updateButtonsStates
205
     * @private
206
     */
207
    _updateButtonsStates: function() {
208
        if (this._undoStack.length > 1) {
209
            this.enableButtons('undo');
210
        } else {
211
            this.disableButtons('undo');
212
        }
213
 
214
        if (this._redoStack.length > 0) {
215
            this.enableButtons('redo');
216
        } else {
217
            this.disableButtons('redo');
218
        }
219
    },
220
 
221
    /**
222
     * Handle a click on undo
223
     *
224
     * @method _undoHandler
225
     * @param {Event} The click event
226
     * @private
227
     */
228
    _undoHandler: function(e) {
229
        e.preventDefault();
230
        var html = this._getHTML(),
231
            undo = this._getUndo(html);
232
 
233
        // Edge case, but that could happen. We do nothing when the content equals the undo step.
234
        if (html === undo) {
235
            this._updateButtonsStates();
236
            return;
237
        }
238
 
239
        // Restore the value.
240
        this._restoreValue(undo);
241
 
242
        // Add to the redo stack.
243
        this._addToRedo(html);
244
 
245
        // Update the button states.
246
        this._updateButtonsStates();
247
    },
248
 
249
    /**
250
     * Handle a click on redo
251
     *
252
     * @method _redoHandler
253
     * @param {Event} The click event
254
     * @private
255
     */
256
    _redoHandler: function(e) {
257
        e.preventDefault();
258
 
259
        // Don't do anything if redo stack is empty.
260
        if (this._redoStack.length === 0) {
261
            return;
262
        }
263
 
264
        var html = this._getHTML(),
265
            redo = this._getRedo();
266
 
267
        // Edge case, but that could happen. We do nothing when the content equals the redo step.
268
        if (html === redo) {
269
            this._updateButtonsStates();
270
            return;
271
        }
272
        // Restore the value.
273
        this._restoreValue(redo);
274
 
275
        // Update the button states.
276
        this._updateButtonsStates();
277
    },
278
 
279
    /**
280
     * If we are significantly different from the last saved version, save a new version.
281
     *
282
     * @method _changeListener
283
     * @param {EventFacade} The click event
284
     * @private
285
     */
286
    _changeListener: function(e) {
287
        if (e.event && e.event.type.indexOf('key') !== -1) {
288
            // These are the 4 arrow keys.
289
            if ((e.event.keyCode !== 39) &&
290
                    (e.event.keyCode !== 37) &&
291
                    (e.event.keyCode !== 40) &&
292
                    (e.event.keyCode !== 38)) {
293
                // Skip this event type. We only want focus/mouse/arrow events.
294
                return;
295
            }
296
        }
297
 
298
        this._addToUndo(this._getHTML(), true);
299
        this._updateButtonsStates();
300
    }
301
});