]> scripts.mit.edu Git - autoinstallsdev/wordpress.git/blob - wp-includes/js/tinymce/plugins/lists/plugin.js
Wordpress 4.6
[autoinstallsdev/wordpress.git] / wp-includes / js / tinymce / plugins / lists / plugin.js
1 /**
2  * plugin.js
3  *
4  * Released under LGPL License.
5  * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6  *
7  * License: http://www.tinymce.com/license
8  * Contributing: http://www.tinymce.com/contributing
9  */
10
11 /*global tinymce:true */
12 /*eslint consistent-this:0 */
13
14 tinymce.PluginManager.add('lists', function(editor) {
15         var self = this;
16
17         function isChildOfBody(elm) {
18                 return editor.$.contains(editor.getBody(), elm);
19         }
20
21         function isBr(node) {
22                 return node && node.nodeName == 'BR';
23         }
24
25         function isListNode(node) {
26                 return node && (/^(OL|UL|DL)$/).test(node.nodeName) && isChildOfBody(node);
27         }
28
29         function isFirstChild(node) {
30                 return node.parentNode.firstChild == node;
31         }
32
33         function isLastChild(node) {
34                 return node.parentNode.lastChild == node;
35         }
36
37         function isTextBlock(node) {
38                 return node && !!editor.schema.getTextBlockElements()[node.nodeName];
39         }
40
41         function isEditorBody(elm) {
42                 return elm === editor.getBody();
43         }
44
45         editor.on('init', function() {
46                 var dom = editor.dom, selection = editor.selection;
47
48                 function isEmpty(elm, keepBookmarks) {
49                         var empty = dom.isEmpty(elm);
50
51                         if (keepBookmarks && dom.select('span[data-mce-type=bookmark]').length > 0) {
52                                 return false;
53                         }
54
55                         return empty;
56                 }
57
58                 /**
59                  * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
60                  * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
61                  * added to them since they can be restored after a dom operation.
62                  *
63                  * So this: <p><b>|</b><b>|</b></p>
64                  * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
65                  *
66                  * @param  {DOMRange} rng DOM Range to get bookmark on.
67                  * @return {Object} Bookmark object.
68                  */
69                 function createBookmark(rng) {
70                         var bookmark = {};
71
72                         function setupEndPoint(start) {
73                                 var offsetNode, container, offset;
74
75                                 container = rng[start ? 'startContainer' : 'endContainer'];
76                                 offset = rng[start ? 'startOffset' : 'endOffset'];
77
78                                 if (container.nodeType == 1) {
79                                         offsetNode = dom.create('span', {'data-mce-type': 'bookmark'});
80
81                                         if (container.hasChildNodes()) {
82                                                 offset = Math.min(offset, container.childNodes.length - 1);
83
84                                                 if (start) {
85                                                         container.insertBefore(offsetNode, container.childNodes[offset]);
86                                                 } else {
87                                                         dom.insertAfter(offsetNode, container.childNodes[offset]);
88                                                 }
89                                         } else {
90                                                 container.appendChild(offsetNode);
91                                         }
92
93                                         container = offsetNode;
94                                         offset = 0;
95                                 }
96
97                                 bookmark[start ? 'startContainer' : 'endContainer'] = container;
98                                 bookmark[start ? 'startOffset' : 'endOffset'] = offset;
99                         }
100
101                         setupEndPoint(true);
102
103                         if (!rng.collapsed) {
104                                 setupEndPoint();
105                         }
106
107                         return bookmark;
108                 }
109
110                 /**
111                  * Moves the selection to the current bookmark and removes any selection container wrappers.
112                  *
113                  * @param {Object} bookmark Bookmark object to move selection to.
114                  */
115                 function moveToBookmark(bookmark) {
116                         function restoreEndPoint(start) {
117                                 var container, offset, node;
118
119                                 function nodeIndex(container) {
120                                         var node = container.parentNode.firstChild, idx = 0;
121
122                                         while (node) {
123                                                 if (node == container) {
124                                                         return idx;
125                                                 }
126
127                                                 // Skip data-mce-type=bookmark nodes
128                                                 if (node.nodeType != 1 || node.getAttribute('data-mce-type') != 'bookmark') {
129                                                         idx++;
130                                                 }
131
132                                                 node = node.nextSibling;
133                                         }
134
135                                         return -1;
136                                 }
137
138                                 container = node = bookmark[start ? 'startContainer' : 'endContainer'];
139                                 offset = bookmark[start ? 'startOffset' : 'endOffset'];
140
141                                 if (!container) {
142                                         return;
143                                 }
144
145                                 if (container.nodeType == 1) {
146                                         offset = nodeIndex(container);
147                                         container = container.parentNode;
148                                         dom.remove(node);
149                                 }
150
151                                 bookmark[start ? 'startContainer' : 'endContainer'] = container;
152                                 bookmark[start ? 'startOffset' : 'endOffset'] = offset;
153                         }
154
155                         restoreEndPoint(true);
156                         restoreEndPoint();
157
158                         var rng = dom.createRng();
159
160                         rng.setStart(bookmark.startContainer, bookmark.startOffset);
161
162                         if (bookmark.endContainer) {
163                                 rng.setEnd(bookmark.endContainer, bookmark.endOffset);
164                         }
165
166                         selection.setRng(rng);
167                 }
168
169                 function createNewTextBlock(contentNode, blockName) {
170                         var node, textBlock, fragment = dom.createFragment(), hasContentNode;
171                         var blockElements = editor.schema.getBlockElements();
172
173                         if (editor.settings.forced_root_block) {
174                                 blockName = blockName || editor.settings.forced_root_block;
175                         }
176
177                         if (blockName) {
178                                 textBlock = dom.create(blockName);
179
180                                 if (textBlock.tagName === editor.settings.forced_root_block) {
181                                         dom.setAttribs(textBlock, editor.settings.forced_root_block_attrs);
182                                 }
183
184                                 fragment.appendChild(textBlock);
185                         }
186
187                         if (contentNode) {
188                                 while ((node = contentNode.firstChild)) {
189                                         var nodeName = node.nodeName;
190
191                                         if (!hasContentNode && (nodeName != 'SPAN' || node.getAttribute('data-mce-type') != 'bookmark')) {
192                                                 hasContentNode = true;
193                                         }
194
195                                         if (blockElements[nodeName]) {
196                                                 fragment.appendChild(node);
197                                                 textBlock = null;
198                                         } else {
199                                                 if (blockName) {
200                                                         if (!textBlock) {
201                                                                 textBlock = dom.create(blockName);
202                                                                 fragment.appendChild(textBlock);
203                                                         }
204
205                                                         textBlock.appendChild(node);
206                                                 } else {
207                                                         fragment.appendChild(node);
208                                                 }
209                                         }
210                                 }
211                         }
212
213                         if (!editor.settings.forced_root_block) {
214                                 fragment.appendChild(dom.create('br'));
215                         } else {
216                                 // BR is needed in empty blocks on non IE browsers
217                                 if (!hasContentNode && (!tinymce.Env.ie || tinymce.Env.ie > 10)) {
218                                         textBlock.appendChild(dom.create('br', {'data-mce-bogus': '1'}));
219                                 }
220                         }
221
222                         return fragment;
223                 }
224
225                 function getSelectedListItems() {
226                         return tinymce.grep(selection.getSelectedBlocks(), function(block) {
227                                 return /^(LI|DT|DD)$/.test(block.nodeName);
228                         });
229                 }
230
231                 function splitList(ul, li, newBlock) {
232                         var tmpRng, fragment, bookmarks, node;
233
234                         function removeAndKeepBookmarks(targetNode) {
235                                 tinymce.each(bookmarks, function(node) {
236                                         targetNode.parentNode.insertBefore(node, li.parentNode);
237                                 });
238
239                                 dom.remove(targetNode);
240                         }
241
242                         bookmarks = dom.select('span[data-mce-type="bookmark"]', ul);
243                         newBlock = newBlock || createNewTextBlock(li);
244                         tmpRng = dom.createRng();
245                         tmpRng.setStartAfter(li);
246                         tmpRng.setEndAfter(ul);
247                         fragment = tmpRng.extractContents();
248
249                         for (node = fragment.firstChild; node; node = node.firstChild) {
250                                 if (node.nodeName == 'LI' && dom.isEmpty(node)) {
251                                         dom.remove(node);
252                                         break;
253                                 }
254                         }
255
256                         if (!dom.isEmpty(fragment)) {
257                                 dom.insertAfter(fragment, ul);
258                         }
259
260                         dom.insertAfter(newBlock, ul);
261
262                         if (isEmpty(li.parentNode)) {
263                                 removeAndKeepBookmarks(li.parentNode);
264                         }
265
266                         dom.remove(li);
267
268                         if (isEmpty(ul)) {
269                                 dom.remove(ul);
270                         }
271                 }
272
273                 var shouldMerge = function (listBlock, sibling) {
274                         var targetStyle = editor.dom.getStyle(listBlock, 'list-style-type', true);
275                         var style = editor.dom.getStyle(sibling, 'list-style-type', true);
276                         return targetStyle === style;
277                 };
278
279                 function mergeWithAdjacentLists(listBlock) {
280                         var sibling, node;
281
282                         sibling = listBlock.nextSibling;
283                         if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
284                                 while ((node = sibling.firstChild)) {
285                                         listBlock.appendChild(node);
286                                 }
287
288                                 dom.remove(sibling);
289                         }
290
291                         sibling = listBlock.previousSibling;
292                         if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName && shouldMerge(listBlock, sibling)) {
293                                 while ((node = sibling.firstChild)) {
294                                         listBlock.insertBefore(node, listBlock.firstChild);
295                                 }
296
297                                 dom.remove(sibling);
298                         }
299                 }
300
301                 /**
302                  * Normalizes the all lists in the specified element.
303                  */
304                 function normalizeList(element) {
305                         tinymce.each(tinymce.grep(dom.select('ol,ul', element)), function(ul) {
306                                 var sibling, parentNode = ul.parentNode;
307
308                                 // Move UL/OL to previous LI if it's the only child of a LI
309                                 if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
310                                         sibling = parentNode.previousSibling;
311                                         if (sibling && sibling.nodeName == 'LI') {
312                                                 sibling.appendChild(ul);
313
314                                                 if (isEmpty(parentNode)) {
315                                                         dom.remove(parentNode);
316                                                 }
317                                         }
318                                 }
319
320                                 // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
321                                 if (isListNode(parentNode)) {
322                                         sibling = parentNode.previousSibling;
323                                         if (sibling && sibling.nodeName == 'LI') {
324                                                 sibling.appendChild(ul);
325                                         }
326                                 }
327                         });
328                 }
329
330                 function outdent(li) {
331                         var ul = li.parentNode, ulParent = ul.parentNode, newBlock;
332
333                         function removeEmptyLi(li) {
334                                 if (isEmpty(li)) {
335                                         dom.remove(li);
336                                 }
337                         }
338
339                         if (isEditorBody(ul)) {
340                                 return true;
341                         }
342
343                         if (li.nodeName == 'DD') {
344                                 dom.rename(li, 'DT');
345                                 return true;
346                         }
347
348                         if (isFirstChild(li) && isLastChild(li)) {
349                                 if (ulParent.nodeName == "LI") {
350                                         dom.insertAfter(li, ulParent);
351                                         removeEmptyLi(ulParent);
352                                         dom.remove(ul);
353                                 } else if (isListNode(ulParent)) {
354                                         dom.remove(ul, true);
355                                 } else {
356                                         ulParent.insertBefore(createNewTextBlock(li), ul);
357                                         dom.remove(ul);
358                                 }
359
360                                 return true;
361                         } else if (isFirstChild(li)) {
362                                 if (ulParent.nodeName == "LI") {
363                                         dom.insertAfter(li, ulParent);
364                                         li.appendChild(ul);
365                                         removeEmptyLi(ulParent);
366                                 } else if (isListNode(ulParent)) {
367                                         ulParent.insertBefore(li, ul);
368                                 } else {
369                                         ulParent.insertBefore(createNewTextBlock(li), ul);
370                                         dom.remove(li);
371                                 }
372
373                                 return true;
374                         } else if (isLastChild(li)) {
375                                 if (ulParent.nodeName == "LI") {
376                                         dom.insertAfter(li, ulParent);
377                                 } else if (isListNode(ulParent)) {
378                                         dom.insertAfter(li, ul);
379                                 } else {
380                                         dom.insertAfter(createNewTextBlock(li), ul);
381                                         dom.remove(li);
382                                 }
383
384                                 return true;
385                         }
386
387                         if (ulParent.nodeName == 'LI') {
388                                 ul = ulParent;
389                                 newBlock = createNewTextBlock(li, 'LI');
390                         } else if (isListNode(ulParent)) {
391                                 newBlock = createNewTextBlock(li, 'LI');
392                         } else {
393                                 newBlock = createNewTextBlock(li);
394                         }
395
396                         splitList(ul, li, newBlock);
397                         normalizeList(ul.parentNode);
398
399                         return true;
400                 }
401
402                 function indent(li) {
403                         var sibling, newList, listStyle;
404
405                         function mergeLists(from, to) {
406                                 var node;
407
408                                 if (isListNode(from)) {
409                                         while ((node = li.lastChild.firstChild)) {
410                                                 to.appendChild(node);
411                                         }
412
413                                         dom.remove(from);
414                                 }
415                         }
416
417                         if (li.nodeName == 'DT') {
418                                 dom.rename(li, 'DD');
419                                 return true;
420                         }
421
422                         sibling = li.previousSibling;
423
424                         if (sibling && isListNode(sibling)) {
425                                 sibling.appendChild(li);
426                                 return true;
427                         }
428
429                         if (sibling && sibling.nodeName == 'LI' && isListNode(sibling.lastChild)) {
430                                 sibling.lastChild.appendChild(li);
431                                 mergeLists(li.lastChild, sibling.lastChild);
432                                 return true;
433                         }
434
435                         sibling = li.nextSibling;
436
437                         if (sibling && isListNode(sibling)) {
438                                 sibling.insertBefore(li, sibling.firstChild);
439                                 return true;
440                         }
441
442                         /*if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
443                                 return false;
444                         }*/
445
446                         sibling = li.previousSibling;
447                         if (sibling && sibling.nodeName == 'LI') {
448                                 newList = dom.create(li.parentNode.nodeName);
449                                 listStyle = dom.getStyle(li.parentNode, 'listStyleType');
450                                 if (listStyle) {
451                                         dom.setStyle(newList, 'listStyleType', listStyle);
452                                 }
453                                 sibling.appendChild(newList);
454                                 newList.appendChild(li);
455                                 mergeLists(li.lastChild, newList);
456                                 return true;
457                         }
458
459                         return false;
460                 }
461
462                 function indentSelection() {
463                         var listElements = getSelectedListItems();
464
465                         if (listElements.length) {
466                                 var bookmark = createBookmark(selection.getRng(true));
467
468                                 for (var i = 0; i < listElements.length; i++) {
469                                         if (!indent(listElements[i]) && i === 0) {
470                                                 break;
471                                         }
472                                 }
473
474                                 moveToBookmark(bookmark);
475                                 editor.nodeChanged();
476
477                                 return true;
478                         }
479                 }
480
481                 function outdentSelection() {
482                         var listElements = getSelectedListItems();
483
484                         if (listElements.length) {
485                                 var bookmark = createBookmark(selection.getRng(true));
486                                 var i, y, root = editor.getBody();
487
488                                 i = listElements.length;
489                                 while (i--) {
490                                         var node = listElements[i].parentNode;
491
492                                         while (node && node != root) {
493                                                 y = listElements.length;
494                                                 while (y--) {
495                                                         if (listElements[y] === node) {
496                                                                 listElements.splice(i, 1);
497                                                                 break;
498                                                         }
499                                                 }
500
501                                                 node = node.parentNode;
502                                         }
503                                 }
504
505                                 for (i = 0; i < listElements.length; i++) {
506                                         if (!outdent(listElements[i]) && i === 0) {
507                                                 break;
508                                         }
509                                 }
510
511                                 moveToBookmark(bookmark);
512                                 editor.nodeChanged();
513
514                                 return true;
515                         }
516                 }
517
518                 function applyList(listName, detail) {
519                         var rng = selection.getRng(true), bookmark, listItemName = 'LI';
520
521                         if (dom.getContentEditable(selection.getNode()) === "false") {
522                                 return;
523                         }
524
525                         listName = listName.toUpperCase();
526
527                         if (listName == 'DL') {
528                                 listItemName = 'DT';
529                         }
530
531                         function getSelectedTextBlocks() {
532                                 var textBlocks = [], root = editor.getBody();
533
534                                 function getEndPointNode(start) {
535                                         var container, offset;
536
537                                         container = rng[start ? 'startContainer' : 'endContainer'];
538                                         offset = rng[start ? 'startOffset' : 'endOffset'];
539
540                                         // Resolve node index
541                                         if (container.nodeType == 1) {
542                                                 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
543                                         }
544
545                                         while (container.parentNode != root) {
546                                                 if (isTextBlock(container)) {
547                                                         return container;
548                                                 }
549
550                                                 if (/^(TD|TH)$/.test(container.parentNode.nodeName)) {
551                                                         return container;
552                                                 }
553
554                                                 container = container.parentNode;
555                                         }
556
557                                         return container;
558                                 }
559
560                                 var startNode = getEndPointNode(true);
561                                 var endNode = getEndPointNode();
562                                 var block, siblings = [];
563
564                                 for (var node = startNode; node; node = node.nextSibling) {
565                                         siblings.push(node);
566
567                                         if (node == endNode) {
568                                                 break;
569                                         }
570                                 }
571
572                                 tinymce.each(siblings, function(node) {
573                                         if (isTextBlock(node)) {
574                                                 textBlocks.push(node);
575                                                 block = null;
576                                                 return;
577                                         }
578
579                                         if (dom.isBlock(node) || isBr(node)) {
580                                                 if (isBr(node)) {
581                                                         dom.remove(node);
582                                                 }
583
584                                                 block = null;
585                                                 return;
586                                         }
587
588                                         var nextSibling = node.nextSibling;
589                                         if (tinymce.dom.BookmarkManager.isBookmarkNode(node)) {
590                                                 if (isTextBlock(nextSibling) || (!nextSibling && node.parentNode == root)) {
591                                                         block = null;
592                                                         return;
593                                                 }
594                                         }
595
596                                         if (!block) {
597                                                 block = dom.create('p');
598                                                 node.parentNode.insertBefore(block, node);
599                                                 textBlocks.push(block);
600                                         }
601
602                                         block.appendChild(node);
603                                 });
604
605                                 return textBlocks;
606                         }
607
608                         bookmark = createBookmark(rng);
609
610                         tinymce.each(getSelectedTextBlocks(), function(block) {
611                                 var listBlock, sibling;
612
613                                 var hasCompatibleStyle = function (sib) {
614                                         var sibStyle = dom.getStyle(sib, 'list-style-type');
615                                         var detailStyle = detail ? detail['list-style-type'] : '';
616
617                                         detailStyle = detailStyle === null ? '' : detailStyle;
618
619                                         return sibStyle === detailStyle;
620                                 };
621
622                                 sibling = block.previousSibling;
623                                 if (sibling && isListNode(sibling) && sibling.nodeName == listName && hasCompatibleStyle(sibling)) {
624                                         listBlock = sibling;
625                                         block = dom.rename(block, listItemName);
626                                         sibling.appendChild(block);
627                                 } else {
628                                         listBlock = dom.create(listName);
629                                         block.parentNode.insertBefore(listBlock, block);
630                                         listBlock.appendChild(block);
631                                         block = dom.rename(block, listItemName);
632                                 }
633
634                                 updateListStyle(listBlock, detail);
635                                 mergeWithAdjacentLists(listBlock);
636                         });
637
638                         moveToBookmark(bookmark);
639                 }
640
641                 var updateListStyle = function (el, detail) {
642                         dom.setStyle(el, 'list-style-type', detail ? detail['list-style-type'] : null);
643                 };
644
645                 function removeList() {
646                         var bookmark = createBookmark(selection.getRng(true)), root = editor.getBody();
647
648                         tinymce.each(getSelectedListItems(), function(li) {
649                                 var node, rootList;
650
651                                 if (isEditorBody(li.parentNode)) {
652                                         return;
653                                 }
654
655                                 if (isEmpty(li)) {
656                                         outdent(li);
657                                         return;
658                                 }
659
660                                 for (node = li; node && node != root; node = node.parentNode) {
661                                         if (isListNode(node)) {
662                                                 rootList = node;
663                                         }
664                                 }
665
666                                 splitList(rootList, li);
667                         });
668
669                         moveToBookmark(bookmark);
670                 }
671
672                 function toggleList(listName, detail) {
673                         var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
674
675                         if (isEditorBody(parentList)) {
676                                 return;
677                         }
678
679                         if (parentList) {
680                                 if (parentList.nodeName == listName) {
681                                         removeList(listName);
682                                 } else {
683                                         var bookmark = createBookmark(selection.getRng(true));
684                                         updateListStyle(parentList, detail);
685                                         mergeWithAdjacentLists(dom.rename(parentList, listName));
686
687                                         moveToBookmark(bookmark);
688                                 }
689                         } else {
690                                 applyList(listName, detail);
691                         }
692                 }
693
694                 function queryListCommandState(listName) {
695                         return function() {
696                                 var parentList = dom.getParent(editor.selection.getStart(), 'UL,OL,DL');
697
698                                 return parentList && parentList.nodeName == listName;
699                         };
700                 }
701
702                 function isBogusBr(node) {
703                         if (!isBr(node)) {
704                                 return false;
705                         }
706
707                         if (dom.isBlock(node.nextSibling) && !isBr(node.previousSibling)) {
708                                 return true;
709                         }
710
711                         return false;
712                 }
713
714                 self.backspaceDelete = function(isForward) {
715                         function findNextCaretContainer(rng, isForward) {
716                                 var node = rng.startContainer, offset = rng.startOffset;
717                                 var nonEmptyBlocks, walker;
718
719                                 if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
720                                         return node;
721                                 }
722
723                                 nonEmptyBlocks = editor.schema.getNonEmptyElements();
724                                 if (node.nodeType == 1) {
725                                         node = tinymce.dom.RangeUtils.getNode(node, offset);
726                                 }
727
728                                 walker = new tinymce.dom.TreeWalker(node, editor.getBody());
729
730                                 // Delete at <li>|<br></li> then jump over the bogus br
731                                 if (isForward) {
732                                         if (isBogusBr(node)) {
733                                                 walker.next();
734                                         }
735                                 }
736
737                                 while ((node = walker[isForward ? 'next' : 'prev2']())) {
738                                         if (node.nodeName == 'LI' && !node.hasChildNodes()) {
739                                                 return node;
740                                         }
741
742                                         if (nonEmptyBlocks[node.nodeName]) {
743                                                 return node;
744                                         }
745
746                                         if (node.nodeType == 3 && node.data.length > 0) {
747                                                 return node;
748                                         }
749                                 }
750                         }
751
752                         function mergeLiElements(fromElm, toElm) {
753                                 var node, listNode, ul = fromElm.parentNode;
754
755                                 if (!isChildOfBody(fromElm) || !isChildOfBody(toElm)) {
756                                         return;
757                                 }
758
759                                 if (isListNode(toElm.lastChild)) {
760                                         listNode = toElm.lastChild;
761                                 }
762
763                                 if (ul == toElm.lastChild) {
764                                         if (isBr(ul.previousSibling)) {
765                                                 dom.remove(ul.previousSibling);
766                                         }
767                                 }
768
769                                 node = toElm.lastChild;
770                                 if (node && isBr(node) && fromElm.hasChildNodes()) {
771                                         dom.remove(node);
772                                 }
773
774                                 if (isEmpty(toElm, true)) {
775                                         dom.$(toElm).empty();
776                                 }
777
778                                 if (!isEmpty(fromElm, true)) {
779                                         while ((node = fromElm.firstChild)) {
780                                                 toElm.appendChild(node);
781                                         }
782                                 }
783
784                                 if (listNode) {
785                                         toElm.appendChild(listNode);
786                                 }
787
788                                 dom.remove(fromElm);
789
790                                 if (isEmpty(ul) && !isEditorBody(ul)) {
791                                         dom.remove(ul);
792                                 }
793                         }
794
795                         if (selection.isCollapsed()) {
796                                 var li = dom.getParent(selection.getStart(), 'LI'), ul, rng, otherLi;
797
798                                 if (li) {
799                                         ul = li.parentNode;
800                                         if (isEditorBody(ul) && dom.isEmpty(ul)) {
801                                                 return true;
802                                         }
803
804                                         rng = selection.getRng(true);
805                                         otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
806
807                                         if (otherLi && otherLi != li) {
808                                                 var bookmark = createBookmark(rng);
809
810                                                 if (isForward) {
811                                                         mergeLiElements(otherLi, li);
812                                                 } else {
813                                                         mergeLiElements(li, otherLi);
814                                                 }
815
816                                                 moveToBookmark(bookmark);
817
818                                                 return true;
819                                         } else if (!otherLi) {
820                                                 if (!isForward && removeList(ul.nodeName)) {
821                                                         return true;
822                                                 }
823                                         }
824                                 }
825                         }
826                 };
827
828                 editor.on('BeforeExecCommand', function(e) {
829                         var cmd = e.command.toLowerCase(), isHandled;
830
831                         if (cmd == "indent") {
832                                 if (indentSelection()) {
833                                         isHandled = true;
834                                 }
835                         } else if (cmd == "outdent") {
836                                 if (outdentSelection()) {
837                                         isHandled = true;
838                                 }
839                         }
840
841                         if (isHandled) {
842                                 editor.fire('ExecCommand', {command: e.command});
843                                 e.preventDefault();
844                                 return true;
845                         }
846                 });
847
848                 editor.addCommand('InsertUnorderedList', function(ui, detail) {
849                         toggleList('UL', detail);
850                 });
851
852                 editor.addCommand('InsertOrderedList', function(ui, detail) {
853                         toggleList('OL', detail);
854                 });
855
856                 editor.addCommand('InsertDefinitionList', function(ui, detail) {
857                         toggleList('DL', detail);
858                 });
859
860                 editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState('UL'));
861                 editor.addQueryStateHandler('InsertOrderedList', queryListCommandState('OL'));
862                 editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState('DL'));
863
864                 editor.on('keydown', function(e) {
865                         // Check for tab but not ctrl/cmd+tab since it switches browser tabs
866                         if (e.keyCode != 9 || tinymce.util.VK.metaKeyPressed(e)) {
867                                 return;
868                         }
869
870                         if (editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD')) {
871                                 e.preventDefault();
872
873                                 if (e.shiftKey) {
874                                         outdentSelection();
875                                 } else {
876                                         indentSelection();
877                                 }
878                         }
879                 });
880         });
881
882         editor.addButton('indent', {
883                 icon: 'indent',
884                 title: 'Increase indent',
885                 cmd: 'Indent',
886                 onPostRender: function() {
887                         var ctrl = this;
888
889                         editor.on('nodechange', function() {
890                                 var blocks = editor.selection.getSelectedBlocks();
891                                 var disable = false;
892
893                                 for (var i = 0, l = blocks.length; !disable && i < l; i++) {
894                                         var tag = blocks[i].nodeName;
895
896                                         disable = (tag == 'LI' && isFirstChild(blocks[i]) || tag == 'UL' || tag == 'OL' || tag == 'DD');
897                                 }
898
899                                 ctrl.disabled(disable);
900                         });
901                 }
902         });
903
904         editor.on('keydown', function(e) {
905                 if (e.keyCode == tinymce.util.VK.BACKSPACE) {
906                         if (self.backspaceDelete()) {
907                                 e.preventDefault();
908                         }
909                 } else if (e.keyCode == tinymce.util.VK.DELETE) {
910                         if (self.backspaceDelete(true)) {
911                                 e.preventDefault();
912                         }
913                 }
914         });
915 });