X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Ftiny_mce%2Fplugins%2Flists%2Feditor_plugin_src.js;h=ebe4fa5c4e14856186180cd5d1839ded99d0c1ba;hb=a8ce0eff2e9c97d6c0c878451f83a3759ec0d07f;hp=1db29f71c969da6653efcbbbeb35517ff0e77669;hpb=2797e21fbf023037fc706d85ab314e609a0d3a07;p=citadel.git diff --git a/webcit/tiny_mce/plugins/lists/editor_plugin_src.js b/webcit/tiny_mce/plugins/lists/editor_plugin_src.js index 1db29f71c..ebe4fa5c4 100644 --- a/webcit/tiny_mce/plugins/lists/editor_plugin_src.js +++ b/webcit/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,97 @@ 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(); } } @@ -230,7 +283,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) { @@ -338,25 +391,118 @@ 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) { @@ -382,16 +528,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 +556,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 +612,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 +707,7 @@ } }); - if (hasNonList || hasOppositeType || selectedBlocks.length === 0) { + if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) { actions = { 'LI': changeList, 'H1': makeList, @@ -572,11 +719,13 @@ '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); @@ -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 || selectedBlocks.length > 1 && isEmptyElement(element)) { return; } element = findItemToOperateOn(element, dom); @@ -689,11 +849,14 @@ 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)) { @@ -721,8 +884,12 @@ 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) { @@ -767,6 +934,12 @@ }; }, + selectedBlocks: function() { + var ed = this.ed + var selectedBlocks = ed.selection.getSelectedBlocks(); + return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks; + }, + getInfo: function() { return { longname : 'Lists',