2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
7 * @file Increse and decrease indent commands.
12 var listNodeNames
= { ol
: 1, ul
: 1 };
14 function setState( editor
, state
)
16 editor
.getCommand( this.name
).setState( state
);
19 function onSelectionChange( evt
)
21 var elements
= evt
.data
.path
.elements
,
25 for ( var i
= 0 ; i
< elements
.length
; i
++ )
27 if ( elements
[i
].getName() == 'li' )
29 listItem
= elements
[i
];
32 if ( listNodeNames
[ elements
[i
].getName() ] )
34 listNode
= elements
[i
];
41 if ( this.name
== 'outdent' )
42 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_OFF
);
45 while ( listItem
&& ( listItem
= listItem
.getPrevious( CKEDITOR
.dom
.walker
.whitespaces( true ) ) ) )
47 if ( listItem
.getName
&& listItem
.getName() == 'li' )
48 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_OFF
);
50 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_DISABLED
);
54 if ( !this.useIndentClasses
&& this.name
== 'indent' )
55 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_OFF
);
57 var path
= evt
.data
.path
,
58 firstBlock
= path
.block
|| path
.blockLimit
;
60 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_DISABLED
);
62 if ( this.useIndentClasses
)
64 var indentClass
= firstBlock
.$.className
.match( this.classNameRegex
),
68 indentClass
= indentClass
[1];
69 indentStep
= this.indentClassMap
[ indentClass
];
71 if ( ( this.name
== 'outdent' && !indentStep
) ||
72 ( this.name
== 'indent' && indentStep
== editor
.config
.indentClasses
.length
) )
73 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_DISABLED
);
74 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_OFF
);
78 var indent
= parseInt( firstBlock
.getStyle( this.indentCssProperty
), 10 );
79 if ( isNaN( indent
) )
82 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_DISABLED
);
83 return setState
.call( this, editor
, CKEDITOR
.TRISTATE_OFF
);
87 function indentList( editor
, range
, listNode
)
89 // Our starting and ending points of the range might be inside some blocks under a list item...
90 // So before playing with the iterator, we need to expand the block to include the list items.
91 var startContainer
= range
.startContainer
,
92 endContainer
= range
.endContainer
;
93 while ( startContainer
&& !startContainer
.getParent().equals( listNode
) )
94 startContainer
= startContainer
.getParent();
95 while ( endContainer
&& !endContainer
.getParent().equals( listNode
) )
96 endContainer
= endContainer
.getParent();
98 if ( !startContainer
|| !endContainer
)
101 // Now we can iterate over the individual items on the same tree depth.
102 var block
= startContainer
,
107 if ( block
.equals( endContainer
) )
109 itemsToMove
.push( block
);
110 block
= block
.getNext();
112 if ( itemsToMove
.length
< 1 )
115 // Do indent or outdent operations on the array model of the list, not the
116 // list's DOM tree itself. The array model demands that it knows as much as
117 // possible about the surrounding lists, we need to feed it the further
118 // ancestor node that is still a list.
119 var listParents
= listNode
.getParents( true );
120 for ( var i
= 0 ; i
< listParents
.length
; i
++ )
122 if ( listParents
[i
].getName
&& listNodeNames
[ listParents
[i
].getName() ] )
124 listNode
= listParents
[i
];
128 var indentOffset
= this.name
== 'indent' ? 1 : -1,
129 startItem
= itemsToMove
[0],
130 lastItem
= itemsToMove
[ itemsToMove
.length
- 1 ],
133 // Convert the list DOM tree into a one dimensional array.
134 var listArray
= CKEDITOR
.plugins
.list
.listToArray( listNode
, database
);
136 // Apply indenting or outdenting on the array.
137 var baseIndent
= listArray
[ lastItem
.getCustomData( 'listarray_index' ) ].indent
;
138 for ( i
= startItem
.getCustomData( 'listarray_index' ); i
<= lastItem
.getCustomData( 'listarray_index' ); i
++ )
140 listArray
[ i
].indent
+= indentOffset
;
141 // Make sure the newly created sublist get a brand-new element of the same type. (#5372)
142 var listRoot
= listArray
[ i
].parent
;
143 listArray
[ i
].parent
= new CKEDITOR
.dom
.element( listRoot
.getName(), listRoot
.getDocument() );
146 for ( i
= lastItem
.getCustomData( 'listarray_index' ) + 1 ;
147 i
< listArray
.length
&& listArray
[i
].indent
> baseIndent
; i
++ )
148 listArray
[i
].indent
+= indentOffset
;
150 // Convert the array back to a DOM forest (yes we might have a few subtrees now).
151 // And replace the old list with the new forest.
152 var newList
= CKEDITOR
.plugins
.list
.arrayToList( listArray
, database
, null, editor
.config
.enterMode
, 0 );
154 // Avoid nested <li> after outdent even they're visually same,
155 // recording them for later refactoring.(#3982)
156 if ( this.name
== 'outdent' )
159 if ( ( parentLiElement
= listNode
.getParent() ) && parentLiElement
.is( 'li' ) )
161 var children
= newList
.listNode
.getChildren(),
163 count
= children
.count(),
166 for ( i
= count
- 1 ; i
>= 0 ; i
-- )
168 if ( ( child
= children
.getItem( i
) ) && child
.is
&& child
.is( 'li' ) )
169 pendingLis
.push( child
);
175 newList
.listNode
.replace( listNode
);
177 // Move the nested <li> to be appeared after the parent.
178 if ( pendingLis
&& pendingLis
.length
)
180 for ( i
= 0; i
< pendingLis
.length
; i
++ )
182 var li
= pendingLis
[ i
],
185 // Nest preceding <ul>/<ol> inside current <li> if any.
186 while ( ( followingList
= followingList
.getNext() ) &&
188 followingList
.getName() in listNodeNames
)
190 li
.append( followingList
);
193 li
.insertAfter( parentLiElement
);
197 // Clean up the markers.
198 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
201 function indentBlock( editor
, range
)
203 var iterator
= range
.createIterator(),
204 enterMode
= editor
.config
.enterMode
;
205 iterator
.enforceRealBlocks
= true;
206 iterator
.enlargeBr
= enterMode
!= CKEDITOR
.ENTER_BR
;
208 while ( ( block
= iterator
.getNextParagraph() ) )
211 if ( this.useIndentClasses
)
213 // Transform current class name to indent step index.
214 var indentClass
= block
.$.className
.match( this.classNameRegex
),
218 indentClass
= indentClass
[1];
219 indentStep
= this.indentClassMap
[ indentClass
];
222 // Operate on indent step index, transform indent step index back to class
224 if ( this.name
== 'outdent' )
228 indentStep
= Math
.min( indentStep
, editor
.config
.indentClasses
.length
);
229 indentStep
= Math
.max( indentStep
, 0 );
230 var className
= CKEDITOR
.tools
.ltrim( block
.$.className
.replace( this.classNameRegex
, '' ) );
231 if ( indentStep
< 1 )
232 block
.$.className
= className
;
234 block
.$.className
= CKEDITOR
.tools
.ltrim( className
+ ' ' + editor
.config
.indentClasses
[ indentStep
- 1 ] );
238 var currentOffset
= parseInt( block
.getStyle( this.indentCssProperty
), 10 );
239 if ( isNaN( currentOffset
) )
241 currentOffset
+= ( this.name
== 'indent' ? 1 : -1 ) * editor
.config
.indentOffset
;
242 currentOffset
= Math
.max( currentOffset
, 0 );
243 currentOffset
= Math
.ceil( currentOffset
/ editor
.config
.indentOffset
) * editor
.config
.indentOffset
;
244 block
.setStyle( this.indentCssProperty
, currentOffset
? currentOffset
+ editor
.config
.indentUnit
: '' );
245 if ( block
.getAttribute( 'style' ) === '' )
246 block
.removeAttribute( 'style' );
251 function indentCommand( editor
, name
)
254 this.useIndentClasses
= editor
.config
.indentClasses
&& editor
.config
.indentClasses
.length
> 0;
255 if ( this.useIndentClasses
)
257 this.classNameRegex
= new RegExp( '(?:^|\\s+)(' + editor
.config
.indentClasses
.join( '|' ) + ')(?=$|\\s)' );
258 this.indentClassMap
= {};
259 for ( var i
= 0 ; i
< editor
.config
.indentClasses
.length
; i
++ )
260 this.indentClassMap
[ editor
.config
.indentClasses
[i
] ] = i
+ 1;
263 this.indentCssProperty
= editor
.config
.contentsLangDirection
== 'ltr' ? 'margin-left' : 'margin-right';
264 this.startDisabled
= name
== 'outdent';
267 indentCommand
.prototype = {
268 exec : function( editor
)
270 var selection
= editor
.getSelection(),
271 range
= selection
&& selection
.getRanges()[0];
273 if ( !selection
|| !range
)
276 var bookmarks
= selection
.createBookmarks( true ),
277 nearestListBlock
= range
.getCommonAncestor();
279 while ( nearestListBlock
&& !( nearestListBlock
.type
== CKEDITOR
.NODE_ELEMENT
&&
280 listNodeNames
[ nearestListBlock
.getName() ] ) )
281 nearestListBlock
= nearestListBlock
.getParent();
283 if ( nearestListBlock
)
284 indentList
.call( this, editor
, range
, nearestListBlock
);
286 indentBlock
.call( this, editor
, range
);
289 editor
.forceNextSelectionCheck();
290 selection
.selectBookmarks( bookmarks
);
294 CKEDITOR
.plugins
.add( 'indent',
296 init : function( editor
)
298 // Register commands.
299 var indent
= new indentCommand( editor
, 'indent' ),
300 outdent
= new indentCommand( editor
, 'outdent' );
301 editor
.addCommand( 'indent', indent
);
302 editor
.addCommand( 'outdent', outdent
);
304 // Register the toolbar buttons.
305 editor
.ui
.addButton( 'Indent',
307 label
: editor
.lang
.indent
,
310 editor
.ui
.addButton( 'Outdent',
312 label
: editor
.lang
.outdent
,
316 // Register the state changing handlers.
317 editor
.on( 'selectionChange', CKEDITOR
.tools
.bind( onSelectionChange
, indent
) );
318 editor
.on( 'selectionChange', CKEDITOR
.tools
.bind( onSelectionChange
, outdent
) );
321 requires
: [ 'domiterator', 'list' ]
325 CKEDITOR
.tools
.extend( CKEDITOR
.config
,
333 * Size of each indentation step
336 * config.indentOffset = 40;
340 * Unit for the indentation style
343 * config.indentUnit = 'px';
347 * List of classes to use for indenting the contents.
350 * // Don't use classes for indenting. (this is the default value)
351 * config.indentClasses = null;
353 * // Use the classes 'Indent1', 'Indent2', 'Indent3'
354 * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];
358 * Size of each indentation step
362 * config.indentOffset = 4;
366 * Unit for the indentation style
370 * config.indentUnit = 'em';
374 * List of classes to use for indenting the contents. If it's null, no classes will be used
375 * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used.
379 * // Use the classes 'Indent1', 'Indent2', 'Indent3'
380 * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];