]>
Commit | Line | Data |
---|---|---|
1 | /**\r | |
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.\r | |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license\r | |
4 | */\r | |
5 | \r | |
6 | ( function() {\r | |
7 | var guardElements = { table: 1, ul: 1, ol: 1, blockquote: 1, div: 1 },\r | |
8 | directSelectionGuardElements = {},\r | |
9 | // All guard elements which can have a direction applied on them.\r | |
10 | allGuardElements = {};\r | |
11 | CKEDITOR.tools.extend( directSelectionGuardElements, guardElements, { tr: 1, p: 1, div: 1, li: 1 } );\r | |
12 | CKEDITOR.tools.extend( allGuardElements, directSelectionGuardElements, { td: 1 } );\r | |
13 | \r | |
14 | function setToolbarStates( editor, path ) {\r | |
15 | var useComputedState = editor.config.useComputedState,\r | |
16 | selectedElement;\r | |
17 | \r | |
18 | useComputedState = useComputedState === undefined || useComputedState;\r | |
19 | \r | |
20 | // We can use computedState provided by the browser or traverse parents manually.\r | |
21 | if ( !useComputedState )\r | |
22 | selectedElement = getElementForDirection( path.lastElement, editor.editable() );\r | |
23 | \r | |
24 | selectedElement = selectedElement || path.block || path.blockLimit;\r | |
25 | \r | |
26 | // If we're having BODY here, user probably done CTRL+A, let's try to get the enclosed node, if any.\r | |
27 | if ( selectedElement.equals( editor.editable() ) ) {\r | |
28 | var enclosedNode = editor.getSelection().getRanges()[ 0 ].getEnclosedNode();\r | |
29 | enclosedNode && enclosedNode.type == CKEDITOR.NODE_ELEMENT && ( selectedElement = enclosedNode );\r | |
30 | }\r | |
31 | \r | |
32 | if ( !selectedElement )\r | |
33 | return;\r | |
34 | \r | |
35 | var selectionDir = useComputedState ? selectedElement.getComputedStyle( 'direction' ) : selectedElement.getStyle( 'direction' ) || selectedElement.getAttribute( 'dir' );\r | |
36 | \r | |
37 | editor.getCommand( 'bidirtl' ).setState( selectionDir == 'rtl' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );\r | |
38 | editor.getCommand( 'bidiltr' ).setState( selectionDir == 'ltr' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );\r | |
39 | }\r | |
40 | \r | |
41 | function handleMixedDirContent( editor, path ) {\r | |
42 | var directionNode = path.block || path.blockLimit || editor.editable();\r | |
43 | var pathDir = directionNode.getDirection( 1 );\r | |
44 | if ( pathDir != ( editor._.selDir || editor.lang.dir ) ) {\r | |
45 | editor._.selDir = pathDir;\r | |
46 | editor.fire( 'contentDirChanged', pathDir );\r | |
47 | }\r | |
48 | }\r | |
49 | \r | |
50 | // Returns element with possibility of applying the direction.\r | |
51 | // @param node\r | |
52 | function getElementForDirection( node, root ) {\r | |
53 | while ( node && !( node.getName() in allGuardElements || node.equals( root ) ) ) {\r | |
54 | var parent = node.getParent();\r | |
55 | if ( !parent )\r | |
56 | break;\r | |
57 | \r | |
58 | node = parent;\r | |
59 | }\r | |
60 | \r | |
61 | return node;\r | |
62 | }\r | |
63 | \r | |
64 | function switchDir( element, dir, editor, database ) {\r | |
65 | if ( element.isReadOnly() || element.equals( editor.editable() ) )\r | |
66 | return;\r | |
67 | \r | |
68 | // Mark this element as processed by switchDir.\r | |
69 | CKEDITOR.dom.element.setMarker( database, element, 'bidi_processed', 1 );\r | |
70 | \r | |
71 | // Check whether one of the ancestors has already been styled.\r | |
72 | var parent = element,\r | |
73 | editable = editor.editable();\r | |
74 | while ( ( parent = parent.getParent() ) && !parent.equals( editable ) ) {\r | |
75 | if ( parent.getCustomData( 'bidi_processed' ) ) {\r | |
76 | // Ancestor style must dominate.\r | |
77 | element.removeStyle( 'direction' );\r | |
78 | element.removeAttribute( 'dir' );\r | |
79 | return;\r | |
80 | }\r | |
81 | }\r | |
82 | \r | |
83 | var useComputedState = ( 'useComputedState' in editor.config ) ? editor.config.useComputedState : 1;\r | |
84 | \r | |
85 | var elementDir = useComputedState ? element.getComputedStyle( 'direction' ) : element.getStyle( 'direction' ) || element.hasAttribute( 'dir' );\r | |
86 | \r | |
87 | // Stop if direction is same as present.\r | |
88 | if ( elementDir == dir )\r | |
89 | return;\r | |
90 | \r | |
91 | // Clear direction on this element.\r | |
92 | element.removeStyle( 'direction' );\r | |
93 | \r | |
94 | // Do the second check when computed state is ON, to check\r | |
95 | // if we need to apply explicit direction on this element.\r | |
96 | if ( useComputedState ) {\r | |
97 | element.removeAttribute( 'dir' );\r | |
98 | if ( dir != element.getComputedStyle( 'direction' ) ) {\r | |
99 | element.setAttribute( 'dir', dir );\r | |
100 | }\r | |
101 | } else {\r | |
102 | // Set new direction for this element.\r | |
103 | element.setAttribute( 'dir', dir );\r | |
104 | }\r | |
105 | \r | |
106 | editor.forceNextSelectionCheck();\r | |
107 | \r | |
108 | return;\r | |
109 | }\r | |
110 | \r | |
111 | function getFullySelected( range, elements, enterMode ) {\r | |
112 | var ancestor = range.getCommonAncestor( false, true );\r | |
113 | \r | |
114 | range = range.clone();\r | |
115 | range.enlarge( enterMode == CKEDITOR.ENTER_BR ? CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS : CKEDITOR.ENLARGE_BLOCK_CONTENTS );\r | |
116 | \r | |
117 | if ( range.checkBoundaryOfElement( ancestor, CKEDITOR.START ) && range.checkBoundaryOfElement( ancestor, CKEDITOR.END ) ) {\r | |
118 | var parent;\r | |
119 | while ( ancestor && ancestor.type == CKEDITOR.NODE_ELEMENT && ( parent = ancestor.getParent() ) && parent.getChildCount() == 1 && !( ancestor.getName() in elements ) )\r | |
120 | ancestor = parent;\r | |
121 | \r | |
122 | return ancestor.type == CKEDITOR.NODE_ELEMENT && ( ancestor.getName() in elements ) && ancestor;\r | |
123 | }\r | |
124 | }\r | |
125 | \r | |
126 | function bidiCommand( dir ) {\r | |
127 | return {\r | |
128 | // It applies to a "block-like" context.\r | |
129 | context: 'p',\r | |
130 | allowedContent: {\r | |
131 | 'h1 h2 h3 h4 h5 h6 table ul ol blockquote div tr p div li td': {\r | |
132 | propertiesOnly: true,\r | |
133 | attributes: 'dir'\r | |
134 | }\r | |
135 | },\r | |
136 | requiredContent: 'p[dir]',\r | |
137 | refresh: function( editor, path ) {\r | |
138 | setToolbarStates( editor, path );\r | |
139 | handleMixedDirContent( editor, path );\r | |
140 | },\r | |
141 | exec: function( editor ) {\r | |
142 | var selection = editor.getSelection(),\r | |
143 | enterMode = editor.config.enterMode,\r | |
144 | ranges = selection.getRanges();\r | |
145 | \r | |
146 | if ( ranges && ranges.length ) {\r | |
147 | var database = {};\r | |
148 | \r | |
149 | // Creates bookmarks for selection, as we may split some blocks.\r | |
150 | var bookmarks = selection.createBookmarks();\r | |
151 | \r | |
152 | var rangeIterator = ranges.createIterator(),\r | |
153 | range,\r | |
154 | i = 0;\r | |
155 | \r | |
156 | while ( ( range = rangeIterator.getNextRange( 1 ) ) ) {\r | |
157 | // Apply do directly selected elements from guardElements.\r | |
158 | var selectedElement = range.getEnclosedNode();\r | |
159 | \r | |
160 | // If this is not our element of interest, apply to fully selected elements from guardElements.\r | |
161 | if ( !selectedElement || selectedElement && !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements ) )\r | |
162 | selectedElement = getFullySelected( range, guardElements, enterMode );\r | |
163 | \r | |
164 | selectedElement && switchDir( selectedElement, dir, editor, database );\r | |
165 | \r | |
166 | var iterator, block;\r | |
167 | \r | |
168 | // Walker searching for guardElements.\r | |
169 | var walker = new CKEDITOR.dom.walker( range );\r | |
170 | \r | |
171 | var start = bookmarks[ i ].startNode,\r | |
172 | end = bookmarks[ i++ ].endNode;\r | |
173 | \r | |
174 | walker.evaluator = function( node ) {\r | |
175 | var enterTagName = ( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );\r | |
176 | \r | |
177 | function isNodeElement( node ) {\r | |
178 | return node ? ( node.type == CKEDITOR.NODE_ELEMENT ) : false;\r | |
179 | }\r | |
180 | \r | |
181 | function isGuard( node ) {\r | |
182 | return node.getName() in guardElements;\r | |
183 | }\r | |
184 | \r | |
185 | return !!( isNodeElement( node ) && isGuard( node ) && !( node.is( enterTagName ) && isNodeElement( node.getParent() ) && node.getParent().is( 'blockquote' ) ) &&\r | |
186 | // Element must be fully included in the range as well. (#6485).\r | |
187 | node.getPosition( start ) & CKEDITOR.POSITION_FOLLOWING &&\r | |
188 | ( ( node.getPosition( end ) & CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_CONTAINS ) == CKEDITOR.POSITION_PRECEDING ) );\r | |
189 | };\r | |
190 | \r | |
191 | while ( ( block = walker.next() ) )\r | |
192 | switchDir( block, dir, editor, database );\r | |
193 | \r | |
194 | iterator = range.createIterator();\r | |
195 | iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;\r | |
196 | \r | |
197 | while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) )\r | |
198 | switchDir( block, dir, editor, database );\r | |
199 | }\r | |
200 | \r | |
201 | CKEDITOR.dom.element.clearAllMarkers( database );\r | |
202 | \r | |
203 | editor.forceNextSelectionCheck();\r | |
204 | // Restore selection position.\r | |
205 | selection.selectBookmarks( bookmarks );\r | |
206 | \r | |
207 | editor.focus();\r | |
208 | }\r | |
209 | }\r | |
210 | };\r | |
211 | }\r | |
212 | \r | |
213 | CKEDITOR.plugins.add( 'bidi', {\r | |
214 | // jscs:disable maximumLineLength\r | |
215 | lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%\r | |
216 | // jscs:enable maximumLineLength\r | |
217 | icons: 'bidiltr,bidirtl', // %REMOVE_LINE_CORE%\r | |
218 | hidpi: true, // %REMOVE_LINE_CORE%\r | |
219 | init: function( editor ) {\r | |
220 | if ( editor.blockless )\r | |
221 | return;\r | |
222 | \r | |
223 | // All buttons use the same code to register. So, to avoid\r | |
224 | // duplications, let's use this tool function.\r | |
225 | function addButtonCommand( buttonName, buttonLabel, commandName, commandDef, order ) {\r | |
226 | editor.addCommand( commandName, new CKEDITOR.command( editor, commandDef ) );\r | |
227 | \r | |
228 | if ( editor.ui.addButton ) {\r | |
229 | editor.ui.addButton( buttonName, {\r | |
230 | label: buttonLabel,\r | |
231 | command: commandName,\r | |
232 | toolbar: 'bidi,' + order\r | |
233 | } );\r | |
234 | }\r | |
235 | }\r | |
236 | \r | |
237 | var lang = editor.lang.bidi;\r | |
238 | \r | |
239 | addButtonCommand( 'BidiLtr', lang.ltr, 'bidiltr', bidiCommand( 'ltr' ), 10 );\r | |
240 | addButtonCommand( 'BidiRtl', lang.rtl, 'bidirtl', bidiCommand( 'rtl' ), 20 );\r | |
241 | \r | |
242 | editor.on( 'contentDom', function() {\r | |
243 | editor.document.on( 'dirChanged', function( evt ) {\r | |
244 | editor.fire( 'dirChanged', {\r | |
245 | node: evt.data,\r | |
246 | dir: evt.data.getDirection( 1 )\r | |
247 | } );\r | |
248 | } );\r | |
249 | } );\r | |
250 | \r | |
251 | // Indicate that the current selection is in different direction than the UI.\r | |
252 | editor.on( 'contentDirChanged', function( evt ) {\r | |
253 | var func = ( editor.lang.dir != evt.data ? 'add' : 'remove' ) + 'Class';\r | |
254 | var toolbar = editor.ui.space( editor.config.toolbarLocation );\r | |
255 | if ( toolbar )\r | |
256 | toolbar[ func ]( 'cke_mixed_dir_content' );\r | |
257 | } );\r | |
258 | }\r | |
259 | } );\r | |
260 | \r | |
261 | // If the element direction changed, we need to switch the margins of\r | |
262 | // the element and all its children, so it will get really reflected\r | |
263 | // like a mirror. (#5910)\r | |
264 | function isOffline( el ) {\r | |
265 | var html = el.getDocument().getBody().getParent();\r | |
266 | while ( el ) {\r | |
267 | if ( el.equals( html ) )\r | |
268 | return false;\r | |
269 | el = el.getParent();\r | |
270 | }\r | |
271 | return true;\r | |
272 | }\r | |
273 | \r | |
274 | function dirChangeNotifier( org ) {\r | |
275 | var isAttribute = org == elementProto.setAttribute,\r | |
276 | isRemoveAttribute = org == elementProto.removeAttribute,\r | |
277 | dirStyleRegexp = /\bdirection\s*:\s*(.*?)\s*(:?$|;)/;\r | |
278 | \r | |
279 | return function( name, val ) {\r | |
280 | if ( !this.isReadOnly() ) {\r | |
281 | var orgDir;\r | |
282 | if ( ( name == ( isAttribute || isRemoveAttribute ? 'dir' : 'direction' ) || name == 'style' && ( isRemoveAttribute || dirStyleRegexp.test( val ) ) ) && !isOffline( this ) ) {\r | |
283 | orgDir = this.getDirection( 1 );\r | |
284 | var retval = org.apply( this, arguments );\r | |
285 | if ( orgDir != this.getDirection( 1 ) ) {\r | |
286 | this.getDocument().fire( 'dirChanged', this );\r | |
287 | return retval;\r | |
288 | }\r | |
289 | }\r | |
290 | }\r | |
291 | \r | |
292 | return org.apply( this, arguments );\r | |
293 | };\r | |
294 | }\r | |
295 | \r | |
296 | var elementProto = CKEDITOR.dom.element.prototype,\r | |
297 | methods = [ 'setStyle', 'removeStyle', 'setAttribute', 'removeAttribute' ];\r | |
298 | for ( var i = 0; i < methods.length; i++ )\r | |
299 | elementProto[ methods[ i ] ] = CKEDITOR.tools.override( elementProto[ methods[ i ] ], dirChangeNotifier );\r | |
300 | } )();\r | |
301 | \r | |
302 | /**\r | |
303 | * Fired when the language direction of an element is changed.\r | |
304 | *\r | |
305 | * @event dirChanged\r | |
306 | * @member CKEDITOR.editor\r | |
307 | * @param {CKEDITOR.editor} editor This editor instance.\r | |
308 | * @param data\r | |
309 | * @param {CKEDITOR.dom.node} data.node The element that is being changed.\r | |
310 | * @param {String} data.dir The new direction.\r | |
311 | */\r | |
312 | \r | |
313 | /**\r | |
314 | * Fired when the language direction in the specific cursor position is changed\r | |
315 | *\r | |
316 | * @event contentDirChanged\r | |
317 | * @member CKEDITOR.editor\r | |
318 | * @param {CKEDITOR.editor} editor This editor instance.\r | |
319 | * @param {String} data The direction in the current position.\r | |
320 | */\r |