1 /** 2 * Add indent and outdent command identifier for KISSY Editor.Modified from CKEditor 3 * @author yiminghe@gmail.com 4 */ 5 /* 6 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 7 For licensing, see LICENSE.html or http://ckeditor.com/license 8 */ 9 KISSY.add("editor/plugin/dentUtils/cmd", function (S, Editor, ListUtils) { 10 11 var listNodeNames = {ol:1, ul:1}, 12 Walker = Editor.Walker, 13 DOM = S.DOM, 14 Node = S.Node, 15 UA = S.UA, 16 isNotWhitespaces = Walker.whitespaces(true), 17 INDENT_CSS_PROPERTY = "margin-left", 18 INDENT_OFFSET = 40, 19 INDENT_UNIT = "px", 20 isNotBookmark = Walker.bookmark(false, true); 21 22 function isListItem(node) { 23 return node.nodeType == DOM.ELEMENT_NODE && DOM.nodeName(node) == 'li'; 24 } 25 26 function indentList(range, listNode, type) { 27 // Our starting and ending points of the range might be inside some blocks under a list item... 28 // So before playing with the iterator, we need to expand the block to include the list items. 29 30 var startContainer = range.startContainer, 31 endContainer = range.endContainer; 32 while (startContainer && 33 !startContainer.parent().equals(listNode)) 34 startContainer = startContainer.parent(); 35 while (endContainer && 36 !endContainer.parent().equals(listNode)) 37 endContainer = endContainer.parent(); 38 39 if (!startContainer || !endContainer) 40 return; 41 42 // Now we can iterate over the individual items on the same tree depth. 43 var block = startContainer, 44 itemsToMove = [], 45 stopFlag = false; 46 while (!stopFlag) { 47 if (block.equals(endContainer)) 48 stopFlag = true; 49 itemsToMove.push(block); 50 block = block.next(); 51 } 52 if (itemsToMove.length < 1) 53 return; 54 55 // Do indent or outdent operations on the array model of the list, not the 56 // list's DOM tree itself. The array model demands that it knows as much as 57 // possible about the surrounding lists, we need to feed it the further 58 // ancestor node that is still a list. 59 var listParents = listNode._4e_parents(true, undefined); 60 61 listParents.each(function (n, i) { 62 listParents[i] = n; 63 }); 64 65 for (var i = 0; i < listParents.length; i++) { 66 if (listNodeNames[ listParents[i].nodeName() ]) { 67 listNode = listParents[i]; 68 break; 69 } 70 } 71 var indentOffset = type == 'indent' ? 1 : -1, 72 startItem = itemsToMove[0], 73 lastItem = itemsToMove[ itemsToMove.length - 1 ], 74 database = {}; 75 76 // Convert the list DOM tree into a one dimensional array. 77 var listArray = ListUtils.listToArray(listNode, database); 78 79 // Apply indenting or outdenting on the array. 80 // listarray_index 为 item 在数组中的下标,方便计算 81 var baseIndent = listArray[ lastItem.data('listarray_index') ].indent; 82 for (i = startItem.data('listarray_index'); 83 i <= lastItem.data('listarray_index'); i++) { 84 listArray[ i ].indent += indentOffset; 85 // Make sure the newly created sublist get a brand-new element of the same type. (#5372) 86 var listRoot = listArray[ i ].parent; 87 listArray[ i ].parent = 88 new Node(listRoot[0].ownerDocument.createElement(listRoot.nodeName())); 89 } 90 /* 91 嵌到下层的li 92 <li>鼠标所在开始</li> 93 <li>ss鼠标所在结束ss 94 <ul> 95 <li></li> 96 <li></li> 97 </ul> 98 </li> 99 baseIndent 为鼠标所在结束的嵌套层次, 100 如果下面的比结束li的indent大,那么证明是嵌在结束li里面的,也要缩进 101 一直处理到大于或等于,跳出了当前嵌套 102 */ 103 for (i = lastItem.data('listarray_index') + 1; 104 i < listArray.length && listArray[i].indent > baseIndent; i++) 105 listArray[i].indent += indentOffset; 106 107 // Convert the array back to a DOM forest (yes we might have a few subtrees now). 108 // And replace the old list with the new forest. 109 var newList = ListUtils.arrayToList(listArray, database, null, "p"); 110 111 // Avoid nested <li> after outdent even they're visually same, 112 // recording them for later refactoring.(#3982) 113 var pendingList = []; 114 if (type == 'outdent') { 115 var parentLiElement; 116 if (( parentLiElement = listNode.parent() ) && 117 parentLiElement.nodeName() == 'li') { 118 var children = newList.listNode.childNodes 119 , count = children.length, 120 child; 121 122 for (i = count - 1; i >= 0; i--) { 123 if (( child = new Node(children[i]) ) && 124 child.nodeName() == 'li') 125 pendingList.push(child); 126 } 127 } 128 } 129 130 if (newList) { 131 DOM.insertBefore(newList.listNode[0] || newList.listNode, 132 listNode[0] || listNode); 133 listNode.remove(); 134 } 135 // Move the nested <li> to be appeared after the parent. 136 if (pendingList && pendingList.length) { 137 for (i = 0; i < pendingList.length; i++) { 138 var li = pendingList[ i ], 139 followingList = li; 140 141 // Nest preceding <ul>/<ol> inside current <li> if any. 142 while (( followingList = followingList.next() ) && 143 144 followingList.nodeName() in listNodeNames) { 145 // IE requires a filler NBSP for nested list inside empty list item, 146 // otherwise the list item will be inaccessiable. (#4476) 147 if (UA['ie'] && !li.first(function (node) { 148 return isNotWhitespaces(node) && isNotBookmark(node); 149 },1)) { 150 li[0].appendChild(range.document.createTextNode('\u00a0')); 151 } 152 li[0].appendChild(followingList[0]); 153 } 154 DOM.insertAfter(li[0], parentLiElement[0]); 155 } 156 } 157 158 // Clean up the markers. 159 Editor.Utils.clearAllMarkers(database); 160 } 161 162 function indentBlock(range, type) { 163 var iterator = range.createIterator(), 164 block; 165 // enterMode = "p"; 166 iterator.enforceRealBlocks = true; 167 iterator.enlargeBr = true; 168 while (block = iterator.getNextParagraph()) { 169 indentElement(block, type); 170 } 171 } 172 173 function indentElement(element, type) { 174 var currentOffset = parseInt(element.style(INDENT_CSS_PROPERTY), 10); 175 if (isNaN(currentOffset)) { 176 currentOffset = 0; 177 } 178 currentOffset += ( type == 'indent' ? 1 : -1 ) * INDENT_OFFSET; 179 if (currentOffset < 0) { 180 return false; 181 } 182 currentOffset = Math.max(currentOffset, 0); 183 currentOffset = Math.ceil(currentOffset / INDENT_OFFSET) * INDENT_OFFSET; 184 element.css(INDENT_CSS_PROPERTY, currentOffset ? currentOffset + INDENT_UNIT : ''); 185 if (element[0].style.cssText === '') { 186 element.removeAttr('style'); 187 } 188 189 return true; 190 } 191 192 193 function indentEditor(editor, type) { 194 var selection = editor.getSelection(), 195 range = selection && selection.getRanges()[0]; 196 if (!range) { 197 return; 198 } 199 var startContainer = range.startContainer, 200 endContainer = range.endContainer, 201 rangeRoot = range.getCommonAncestor(), 202 nearestListBlock = rangeRoot; 203 204 while (nearestListBlock && 205 !( nearestListBlock[0].nodeType == DOM.ELEMENT_NODE && 206 listNodeNames[ nearestListBlock.nodeName() ] )) { 207 nearestListBlock = nearestListBlock.parent(); 208 } 209 210 // Avoid selection anchors under list root. 211 // <ul>[<li>...</li>]</ul> => <ul><li>[...]</li></ul> 212 //注:firefox 永远不会出现 213 //注2:哪种情况会出现? 214 if (nearestListBlock 215 && startContainer[0].nodeType == DOM.ELEMENT_NODE 216 && startContainer.nodeName() in listNodeNames) { 217 //S.log("indent from ul/ol"); 218 var walker = new Walker(range); 219 walker.evaluator = isListItem; 220 range.startContainer = walker.next(); 221 } 222 223 if (nearestListBlock 224 && endContainer[0].nodeType == DOM.ELEMENT_NODE 225 && endContainer.nodeName() in listNodeNames) { 226 walker = new Walker(range); 227 walker.evaluator = isListItem; 228 range.endContainer = walker.previous(); 229 } 230 231 var bookmarks = selection.createBookmarks(true); 232 233 if (nearestListBlock) { 234 var firstListItem = nearestListBlock.first(); 235 while (firstListItem && firstListItem.nodeName() != "li") { 236 firstListItem = firstListItem.next(); 237 } 238 var rangeStart = range.startContainer, 239 indentWholeList = firstListItem[0] == rangeStart[0] || firstListItem.contains(rangeStart); 240 241 // Indent the entire list if cursor is inside the first list item. (#3893) 242 if (!( indentWholeList && 243 indentElement(nearestListBlock, type) )) { 244 indentList(range, nearestListBlock, type); 245 } 246 } 247 else { 248 indentBlock(range, type); 249 } 250 selection.selectBookmarks(bookmarks); 251 } 252 253 function addCommand(editor, cmdType) { 254 if (!editor.hasCommand(cmdType)) { 255 editor.addCommand(cmdType, { 256 exec:function (editor) { 257 editor.execCommand("save"); 258 indentEditor(editor, cmdType); 259 editor.execCommand("save"); 260 editor.notifySelectionChange(); 261 } 262 }); 263 } 264 } 265 266 return { 267 checkOutdentActive:function (elementPath) { 268 var blockLimit = elementPath.blockLimit; 269 if (elementPath.contains(listNodeNames)) { 270 return true; 271 } else { 272 var block = elementPath.block || blockLimit; 273 return block && block.style(INDENT_CSS_PROPERTY); 274 } 275 }, 276 addCommand:addCommand 277 }; 278 279 }, { 280 requires:['editor', '../listUtils/'] 281 });