1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 /** 7 * @file Increse and decrease indent commands. 8 */ 9 10 (function() 11 { 12 var listNodeNames = { ol : 1, ul : 1 }; 13 14 function setState( editor, state ) 15 { 16 editor.getCommand( this.name ).setState( state ); 17 } 18 19 function onSelectionChange( evt ) 20 { 21 var elements = evt.data.path.elements, 22 listNode, listItem, 23 editor = evt.editor; 24 25 for ( var i = 0 ; i < elements.length ; i++ ) 26 { 27 if ( elements[i].getName() == 'li' ) 28 { 29 listItem = elements[i]; 30 continue; 31 } 32 if ( listNodeNames[ elements[i].getName() ] ) 33 { 34 listNode = elements[i]; 35 break; 36 } 37 } 38 39 if ( listNode ) 40 { 41 if ( this.name == 'outdent' ) 42 return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); 43 else 44 { 45 while ( listItem && ( listItem = listItem.getPrevious() ) ) 46 { 47 if ( listItem.getName && listItem.getName() == 'li' ) 48 return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); 49 } 50 return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); 51 } 52 } 53 54 if ( !this.useIndentClasses && this.name == 'indent' ) 55 return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); 56 57 var path = evt.data.path, 58 firstBlock = path.block || path.blockLimit; 59 if ( !firstBlock ) 60 return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); 61 62 if ( this.useIndentClasses ) 63 { 64 var indentClass = firstBlock.$.className.match( this.classNameRegex ), 65 indentStep = 0; 66 if ( indentClass ) 67 { 68 indentClass = indentClass[1]; 69 indentStep = this.indentClassMap[ indentClass ]; 70 } 71 if ( ( this.name == 'outdent' && !indentStep ) || 72 ( this.name == 'indent' && indentStep == editor.config.indentClass.length ) ) 73 return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); 74 return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); 75 } 76 else 77 { 78 var indent = parseInt( firstBlock.getStyle( this.indentCssProperty ), 10 ); 79 if ( isNaN( indent ) ) 80 indent = 0; 81 if ( indent <= 0 ) 82 return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); 83 return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); 84 } 85 } 86 87 function indentList( editor, range, listNode ) 88 { 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(); 97 98 if ( !startContainer || !endContainer ) 99 return; 100 101 // Now we can iterate over the individual items on the same tree depth. 102 var block = startContainer, 103 itemsToMove = [], 104 stopFlag = false; 105 while ( !stopFlag ) 106 { 107 if ( block.equals( endContainer ) ) 108 stopFlag = true; 109 itemsToMove.push( block ); 110 block = block.getNext(); 111 } 112 if ( itemsToMove.length < 1 ) 113 return; 114 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(); 120 for ( var i = 0 ; i < listParents.length ; i++ ) 121 { 122 if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] ) 123 { 124 listNode = listParents[i]; 125 break; 126 } 127 } 128 var indentOffset = this.name == 'indent' ? 1 : -1, 129 startItem = itemsToMove[0], 130 lastItem = itemsToMove[ itemsToMove.length - 1 ], 131 database = {}; 132 133 // Convert the list DOM tree into a one dimensional array. 134 var listArray = CKEDITOR.plugins.list.listToArray( listNode, database ); 135 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++ ) 139 listArray[i].indent += indentOffset; 140 for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ; 141 i < listArray.length && listArray[i].indent > baseIndent ; i++ ) 142 listArray[i].indent += indentOffset; 143 144 // Convert the array back to a DOM forest (yes we might have a few subtrees now). 145 // And replace the old list with the new forest. 146 var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 ); 147 if ( newList ) 148 newList.listNode.replace( listNode ); 149 150 // Clean up the markers. 151 CKEDITOR.dom.element.clearAllMarkers( database ); 152 } 153 154 function indentBlock( editor, range ) 155 { 156 var iterator = range.createIterator(); 157 iterator.enforceRealBlocks = true; 158 159 var block; 160 while ( ( block = iterator.getNextParagraph() ) ) 161 { 162 163 if ( this.useIndentClasses ) 164 { 165 // Transform current class name to indent step index. 166 var indentClass = block.$.className.match( this.classNameRegex ), 167 indentStep = 0; 168 if ( indentClass ) 169 { 170 indentClass = indentClass[1]; 171 indentStep = this.indentClassMap[ indentClass ]; 172 } 173 174 // Operate on indent step index, transform indent step index back to class 175 // name. 176 if ( this.name == 'outdent' ) 177 indentStep--; 178 else 179 indentStep++; 180 indentStep = Math.min( indentStep, editor.config.indentClasses.length ); 181 indentStep = Math.max( indentStep, 0 ); 182 var className = CKEDITOR.tools.ltrim( block.$.className.replace( this.classNameRegex, '' ) ); 183 if ( indentStep < 1 ) 184 block.$.className = className; 185 else 186 block.addClass( editor.config.indentClasses[ indentStep - 1 ] ); 187 } 188 else 189 { 190 var currentOffset = parseInt( block.getStyle( this.indentCssProperty ), 10 ); 191 if ( isNaN( currentOffset ) ) 192 currentOffset = 0; 193 currentOffset += ( this.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset; 194 currentOffset = Math.max( currentOffset, 0 ); 195 currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset; 196 block.setStyle( this.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' ); 197 if ( block.getAttribute( 'style' ) === '' ) 198 block.removeAttribute( 'style' ); 199 } 200 } 201 } 202 203 function indentCommand( editor, name ) 204 { 205 this.name = name; 206 this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0; 207 if ( this.useIndentClasses ) 208 { 209 this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' ); 210 this.indentClassMap = {}; 211 for ( var i = 0 ; i < editor.config.indentClasses.length ; i++ ) 212 this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1; 213 } 214 else 215 this.indentCssProperty = editor.config.contentsLangDirection == 'ltr' ? 'margin-left' : 'margin-right'; 216 } 217 218 indentCommand.prototype = { 219 exec : function( editor ) 220 { 221 var selection = editor.getSelection(), 222 range = selection && selection.getRanges()[0]; 223 224 if ( !selection || !range ) 225 return; 226 227 var bookmarks = selection.createBookmarks( true ), 228 nearestListBlock = range.getCommonAncestor(); 229 230 while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT && 231 listNodeNames[ nearestListBlock.getName() ] ) ) 232 nearestListBlock = nearestListBlock.getParent(); 233 234 if ( nearestListBlock ) 235 indentList.call( this, editor, range, nearestListBlock ); 236 else 237 indentBlock.call( this, editor, range ); 238 239 editor.focus(); 240 editor.forceNextSelectionCheck(); 241 selection.selectBookmarks( bookmarks ); 242 } 243 }; 244 245 CKEDITOR.plugins.add( 'indent', 246 { 247 init : function( editor ) 248 { 249 // Register commands. 250 var indent = new indentCommand( editor, 'indent' ), 251 outdent = new indentCommand( editor, 'outdent' ); 252 editor.addCommand( 'indent', indent ); 253 editor.addCommand( 'outdent', outdent ); 254 255 // Register the toolbar buttons. 256 editor.ui.addButton( 'Indent', 257 { 258 label : editor.lang.indent, 259 command : 'indent' 260 }); 261 editor.ui.addButton( 'Outdent', 262 { 263 label : editor.lang.outdent, 264 command : 'outdent' 265 }); 266 267 // Register the state changing handlers. 268 editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, indent ) ); 269 editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, outdent ) ); 270 }, 271 272 requires : [ 'domiterator', 'list' ] 273 } ); 274 })(); 275 276 CKEDITOR.tools.extend( CKEDITOR.config, 277 { 278 indentOffset : 40, 279 indentUnit : 'px', 280 indentClasses : null 281 }); 282