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