2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
8 var guardElements
= { table
:1, ul
:1, ol
:1, blockquote
:1, div
:1 },
9 directSelectionGuardElements
= {},
10 // All guard elements which can have a direction applied on them.
11 allGuardElements
= {};
12 CKEDITOR
.tools
.extend( directSelectionGuardElements
, guardElements
, { tr
:1, p
:1, div
:1, li
:1 } );
13 CKEDITOR
.tools
.extend( allGuardElements
, directSelectionGuardElements
, { td
:1 } );
15 function onSelectionChange( e
)
17 setToolbarStates( e
);
18 handleMixedDirContent( e
);
21 function setToolbarStates( evt
)
23 var editor
= evt
.editor
,
26 if ( editor
.readOnly
)
29 var useComputedState
= editor
.config
.useComputedState
,
32 useComputedState
= useComputedState
=== undefined || useComputedState
;
34 // We can use computedState provided by the browser or traverse parents manually.
35 if ( !useComputedState
)
36 selectedElement
= getElementForDirection( path
.lastElement
);
38 selectedElement
= selectedElement
|| path
.block
|| path
.blockLimit
;
40 // If we're having BODY here, user probably done CTRL+A, let's try to get the enclosed node, if any.
41 if ( selectedElement
.is( 'body' ) )
43 var enclosedNode
= editor
.getSelection().getRanges()[ 0 ].getEnclosedNode();
44 enclosedNode
&& enclosedNode
.type
== CKEDITOR
.NODE_ELEMENT
&& ( selectedElement
= enclosedNode
);
47 if ( !selectedElement
)
50 var selectionDir
= useComputedState
?
51 selectedElement
.getComputedStyle( 'direction' ) :
52 selectedElement
.getStyle( 'direction' ) || selectedElement
.getAttribute( 'dir' );
54 editor
.getCommand( 'bidirtl' ).setState( selectionDir
== 'rtl' ? CKEDITOR
.TRISTATE_ON
: CKEDITOR
.TRISTATE_OFF
);
55 editor
.getCommand( 'bidiltr' ).setState( selectionDir
== 'ltr' ? CKEDITOR
.TRISTATE_ON
: CKEDITOR
.TRISTATE_OFF
);
58 function handleMixedDirContent( evt
)
60 var editor
= evt
.editor
,
61 directionNode
= evt
.data
.path
.block
|| evt
.data
.path
.blockLimit
;
63 editor
.fire( 'contentDirChanged', directionNode
? directionNode
.getComputedStyle( 'direction' ) : editor
.lang
.dir
);
67 * Returns element with possibility of applying the direction.
70 function getElementForDirection( node
)
72 while ( node
&& !( node
.getName() in allGuardElements
|| node
.is( 'body' ) ) )
74 var parent
= node
.getParent();
84 function switchDir( element
, dir
, editor
, database
)
86 if ( element
.isReadOnly() )
89 // Mark this element as processed by switchDir.
90 CKEDITOR
.dom
.element
.setMarker( database
, element
, 'bidi_processed', 1 );
92 // Check whether one of the ancestors has already been styled.
94 while ( ( parent
= parent
.getParent() ) && !parent
.is( 'body' ) )
96 if ( parent
.getCustomData( 'bidi_processed' ) )
98 // Ancestor style must dominate.
99 element
.removeStyle( 'direction' );
100 element
.removeAttribute( 'dir' );
105 var useComputedState
= ( 'useComputedState' in editor
.config
) ? editor
.config
.useComputedState
: 1;
107 var elementDir
= useComputedState
? element
.getComputedStyle( 'direction' )
108 : element
.getStyle( 'direction' ) || element
.hasAttribute( 'dir' );
110 // Stop if direction is same as present.
111 if ( elementDir
== dir
)
114 // Clear direction on this element.
115 element
.removeStyle( 'direction' );
117 // Do the second check when computed state is ON, to check
118 // if we need to apply explicit direction on this element.
119 if ( useComputedState
)
121 element
.removeAttribute( 'dir' );
122 if ( dir
!= element
.getComputedStyle( 'direction' ) )
123 element
.setAttribute( 'dir', dir
);
126 // Set new direction for this element.
127 element
.setAttribute( 'dir', dir
);
129 editor
.forceNextSelectionCheck();
134 function getFullySelected( range
, elements
, enterMode
)
136 var ancestor
= range
.getCommonAncestor( false, true );
138 range
= range
.clone();
139 range
.enlarge( enterMode
== CKEDITOR
.ENTER_BR
?
140 CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS
141 : CKEDITOR
.ENLARGE_BLOCK_CONTENTS
);
143 if ( range
.checkBoundaryOfElement( ancestor
, CKEDITOR
.START
)
144 && range
.checkBoundaryOfElement( ancestor
, CKEDITOR
.END
) )
147 while ( ancestor
&& ancestor
.type
== CKEDITOR
.NODE_ELEMENT
148 && ( parent
= ancestor
.getParent() )
149 && parent
.getChildCount() == 1
150 && !( ancestor
.getName() in elements
) )
153 return ancestor
.type
== CKEDITOR
.NODE_ELEMENT
154 && ( ancestor
.getName() in elements
)
159 function bidiCommand( dir
)
161 return function( editor
)
163 var selection
= editor
.getSelection(),
164 enterMode
= editor
.config
.enterMode
,
165 ranges
= selection
.getRanges();
167 if ( ranges
&& ranges
.length
)
171 // Creates bookmarks for selection, as we may split some blocks.
172 var bookmarks
= selection
.createBookmarks();
174 var rangeIterator
= ranges
.createIterator(),
178 while ( ( range
= rangeIterator
.getNextRange( 1 ) ) )
180 // Apply do directly selected elements from guardElements.
181 var selectedElement
= range
.getEnclosedNode();
183 // If this is not our element of interest, apply to fully selected elements from guardElements.
184 if ( !selectedElement
|| selectedElement
185 && !( selectedElement
.type
== CKEDITOR
.NODE_ELEMENT
&& selectedElement
.getName() in directSelectionGuardElements
)
187 selectedElement
= getFullySelected( range
, guardElements
, enterMode
);
189 selectedElement
&& switchDir( selectedElement
, dir
, editor
, database
);
194 // Walker searching for guardElements.
195 var walker
= new CKEDITOR
.dom
.walker( range
);
197 var start
= bookmarks
[ i
].startNode
,
198 end
= bookmarks
[ i
++ ].endNode
;
200 walker
.evaluator = function( node
)
202 return !! ( node
.type
== CKEDITOR
.NODE_ELEMENT
203 && node
.getName() in guardElements
204 && !( node
.getName() == ( enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' )
205 && node
.getParent().type
== CKEDITOR
.NODE_ELEMENT
206 && node
.getParent().getName() == 'blockquote' )
207 // Element must be fully included in the range as well. (#6485).
208 && node
.getPosition( start
) & CKEDITOR
.POSITION_FOLLOWING
209 && ( ( node
.getPosition( end
) & CKEDITOR
.POSITION_PRECEDING
+ CKEDITOR
.POSITION_CONTAINS
) == CKEDITOR
.POSITION_PRECEDING
) );
212 while ( ( block
= walker
.next() ) )
213 switchDir( block
, dir
, editor
, database
);
215 iterator
= range
.createIterator();
216 iterator
.enlargeBr
= enterMode
!= CKEDITOR
.ENTER_BR
;
218 while ( ( block
= iterator
.getNextParagraph( enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' ) ) )
219 switchDir( block
, dir
, editor
, database
);
222 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
224 editor
.forceNextSelectionCheck();
225 // Restore selection position.
226 selection
.selectBookmarks( bookmarks
);
233 CKEDITOR
.plugins
.add( 'bidi',
235 requires
: [ 'styles', 'button' ],
237 init : function( editor
)
239 // All buttons use the same code to register. So, to avoid
240 // duplications, let's use this tool function.
241 var addButtonCommand = function( buttonName
, buttonLabel
, commandName
, commandExec
)
243 editor
.addCommand( commandName
, new CKEDITOR
.command( editor
, { exec
: commandExec
}) );
245 editor
.ui
.addButton( buttonName
,
248 command
: commandName
252 var lang
= editor
.lang
.bidi
;
254 addButtonCommand( 'BidiLtr', lang
.ltr
, 'bidiltr', bidiCommand( 'ltr' ) );
255 addButtonCommand( 'BidiRtl', lang
.rtl
, 'bidirtl', bidiCommand( 'rtl' ) );
257 editor
.on( 'selectionChange', onSelectionChange
);
258 editor
.on( 'contentDom', function()
260 editor
.document
.on( 'dirChanged', function( evt
)
262 editor
.fire( 'dirChanged',
265 dir
: evt
.data
.getDirection( 1 )
272 // If the element direction changed, we need to switch the margins of
273 // the element and all its children, so it will get really reflected
274 // like a mirror. (#5910)
275 function isOffline( el
)
277 var html
= el
.getDocument().getBody().getParent();
280 if ( el
.equals( html
) )
286 function dirChangeNotifier( org
)
288 var isAttribute
= org
== elementProto
.setAttribute
,
289 isRemoveAttribute
= org
== elementProto
.removeAttribute
,
290 dirStyleRegexp
= /\bdirection\s*:\s*(.*?)\s*(:?$|;)/;
292 return function( name
, val
)
294 if ( !this.getDocument().equals( CKEDITOR
.document
) )
297 if ( ( name
== ( isAttribute
|| isRemoveAttribute
? 'dir' : 'direction' ) ||
298 name
== 'style' && ( isRemoveAttribute
|| dirStyleRegexp
.test( val
) ) ) && !isOffline( this ) )
300 orgDir
= this.getDirection( 1 );
301 var retval
= org
.apply( this, arguments
);
302 if ( orgDir
!= this.getDirection( 1 ) )
304 this.getDocument().fire( 'dirChanged', this );
310 return org
.apply( this, arguments
);
314 var elementProto
= CKEDITOR
.dom
.element
.prototype,
315 methods
= [ 'setStyle', 'removeStyle', 'setAttribute', 'removeAttribute' ];
316 for ( var i
= 0; i
< methods
.length
; i
++ )
317 elementProto
[ methods
[ i
] ] = CKEDITOR
.tools
.override( elementProto
[ methods
[ i
] ], dirChangeNotifier
);
321 * Fired when the language direction of an element is changed
322 * @name CKEDITOR.editor#dirChanged
324 * @param {CKEDITOR.editor} editor This editor instance.
325 * @param {Object} eventData.node The element that is being changed.
326 * @param {String} eventData.dir The new direction.
330 * Fired when the language direction in the specific cursor position is changed
331 * @name CKEDITOR.editor#contentDirChanged
333 * @param {String} eventData The direction in the current position.