~smagoun/whoopsie/whoopsie-lp1017637

« back to all changes in this revision

Viewing changes to backend/stats/static/js/yui/build/editor-bidi/editor-bidi.js

  • Committer: Evan Dandrea
  • Date: 2012-05-09 05:53:45 UTC
  • Revision ID: evan.dandrea@canonical.com-20120509055345-z2j41tmcbf4as5uf
The backend now lives in lp:daisy and the website (errors.ubuntu.com) now lives in lp:errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.0 (build 5089)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('editor-bidi', function(Y) {
8
 
 
9
 
 
10
 
    /**
11
 
     * Plugin for Editor to support BiDirectional (bidi) text operations.
12
 
     * @class Plugin.EditorBidi
13
 
     * @extends Base
14
 
     * @constructor
15
 
     * @module editor
16
 
     * @submodule editor-bidi
17
 
     */
18
 
 
19
 
 
20
 
    var EditorBidi = function() {
21
 
        EditorBidi.superclass.constructor.apply(this, arguments);
22
 
    }, HOST = 'host', DIR = 'dir', BODY = 'BODY', NODE_CHANGE = 'nodeChange',
23
 
    B_C_CHANGE = 'bidiContextChange', FIRST_P = BODY + ' > p', STYLE = 'style';
24
 
 
25
 
    Y.extend(EditorBidi, Y.Base, {
26
 
        /**
27
 
        * Place holder for the last direction when checking for a switch
28
 
        * @private
29
 
        * @property lastDirection
30
 
        */
31
 
        lastDirection: null,
32
 
        /**
33
 
        * Tells us that an initial bidi check has already been performed
34
 
        * @private
35
 
        * @property firstEvent
36
 
        */
37
 
        firstEvent: null,
38
 
 
39
 
        /**
40
 
        * Method checks to see if the direction of the text has changed based on a nodeChange event.
41
 
        * @private
42
 
        * @method _checkForChange
43
 
        */
44
 
        _checkForChange: function() {
45
 
            var host = this.get(HOST),
46
 
                inst = host.getInstance(),
47
 
                sel = new inst.EditorSelection(),
48
 
                node, direction;
49
 
            
50
 
            if (sel.isCollapsed) {
51
 
                node = EditorBidi.blockParent(sel.focusNode);
52
 
                if (node) {
53
 
                    direction = node.getStyle('direction');
54
 
                    if (direction !== this.lastDirection) {
55
 
                        host.fire(B_C_CHANGE, { changedTo: direction });
56
 
                        this.lastDirection = direction;
57
 
                    }
58
 
                }
59
 
            } else {
60
 
                host.fire(B_C_CHANGE, { changedTo: 'select' });
61
 
                this.lastDirection = null;
62
 
            }
63
 
        },
64
 
 
65
 
        /**
66
 
        * Checked for a change after a specific nodeChange event has been fired.
67
 
        * @private
68
 
        * @method _afterNodeChange
69
 
        */
70
 
        _afterNodeChange: function(e) { 
71
 
            // If this is the first event ever, or an event that can result in a context change
72
 
            if (this.firstEvent || EditorBidi.EVENTS[e.changedType]) {
73
 
                this._checkForChange();
74
 
                this.firstEvent = false;
75
 
            }
76
 
        },
77
 
 
78
 
        /**
79
 
        * Checks for a direction change after a mouseup occurs.
80
 
        * @private
81
 
        * @method _afterMouseUp
82
 
        */
83
 
        _afterMouseUp: function(e) {
84
 
            this._checkForChange();
85
 
            this.firstEvent = false;
86
 
        },
87
 
        initializer: function() {
88
 
            var host = this.get(HOST);
89
 
 
90
 
            this.firstEvent = true;
91
 
            
92
 
            host.after(NODE_CHANGE, Y.bind(this._afterNodeChange, this));
93
 
            host.after('dom:mouseup', Y.bind(this._afterMouseUp, this));
94
 
        }
95
 
    }, {
96
 
        /**
97
 
        * The events to check for a direction change on
98
 
        * @property EVENTS
99
 
        * @static
100
 
        */
101
 
        EVENTS: {
102
 
            'backspace-up': true,
103
 
            'pageup-up': true,
104
 
            'pagedown-down': true,
105
 
            'end-up': true,
106
 
            'home-up': true,
107
 
            'left-up': true,
108
 
            'up-up': true,
109
 
            'right-up': true,
110
 
            'down-up': true,
111
 
            'delete-up': true
112
 
        },
113
 
 
114
 
        /**
115
 
        * More elements may be needed. BODY *must* be in the list to take care of the special case.
116
 
        * 
117
 
        * blockParent could be changed to use inst.EditorSelection.BLOCKS
118
 
        * instead, but that would make Y.Plugin.EditorBidi.blockParent
119
 
        * unusable in non-RTE contexts (it being usable is a nice
120
 
        * side-effect).
121
 
        * @property BLOCKS
122
 
        * @static
123
 
        */
124
 
        //BLOCKS: Y.EditorSelection.BLOCKS+',LI,HR,' + BODY,
125
 
        BLOCKS: Y.EditorSelection.BLOCKS,
126
 
        /**
127
 
        * Template for creating a block element
128
 
        * @static
129
 
        * @property DIV_WRAPPER
130
 
        */
131
 
        DIV_WRAPPER: '<DIV></DIV>',
132
 
        /**
133
 
        * Returns a block parent for a given element
134
 
        * @static
135
 
        * @method blockParent
136
 
        */
137
 
        blockParent: function(node, wrap) {
138
 
            var parent = node, divNode, firstChild;
139
 
            
140
 
            if (!parent) {
141
 
                parent = Y.one(BODY);
142
 
            }
143
 
            
144
 
            if (!parent.test(EditorBidi.BLOCKS)) {
145
 
                parent = parent.ancestor(EditorBidi.BLOCKS);
146
 
            }
147
 
            if (wrap && parent.test(BODY)) {
148
 
                // This shouldn't happen if the RTE handles everything
149
 
                // according to spec: we should get to a P before BODY. But
150
 
                // we don't want to set the direction of BODY even if that
151
 
                // happens, so we wrap everything in a DIV.
152
 
                
153
 
                // The code is based on YUI3's Y.EditorSelection._wrapBlock function.
154
 
                divNode = Y.Node.create(EditorBidi.DIV_WRAPPER);
155
 
                parent.get('children').each(function(node, index) {
156
 
                    if (index === 0) {
157
 
                        firstChild = node;
158
 
                    } else {
159
 
                        divNode.append(node);
160
 
                    }
161
 
                });
162
 
                firstChild.replace(divNode);
163
 
                divNode.prepend(firstChild);
164
 
                parent = divNode;
165
 
            }
166
 
            return parent;
167
 
        },
168
 
        /**
169
 
        * The data key to store on the node.
170
 
        * @static
171
 
        * @property _NODE_SELECTED
172
 
        */
173
 
        _NODE_SELECTED: 'bidiSelected',
174
 
        /**
175
 
        * Generates a list of all the block parents of the current NodeList
176
 
        * @static
177
 
        * @method addParents
178
 
        */
179
 
        addParents: function(nodeArray) {
180
 
            var i, parent, addParent;
181
 
 
182
 
            for (i = 0; i < nodeArray.length; i += 1) {
183
 
                nodeArray[i].setData(EditorBidi._NODE_SELECTED, true);
184
 
            }
185
 
 
186
 
            // This works automagically, since new parents added get processed
187
 
            // later themselves. So if there's a node early in the process that
188
 
            // we haven't discovered some of its siblings yet, thus resulting in
189
 
            // its parent not added, the parent will be added later, since those
190
 
            // siblings will be added to the array and then get processed.
191
 
            for (i = 0; i < nodeArray.length; i += 1) {
192
 
                parent = nodeArray[i].get('parentNode');
193
 
 
194
 
                // Don't add the parent if the parent is the BODY element.
195
 
                // We don't want to change the direction of BODY. Also don't
196
 
                // do it if the parent is already in the list.
197
 
                if (!parent.test(BODY) && !parent.getData(EditorBidi._NODE_SELECTED)) {
198
 
                    addParent = true;
199
 
                    parent.get('children').some(function(sibling) {
200
 
                        if (!sibling.getData(EditorBidi._NODE_SELECTED)) {
201
 
                            addParent = false;
202
 
                            return true; // stop more processing
203
 
                        }
204
 
                    });
205
 
                    if (addParent) {
206
 
                        nodeArray.push(parent);
207
 
                        parent.setData(EditorBidi._NODE_SELECTED, true);
208
 
                    }
209
 
                }
210
 
            }   
211
 
 
212
 
            for (i = 0; i < nodeArray.length; i += 1) {
213
 
                nodeArray[i].clearData(EditorBidi._NODE_SELECTED);
214
 
            }
215
 
 
216
 
            return nodeArray;
217
 
        },
218
 
 
219
 
 
220
 
        /**
221
 
        * editorBidi
222
 
        * @static
223
 
        * @property NAME
224
 
        */
225
 
        NAME: 'editorBidi',
226
 
        /**
227
 
        * editorBidi
228
 
        * @static
229
 
        * @property NS
230
 
        */
231
 
        NS: 'editorBidi',
232
 
        ATTRS: {
233
 
            host: {
234
 
                value: false
235
 
            }
236
 
        },
237
 
        /**
238
 
        * Regex for testing/removing text-align style from an element
239
 
        * @static
240
 
        * @property RE_TEXT_ALIGN
241
 
        */
242
 
        RE_TEXT_ALIGN: /text-align:\s*\w*\s*;/,
243
 
        /**
244
 
        * Method to test a node's style attribute for text-align and removing it.
245
 
        * @static
246
 
        * @method removeTextAlign
247
 
        */
248
 
        removeTextAlign: function(n) {
249
 
            if (n) {
250
 
                if (n.getAttribute(STYLE).match(EditorBidi.RE_TEXT_ALIGN)) {
251
 
                    n.setAttribute(STYLE, n.getAttribute(STYLE).replace(EditorBidi.RE_TEXT_ALIGN, ''));
252
 
                }
253
 
                if (n.hasAttribute('align')) {
254
 
                    n.removeAttribute('align');
255
 
                }
256
 
            }
257
 
            return n;
258
 
        }
259
 
    });
260
 
    
261
 
    Y.namespace('Plugin');
262
 
    
263
 
    Y.Plugin.EditorBidi = EditorBidi;
264
 
 
265
 
    /**
266
 
     * bidi execCommand override for setting the text direction of a node.
267
 
     * This property is added to the `Y.Plugin.ExecCommands.COMMANDS`
268
 
     * collection.
269
 
     *
270
 
     * @for Plugin.ExecCommand
271
 
     * @property bidi
272
 
     */
273
 
    //TODO -- This should not add this command unless the plugin is added to the instance..
274
 
    Y.Plugin.ExecCommand.COMMANDS.bidi = function(cmd, direction) {
275
 
        var inst = this.getInstance(),
276
 
            sel = new inst.EditorSelection(),
277
 
            ns = this.get(HOST).get(HOST).editorBidi,
278
 
            returnValue, block,
279
 
            selected, selectedBlocks, dir;
280
 
 
281
 
        if (!ns) {
282
 
            Y.error('bidi execCommand is not available without the EditorBiDi plugin.');
283
 
            return;
284
 
        }
285
 
 
286
 
        inst.EditorSelection.filterBlocks();
287
 
 
288
 
        if (sel.isCollapsed) { // No selection
289
 
            block = EditorBidi.blockParent(sel.anchorNode);
290
 
            if (!block) {
291
 
                block = inst.one('body').one(inst.EditorSelection.BLOCKS);
292
 
            }
293
 
            //Remove text-align attribute if it exists
294
 
            block = EditorBidi.removeTextAlign(block);
295
 
            if (!direction) {
296
 
                //If no direction is set, auto-detect the proper setting to make it "toggle"
297
 
                dir = block.getAttribute(DIR);
298
 
                if (!dir || dir == 'ltr') {
299
 
                    direction = 'rtl';
300
 
                } else {
301
 
                    direction = 'ltr';
302
 
                }
303
 
            }
304
 
            block.setAttribute(DIR, direction);
305
 
            if (Y.UA.ie) {
306
 
                var b = block.all('br.yui-cursor');
307
 
                if (b.size() === 1 && block.get('childNodes').size() == 1) {
308
 
                    b.remove();
309
 
                }
310
 
            }
311
 
            returnValue = block;
312
 
        } else { // some text is selected
313
 
            selected = sel.getSelected();
314
 
            selectedBlocks = [];
315
 
            selected.each(function(node) {
316
 
                selectedBlocks.push(EditorBidi.blockParent(node));
317
 
            });
318
 
            selectedBlocks = inst.all(EditorBidi.addParents(selectedBlocks));
319
 
            selectedBlocks.each(function(n) {
320
 
                var d = direction;
321
 
                //Remove text-align attribute if it exists
322
 
                n = EditorBidi.removeTextAlign(n);
323
 
                if (!d) {
324
 
                    dir = n.getAttribute(DIR);
325
 
                    if (!dir || dir == 'ltr') {
326
 
                        d = 'rtl';
327
 
                    } else {
328
 
                        d = 'ltr';
329
 
                    }
330
 
                }
331
 
                n.setAttribute(DIR, d);
332
 
            });
333
 
            returnValue = selectedBlocks;
334
 
        }
335
 
        ns._checkForChange();
336
 
        return returnValue;
337
 
    };
338
 
 
339
 
 
340
 
 
341
 
 
342
 
}, '3.5.0' ,{skinnable:false, requires:['editor-base']});