Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / editor-bidi / editor-bidi-debug.js
blob395c20d61623d91754d05a4d5861e3ee7a67257c
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) {
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      */
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';
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,
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         },
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         },
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);
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         },
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;
182             for (i = 0; i < nodeArray.length; i += 1) {
183                 nodeArray[i].setData(EditorBidi._NODE_SELECTED, true);
184             }
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');
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             }   
212             for (i = 0; i < nodeArray.length; i += 1) {
213                 nodeArray[i].clearData(EditorBidi._NODE_SELECTED);
214             }
216             return nodeArray;
217         },
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;
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;
281         if (!ns) {
282             Y.error('bidi execCommand is not available without the EditorBiDi plugin.');
283             return;
284         }
286         inst.EditorSelection.filterBlocks();
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     };
342 }, '3.5.0' ,{skinnable:false, requires:['editor-base']});