Updated tiny-mce to most recent 3.4 version
[citadel.git] / webcit / tiny_mce / plugins / lists / editor_plugin_src.js
index 1db29f71c969da6653efcbbbeb35517ff0e77669..ebe4fa5c4e14856186180cd5d1839ded99d0c1ba 100644 (file)
                        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) {
        }
 
        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() {
                        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()) {
+                               } else if (isEnterWithoutShift(e) && isOnLastListItem()) {
                                        return LIST_ESCAPE;
-                               } else if (isEnter(e) && isInEmptyListItem()) {
+                               } 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) {
+                                               li.appendChild(ed.dom.create(" ")); // IE needs an element within the bullet point
+                                               ed.selection.setCursorLocation(li, 1);
+                                       } else if (tinyMCE.isGecko) {
+                                               // This setTimeout is a hack as FF behaves badly if there is no content after the bullet point
+                                               setTimeout(function () {
+                                                       var n = ed.getDoc().createTextNode('\uFEFF');
+                                                       li.appendChild(n);
+                                                       ed.selection.setCursorLocation(li, 0);
+                                               }, 0);
+                                       } else {
+                                               ed.selection.setCursorLocation(li, 0);
+                                       }
+                                       e.preventDefault();
                                }
                        }
 
                                        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) {
                                        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(list, li) {
+                                       var elements = [];
+                                       var walker = new tinymce.dom.TreeWalker(li, list);
+                                       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');
+                                               if (list && list.firstChild === li) {
+                                                       var elements = listElements(list, 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) {
                                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.
                        }
 
                        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;
                                // 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.
                                        }
                                }
                        });
 
-                       if (hasNonList || hasOppositeType || selectedBlocks.length === 0) {
+                       if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) {
                                actions = {
                                        'LI': changeList,
                                        'H1': makeList,
                                        '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()
                                };
                        }
                        this.process(actions);
 
                        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) {
                                }
                        }
 
+                       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);
                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 || selectedBlocks.length > 1 && isEmptyElement(element)) {
                                        return;
                                }
                                element = findItemToOperateOn(element, dom);
                                                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) {
                                if (brAtEdgeOfSelection(r.endContainer, r.endOffset - 1)) {
                        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) {
                        };
                },
 
+               selectedBlocks: function() {
+                       var ed = this.ed
+                       var selectedBlocks = ed.selection.getSelectedBlocks();
+                       return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks;
+               },
+
                getInfo: function() {
                        return {
                                longname : 'Lists',