Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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