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 });