Thomas Bruederli
2012-07-30 6843acc9fced3dc1261e019ad4552b6e4c340ec7
program/js/tiny_mce/plugins/lists/editor_plugin_src.js
@@ -99,11 +99,7 @@
         return e2.style.listStyleType === 'none' || containsOnlyAList(e2);
      } else if (isList(e1)) {
         return (e1.tagName === e2.tagName && (allowDifferentListStyles || e1.style.listStyleType === e2.style.listStyleType)) || isListForIndent(e2);
      } else if (mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P') {
         return true;
      } else {
         return false;
      }
      } else return mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P';
   }
   function isListForIndent(e) {
@@ -144,15 +140,18 @@
   }
   tinymce.create('tinymce.plugins.Lists', {
      init: function(ed, url) {
         var LIST_TABBING = 0;
         var LIST_EMPTY_ITEM = 1;
         var LIST_ESCAPE = 2;
         var LIST_UNKNOWN = 3;
      init: function(ed) {
         var LIST_TABBING = 'TABBING';
         var LIST_EMPTY_ITEM = 'EMPTY';
         var LIST_ESCAPE = 'ESCAPE';
         var LIST_PARAGRAPH = 'PARAGRAPH';
         var LIST_UNKNOWN = 'UNKNOWN';
         var state = LIST_UNKNOWN;
         function isTabInList(e) {
            return e.keyCode === 9 && (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList'));
            // Don't indent on Ctrl+Tab or Alt+Tab
            return e.keyCode === tinymce.VK.TAB && !(e.altKey || e.ctrlKey) &&
               (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList'));
         }
         function isOnLastListItem() {
@@ -183,43 +182,93 @@
         function isEmptyListItem(li) {
            var numChildren = li.childNodes.length;
            if (li.tagName === 'LI') {
               return numChildren == 0 ? true : numChildren == 1 && (li.firstChild.tagName == '' || isEmptyWebKitLi(li) || isEmptyIE9Li(li));
               return numChildren == 0 ? true : numChildren == 1 && (li.firstChild.tagName == '' || li.firstChild.tagName == 'BR' || isEmptyIE9Li(li));
            }
            return false;
         }
         function isEmptyWebKitLi(li) {
            // Check for empty LI or a LI with just a child that is a BR since Gecko and WebKit uses BR elements to place the caret
            return tinymce.isWebKit && li.firstChild.nodeName == 'BR';
         }
         function isEmptyIE9Li(li) {
            // only consider this to be last item if there is no list item content or that content is nbsp or space since IE9 creates these
            var lis = tinymce.grep(li.parentNode.childNodes, function(n) {return n.nodeName == 'LI'});
            var lis = tinymce.grep(li.parentNode.childNodes, function(n) {return n.tagName == 'LI'});
            var isLastLi = li == lis[lis.length - 1];
            var child = li.firstChild;
            return tinymce.isIE9 && isLastLi && (child.nodeValue == String.fromCharCode(160) || child.nodeValue == String.fromCharCode(32));
         }
         function isEnter(e) {
            return e.keyCode === 13;
            return e.keyCode === tinymce.VK.ENTER;
         }
         function isEnterWithoutShift(e) {
            return isEnter(e) && !e.shiftKey;
         }
         function getListKeyState(e) {
            if (isTabInList(e)) {
               return LIST_TABBING;
            } else if (isEnter(e) && isOnLastListItem()) {
               return LIST_ESCAPE;
            } else if (isEnter(e) && isInEmptyListItem()) {
            } else if (isEnterWithoutShift(e) && isOnLastListItem()) {
               // Returns LIST_UNKNOWN since breaking out of lists is handled by the EnterKey.js logic now
               //return LIST_ESCAPE;
               return LIST_UNKNOWN;
            } else if (isEnterWithoutShift(e) && isInEmptyListItem()) {
               return LIST_EMPTY_ITEM;
            } else {
               return LIST_UNKNOWN;
            }
         }
         function cancelEnterAndTab(_, e) {
            if (state == LIST_TABBING || state == LIST_EMPTY_ITEM) {
               return Event.cancel(e);
         function cancelDefaultEvents(ed, e) {
            // list escape is done manually using outdent as it does not create paragraphs correctly in td's
            if (state == LIST_TABBING || state == LIST_EMPTY_ITEM || tinymce.isGecko && state == LIST_ESCAPE) {
               Event.cancel(e);
            }
         }
         function isCursorAtEndOfContainer() {
            var range = ed.selection.getRng(true);
            var startContainer = range.startContainer;
            if (startContainer.nodeType == 3) {
               var value = startContainer.nodeValue;
               if (tinymce.isIE9 && value.length > 1 && value.charCodeAt(value.length-1) == 32) {
                  // IE9 places a space on the end of the text in some cases so ignore last char
                  return (range.endOffset == value.length-1);
               } else {
                  return (range.endOffset == value.length);
               }
            } else if (startContainer.nodeType == 1) {
               return range.endOffset == startContainer.childNodes.length;
            }
            return false;
         }
         /*
             If we are at the end of a list item surrounded with an element, pressing enter should create a
             new list item instead without splitting the element e.g. don't want to create new P or H1 tag
           */
         function isEndOfListItem() {
            var node = ed.selection.getNode();
            var validElements = 'h1,h2,h3,h4,h5,h6,p,div';
            var isLastParagraphOfLi = ed.dom.is(node, validElements) && node.parentNode.tagName === 'LI' && node.parentNode.lastChild === node;
            return ed.selection.isCollapsed() && isLastParagraphOfLi && isCursorAtEndOfContainer();
         }
         // Creates a new list item after the current selection's list item parent
         function createNewLi(ed, e) {
            if (isEnterWithoutShift(e) && isEndOfListItem()) {
               var node = ed.selection.getNode();
               var li = ed.dom.create("li");
               var parentLi = ed.dom.getParent(node, 'li');
               ed.dom.insertAfter(li, parentLi);
               // Move caret to new list element.
               if (tinymce.isIE6 || tinymce.isIE7 || tinyMCE.isIE8) {
                  // Removed this line since it would create an odd <&nbsp;> tag and placing the caret inside an empty LI is handled and should be handled by the selection logic
                  //li.appendChild(ed.dom.create("&nbsp;")); // IE needs an element within the bullet point
                  ed.selection.setCursorLocation(li, 1);
               } else {
                  ed.selection.setCursorLocation(li, 0);
               }
               e.preventDefault();
            }
         }
@@ -230,7 +279,7 @@
               return;
            var n = ed.selection.getStart();
            if (e.keyCode != 8 || n.tagName !== 'IMG')
            if (e.keyCode != tinymce.VK.BACKSPACE || n.tagName !== 'IMG')
               return;
            function lastLI(node) {
@@ -296,7 +345,8 @@
            var list = ed.dom.getParent(li, 'ol,ul');
            if (list != null) {
               var lastLi = list.lastChild;
               lastLi.appendChild(ed.getDoc().createElement(''));
               // Removed this line since IE9 would report an DOM character error and placing the caret inside an empty LI is handled and should be handled by the selection logic
               //lastLi.appendChild(ed.getDoc().createElement(''));
               ed.selection.setCursorLocation(lastLi, 0);
            }
         }
@@ -338,25 +388,120 @@
               if (tinymce.isIE) {
                  setCursorPositionToOriginalLi(li);
               }
               return Event.cancel(e);
            } else if (state == LIST_ESCAPE) {
               if (tinymce.isIE8) {
                  // append a zero sized nbsp so that caret is positioned correctly in IE8 after escaping and applying formatting.
               if (tinymce.isIE6 || tinymce.isIE7 || tinymce.isIE8) {
                  // append a zero sized nbsp so that caret is positioned correctly in IE after escaping and applying formatting.
                  // if there is no text then applying formatting for e.g a H1 to the P tag immediately following list after
                  // escaping from it will cause the caret to be positioned on the last li instead of staying the in P tag.
                  var n = ed.getDoc().createTextNode('\uFEFF');
                  ed.selection.getNode().appendChild(n);
               } else if (tinymce.isIE9) {
               } else if (tinymce.isIE9 || tinymce.isGecko) {
                  // IE9 does not escape the list so we use outdent to do this and cancel the default behaviour
                  // Gecko does not create a paragraph outdenting inside a TD so default behaviour is cancelled and we outdent ourselves
                  ed.execCommand('Outdent');
                  return Event.cancel(e);
               }
            }
         });
         function fixListItem(parent, reference) {
            // a zero-sized non-breaking space is placed in the empty list item so that the nested list is
            // displayed on the below line instead of next to it
            var n = ed.getDoc().createTextNode('\uFEFF');
            parent.insertBefore(n, reference);
            ed.selection.setCursorLocation(n, 0);
            // repaint to remove rendering artifact. only visible when creating new list
            ed.execCommand('mceRepaint');
         }
         function fixIndentedListItemForGecko(ed, e) {
            if (isEnter(e)) {
               var li = getLi();
               if (li) {
                  var parent = li.parentNode;
                  var grandParent = parent && parent.parentNode;
                  if (grandParent && grandParent.nodeName == 'LI' && grandParent.firstChild == parent && li == parent.firstChild) {
                     fixListItem(grandParent, parent);
                  }
               }
            }
         }
         function fixIndentedListItemForIE8(ed, e) {
            if (isEnter(e)) {
               var li = getLi();
               if (ed.dom.select('ul li', li).length === 1) {
                  var list = li.firstChild;
                  fixListItem(li, list);
               }
            }
         }
         function fixDeletingFirstCharOfList(ed, e) {
            function listElements(li) {
               var elements = [];
               var walker = new tinymce.dom.TreeWalker(li.firstChild, li);
               for (var node = walker.current(); node; node = walker.next()) {
                  if (ed.dom.is(node, 'ol,ul,li')) {
                     elements.push(node);
                  }
               }
               return elements;
            }
            if (e.keyCode == tinymce.VK.BACKSPACE) {
               var li = getLi();
               if (li) {
                  var list = ed.dom.getParent(li, 'ol,ul'),
                     rng  = ed.selection.getRng();
                  if (list && list.firstChild === li && rng.startOffset == 0) {
                     var elements = listElements(li);
                     elements.unshift(li);
                     ed.execCommand("Outdent", false, elements);
                     ed.undoManager.add();
                     return Event.cancel(e);
                  }
               }
            }
         }
         function fixDeletingEmptyLiInWebkit(ed, e) {
            var li = getLi();
            if (e.keyCode === tinymce.VK.BACKSPACE && ed.dom.is(li, 'li') && li.parentNode.firstChild!==li) {
               if (ed.dom.select('ul,ol', li).length === 1) {
                  var prevLi = li.previousSibling;
                  ed.dom.remove(ed.dom.select('br', li));
                  ed.dom.remove(li, true);
                  var textNodes = tinymce.grep(prevLi.childNodes, function(n){ return n.nodeType === 3 });
                  if (textNodes.length === 1) {
                     var textNode = textNodes[0];
                     ed.selection.setCursorLocation(textNode, textNode.length);
                  }
                  ed.undoManager.add();
                  return Event.cancel(e);
               }
            }
         }
         ed.onKeyDown.add(function(_, e) { state = getListKeyState(e); });
         ed.onKeyDown.add(cancelEnterAndTab);
         ed.onKeyDown.add(cancelDefaultEvents);
         ed.onKeyDown.add(imageJoiningListItem);
         ed.onKeyPress.add(cancelEnterAndTab);
         ed.onKeyDown.add(createNewLi);
         if (tinymce.isGecko) {
            ed.onKeyUp.add(fixIndentedListItemForGecko);
         }
         if (tinymce.isIE8) {
            ed.onKeyUp.add(fixIndentedListItemForIE8);
         }
         if (tinymce.isGecko || tinymce.isWebKit) {
            ed.onKeyDown.add(fixDeletingFirstCharOfList);
         }
         if (tinymce.isWebKit) {
            ed.onKeyDown.add(fixDeletingEmptyLiInWebkit);
         }
      },
      applyList: function(targetListType, oppositeListType) {
@@ -382,16 +527,18 @@
            if (element.tagName === 'LI') {
               // No change required.
            } else if (element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'BODY') {
               processBrs(element, function(startSection, br, previousBR) {
               processBrs(element, function(startSection, br) {
                  doWrapList(startSection, br, element.tagName === 'BODY' ? null : startSection.parentNode);
                  li = startSection.parentNode;
                  adjustIndentForNewList(li);
                  cleanupBr(br);
               });
               if (element.tagName === 'P' || selectedBlocks.length > 1) {
                  dom.split(li.parentNode.parentNode, li.parentNode);
               if (li) {
                  if (li.tagName === 'LI' && (element.tagName === 'P' || selectedBlocks.length > 1)) {
                     dom.split(li.parentNode.parentNode, li.parentNode);
                  }
                  attemptMergeWithAdjacent(li.parentNode, true);
               }
               attemptMergeWithAdjacent(li.parentNode, true);
               return;
            } else {
               // Put the list around the element.
@@ -408,7 +555,7 @@
         }
         function doWrapList(start, end, template) {
            var li, n = start, tmp, i;
            var li, n = start, tmp;
            while (!dom.isBlock(start.parentNode) && start.parentNode !== dom.getRoot()) {
               start = dom.split(start.parentNode, start.previousSibling);
               start = start.nextSibling;
@@ -464,7 +611,6 @@
            // First mark the BRs that have any part of the previous section selected.
            var trailingContentSelected = false;
            each(dom.select(breakElements, element), function(br) {
               var b;
               if (br.hasAttribute && br.hasAttribute('_mce_bogus')) {
                  return true; // Skip the bogus Brs that are put in to appease Firefox and Safari.
               }
@@ -560,7 +706,7 @@
            }
         });
         if (hasNonList || hasOppositeType || selectedBlocks.length === 0) {
         if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) {
            actions = {
               'LI': changeList,
               'H1': makeList,
@@ -572,11 +718,14 @@
               'P': makeList,
               'BODY': makeList,
               'DIV': selectedBlocks.length > 1 ? makeList : wrapList,
               defaultAction: wrapList
               defaultAction: wrapList,
               elements: this.selectedBlocks()
            };
         } else {
            actions = {
               defaultAction: convertListItemToParagraph
               defaultAction: convertListItemToParagraph,
               elements: this.selectedBlocks(),
               processEvenIfEmpty: true
            };
         }
         this.process(actions);
@@ -619,12 +768,13 @@
         this.process({
            'LI': indentLI,
            defaultAction: this.adjustPaddingFunction(true)
            defaultAction: this.adjustPaddingFunction(true),
            elements: this.selectedBlocks()
         });
      },
      outdent: function() {
      outdent: function(ui, elements) {
         var t = this, ed = t.ed, dom = ed.dom, outdented = [];
         function outdentLI(element) {
@@ -656,9 +806,11 @@
            }
         }
         var listElements = elements && tinymce.is(elements, 'array') ? elements : this.selectedBlocks();
         this.process({
            'LI': outdentLI,
            defaultAction: this.adjustPaddingFunction(false)
            defaultAction: this.adjustPaddingFunction(false),
            elements: listElements
         });
         each(outdented, attemptMergeWithAdjacent);
@@ -667,9 +819,17 @@
      process: function(actions) {
         var t = this, sel = t.ed.selection, dom = t.ed.dom, selectedBlocks, r;
         function isEmptyElement(element) {
            var excludeBrsAndBookmarks = tinymce.grep(element.childNodes, function(n) {
               return !(n.nodeName === 'BR' || n.nodeName === 'SPAN' && dom.getAttrib(n, 'data-mce-type') == 'bookmark'
                     || n.nodeType == 3 && (n.nodeValue == String.fromCharCode(160) || n.nodeValue == ''));
            });
            return excludeBrsAndBookmarks.length === 0;
         }
         function processElement(element) {
            dom.removeClass(element, '_mce_act_on');
            if (!element || element.nodeType !== 1) {
            if (!element || element.nodeType !== 1 || ! actions.processEvenIfEmpty && selectedBlocks.length > 1 && isEmptyElement(element)) {
               return;
            }
            element = findItemToOperateOn(element, dom);
@@ -681,7 +841,7 @@
         }
         function recurse(element) {
            t.splitSafeEach(element.childNodes, processElement);
            t.splitSafeEach(element.childNodes, processElement, true);
         }
         function brAtEdgeOfSelection(container, offset) {
@@ -689,10 +849,13 @@
                  container.childNodes[offset].tagName === 'BR';
         }
         selectedBlocks = sel.getSelectedBlocks();
         if (selectedBlocks.length === 0) {
            selectedBlocks = [ dom.getRoot() ];
         function isInTable() {
            var n = sel.getNode();
            var p = dom.getParent(n, 'td');
            return p !== null;
         }
         selectedBlocks = actions.elements;
         r = sel.getRng(true);
         if (!r.collapsed) {
@@ -721,13 +884,19 @@
         t.splitSafeEach(selectedBlocks, processElement);
         sel.moveToBookmark(bookmark);
         bookmark = null;
         // Avoids table or image handles being left behind in Firefox.
         t.ed.execCommand('mceRepaint');
         // we avoid doing repaint in a table as this will move the caret out of the table in Firefox 3.6
         if (!isInTable()) {
            // Avoids table or image handles being left behind in Firefox.
            t.ed.execCommand('mceRepaint');
         }
      },
      splitSafeEach: function(elements, f) {
         if (tinymce.isGecko && (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) ||
               /Firefox\/3\.[0-4]/.test(navigator.userAgent))) {
      splitSafeEach: function(elements, f, forceClassBase) {
         if (forceClassBase ||
            (tinymce.isGecko &&
               (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) ||
                /Firefox\/3\.[0-4]/.test(navigator.userAgent)))) {
            this.classBasedEach(elements, f);
         } else {
            each(elements, f);
@@ -767,6 +936,11 @@
         };
      },
      selectedBlocks: function() {
         var ed = this.ed, selectedBlocks = ed.selection.getSelectedBlocks();
         return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks;
      },
      getInfo: function() {
         return {
            longname : 'Lists',