JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
6c5d8a6f28ca1ac61c69ddd178ba67d58e3fd42c
[ckeditor.git] / _source / plugins / bidi / plugin.js
1 /*\r
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 (function()\r
7 {\r
8         var guardElements = { table:1, ul:1, ol:1, blockquote:1, div:1 };\r
9         var directSelectionGuardElements = {};\r
10         CKEDITOR.tools.extend( directSelectionGuardElements, guardElements, { tr:1, p:1, div:1, li:1 } );\r
11 \r
12         function onSelectionChange( evt )\r
13         {\r
14                 var editor = evt.editor,\r
15                         path = evt.data.path;\r
16                 var useComputedState = editor.config.useComputedState,\r
17                         selectedElement;\r
18 \r
19                 useComputedState = useComputedState === undefined || useComputedState;\r
20 \r
21                 if ( useComputedState )\r
22                 {\r
23                         var selection = editor.getSelection(),\r
24                                 ranges = selection.getRanges();\r
25 \r
26                         selectedElement = ranges && ranges[ 0 ].getEnclosedNode();\r
27 \r
28                         // If this is not our element of interest, apply to fully selected elements from guardElements.\r
29                         if ( !selectedElement || selectedElement\r
30                                         && !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements )\r
31                                 )\r
32                                 selectedElement = getFullySelected( selection, guardElements );\r
33                 }\r
34 \r
35                 selectedElement = selectedElement || path.block || path.blockLimit;\r
36 \r
37                 if ( !selectedElement || selectedElement.getName() == 'body' )\r
38                         return;\r
39 \r
40                 var selectionDir = useComputedState ?\r
41                         selectedElement.getComputedStyle( 'direction' ) :\r
42                         selectedElement.getStyle( 'direction' ) || selectedElement.getAttribute( 'dir' );\r
43 \r
44                 editor.getCommand( 'bidirtl' ).setState( selectionDir == 'rtl' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );\r
45                 editor.getCommand( 'bidiltr' ).setState( selectionDir == 'ltr' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );\r
46 \r
47                 var chromeRoot = editor.container.getChild( 1 );\r
48 \r
49                 if ( selectionDir != editor.lang.dir )\r
50                         chromeRoot.addClass( 'cke_mixed_dir_content' );\r
51                 else\r
52                         chromeRoot.removeClass( 'cke_mixed_dir_content' );\r
53         }\r
54 \r
55         function switchDir( element, dir, editor )\r
56         {\r
57                 var dirBefore = element.getComputedStyle( 'direction' ),\r
58                         currentDir = element.getStyle( 'direction' ) || element.getAttribute( 'dir' ) || '';\r
59 \r
60                 element.removeStyle( 'direction' );\r
61 \r
62                 if ( currentDir.toLowerCase() == dir )\r
63                         element.removeAttribute( 'dir' );\r
64                 else\r
65                         element.setAttribute( 'dir', dir );\r
66 \r
67                 // If the element direction changed, we need to switch the margins of\r
68                 // the element and all its children, so it will get really reflected\r
69                 // like a mirror. (#5910)\r
70                 var dirAfter = element.getComputedStyle( 'direction' );\r
71                 if ( dirAfter != dirBefore )\r
72                 {\r
73                         var range = new CKEDITOR.dom.range( element.getDocument() );\r
74                         range.setStartBefore( element );\r
75                         range.setEndAfter( element );\r
76 \r
77                         var walker = new CKEDITOR.dom.walker( range );\r
78 \r
79                         var node;\r
80                         while ( ( node = walker.next() ) )\r
81                         {\r
82                                 if ( node.type == CKEDITOR.NODE_ELEMENT )\r
83                                 {\r
84                                         // A child with dir defined is to be ignored.\r
85                                         if ( !node.equals( element ) && node.hasAttribute( 'dir' ) )\r
86                                         {\r
87                                                 range.setStartAfter( node );\r
88                                                 walker = new CKEDITOR.dom.walker( range );\r
89                                                 continue;\r
90                                         }\r
91 \r
92                                         // Switch the margins.\r
93                                         var marginLeft = node.getStyle( 'margin-right' ),\r
94                                                 marginRight = node.getStyle( 'margin-left' );\r
95 \r
96                                         marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );\r
97                                         marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );\r
98                                 }\r
99                         }\r
100                 }\r
101 \r
102                 editor.forceNextSelectionCheck();\r
103         }\r
104 \r
105         function getFullySelected( selection, elements )\r
106         {\r
107                 var selectedElement = selection.getCommonAncestor();\r
108                 while( selectedElement.type == CKEDITOR.NODE_ELEMENT\r
109                                 && !( selectedElement.getName() in elements )\r
110                                 && selectedElement.getParent().getChildCount() == 1\r
111                         )\r
112                         selectedElement = selectedElement.getParent();\r
113 \r
114                 return selectedElement.type == CKEDITOR.NODE_ELEMENT\r
115                         && ( selectedElement.getName() in elements )\r
116                         && selectedElement;\r
117         }\r
118 \r
119         function bidiCommand( dir )\r
120         {\r
121                 return function( editor )\r
122                 {\r
123                         var selection = editor.getSelection(),\r
124                                 enterMode = editor.config.enterMode,\r
125                                 ranges = selection.getRanges();\r
126 \r
127                         if ( ranges && ranges.length )\r
128                         {\r
129                                 // Apply do directly selected elements from guardElements.\r
130                                 var selectedElement = ranges[ 0 ].getEnclosedNode();\r
131 \r
132                                 // If this is not our element of interest, apply to fully selected elements from guardElements.\r
133                                 if ( !selectedElement || selectedElement\r
134                                                 && !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements )\r
135                                         )\r
136                                         selectedElement = getFullySelected( selection, guardElements );\r
137 \r
138                                 if ( selectedElement )\r
139                                 {\r
140                                         if ( !selectedElement.isReadOnly() )\r
141                                                 switchDir( selectedElement, dir, editor );\r
142                                 }\r
143                                 else\r
144                                 {\r
145                                         // Creates bookmarks for selection, as we may split some blocks.\r
146                                         var bookmarks = selection.createBookmarks();\r
147 \r
148                                         var iterator,\r
149                                                 block;\r
150 \r
151                                         for ( var i = ranges.length - 1 ; i >= 0 ; i-- )\r
152                                         {\r
153                                                 // Array of elements processed as guardElements.\r
154                                                 var processedElements = [];\r
155                                                 // Walker searching for guardElements.\r
156                                                 var walker = new CKEDITOR.dom.walker( ranges[ i ] );\r
157                                                 walker.evaluator = function( node ){\r
158                                                         return node.type == CKEDITOR.NODE_ELEMENT\r
159                                                                 && node.getName() in guardElements\r
160                                                                 && !( node.getName() == ( enterMode == CKEDITOR.ENTER_P ) ? 'p' : 'div'\r
161                                                                         && node.getParent().type == CKEDITOR.NODE_ELEMENT\r
162                                                                         && node.getParent().getName() == 'blockquote'\r
163                                                                 );\r
164                                                 };\r
165 \r
166                                                 while ( ( block = walker.next() ) )\r
167                                                 {\r
168                                                         switchDir( block, dir, editor );\r
169                                                         processedElements.push( block );\r
170                                                 }\r
171 \r
172                                                 iterator = ranges[ i ].createIterator();\r
173                                                 iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;\r
174 \r
175                                                 while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) )\r
176                                                 {\r
177                                                         if ( block.isReadOnly() )\r
178                                                                 continue;\r
179 \r
180                                                         var _break = 0;\r
181 \r
182                                                         // Check if block have been already processed by the walker above.\r
183                                                         for ( var ii = 0; ii < processedElements.length; ii++ )\r
184                                                         {\r
185                                                                 var parent = block.getParent();\r
186 \r
187                                                                 while( parent && parent.getName() != 'body' )\r
188                                                                 {\r
189                                                                         if ( ( parent.$.isSameNode && parent.$.isSameNode( processedElements[ ii ].$ ) )\r
190                                                                                         || parent.$ == processedElements[ ii ].$ )\r
191                                                                         {\r
192                                                                                 _break = 1;\r
193                                                                                 break;\r
194                                                                         }\r
195                                                                         parent = parent.getParent();\r
196                                                                 }\r
197 \r
198                                                                 if ( _break )\r
199                                                                         break;\r
200                                                         }\r
201 \r
202                                                         if ( !_break )\r
203                                                         {\r
204                                                                 switchDir( block, dir, editor );\r
205                                                         }\r
206                                                 }\r
207                                         }\r
208 \r
209                                         editor.forceNextSelectionCheck();\r
210                                         // Restore selection position.\r
211                                         selection.selectBookmarks( bookmarks );\r
212                                 }\r
213 \r
214                                 editor.focus();\r
215                         }\r
216                 };\r
217         }\r
218 \r
219         CKEDITOR.plugins.add( 'bidi',\r
220         {\r
221                 requires : [ 'styles', 'button' ],\r
222 \r
223                 init : function( editor )\r
224                 {\r
225                         // All buttons use the same code to register. So, to avoid\r
226                         // duplications, let's use this tool function.\r
227                         var addButtonCommand = function( buttonName, buttonLabel, commandName, commandExec )\r
228                         {\r
229                                 editor.addCommand( commandName, new CKEDITOR.command( editor, { exec : commandExec }) );\r
230 \r
231                                 editor.ui.addButton( buttonName,\r
232                                         {\r
233                                                 label : buttonLabel,\r
234                                                 command : commandName\r
235                                         });\r
236                         };\r
237 \r
238                         var lang = editor.lang.bidi;\r
239 \r
240                         addButtonCommand( 'BidiLtr', lang.ltr, 'bidiltr', bidiCommand( 'ltr' ) );\r
241                         addButtonCommand( 'BidiRtl', lang.rtl, 'bidirtl', bidiCommand( 'rtl' ) );\r
242 \r
243                         editor.on( 'selectionChange', onSelectionChange );\r
244                 }\r
245         });\r
246 \r
247 })();\r