Mercurial > public > sg101
diff media/js/tiny_mce/tiny_mce_src.js @ 217:237710206167
Update TinyMCE to 3.3.6
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 01 Jun 2010 04:49:29 +0000 |
parents | 149c3567fec1 |
children | 6ed2932901fa |
line wrap: on
line diff
--- a/media/js/tiny_mce/tiny_mce_src.js Sun May 30 20:51:41 2010 +0000 +++ b/media/js/tiny_mce/tiny_mce_src.js Tue Jun 01 04:49:29 2010 +0000 @@ -5,9 +5,9 @@ var tinymce = { majorVersion : '3', - minorVersion : '3.2', - - releaseDate : '2010-03-25', + minorVersion : '3.6', + + releaseDate : '2010-05-20', _init : function() { var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; @@ -26,6 +26,8 @@ t.isAir = /adobeair/i.test(ua); + t.isIDevice = /(iPad|iPhone)/.test(ua); + // TinyMCE .NET webcontrol might be setting the values for TinyMCE if (win.tinyMCEPreInit) { t.suffix = tinyMCEPreInit.suffix; @@ -1262,7 +1264,7 @@ if (keep_children) { while (child = node.firstChild) { // IE 8 will crash if you don't remove completely empty text nodes - if (child.nodeType !== 3 || child.nodeValue) + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) parent.insertBefore(child, node); else node.removeChild(child); @@ -1908,7 +1910,7 @@ // So if we replace the p elements with divs and mark them and then replace them back to paragraphs // after we use innerHTML we can fix the DOM tree h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">'); - h = h.replace(/<\/p>/g, '</div>'); + h = h.replace(/<\/p>/gi, '</div>'); // Set the new HTML with DIVs set(); @@ -2412,10 +2414,13 @@ for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { nodeType = node.nodeType; - // Handle normalization of text nodes - if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length)) - idx++; - + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + + idx++; lastNodeType = nodeType; } } @@ -3255,35 +3260,9 @@ function Selection(selection) { var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; - // Compares two IE specific ranges to see if they are different - // this method is useful when invalidating the cached selection range - function compareRanges(rng1, rng2) { - if (rng1 && rng2) { - // Both are control ranges and the selected element matches - if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) - return TRUE; - - // Both are text ranges and the range matches - if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { - // IE will say that the range is equal then produce an invalid argument exception - // if you perform specific operations in a keyup event. For example Ctrl+Del. - // This hack will invalidate the range cache if the exception occurs - try { - // Try accessing nextSibling will producer an invalid argument some times - range.startContainer.nextSibling; - return TRUE; - } catch (ex) { - // Ignore - } - } - } - - return FALSE; - }; - // Returns a W3C DOM compatible range object by using the IE Range API function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged; + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed; // If selection is outside the current document just return an empty range element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); @@ -3298,84 +3277,89 @@ return domRange; } - // Duplicare IE selection range and check if the range is collapsed - ieRange2 = ieRange.duplicate(); collapsed = selection.isCollapsed(); - // Insert invisible start marker - ieRange.collapse(); - ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>'); - - // Insert invisible end marker - if (!collapsed) { - ieRange2.collapse(FALSE); - ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>'); - } - - // Sets the end point of the range by looking for the marker - // This method also merges the text nodes it splits so that - // the DOM doesn't get fragmented. - function setEndPoint(start) { - var container, offset, marker, sibling; - - // Look for endpoint marker - marker = dom.get('_mce_' + (start ? 'start' : 'end')); - sibling = marker.previousSibling; - - // Is marker after a text node - if (sibling && sibling.nodeType == 3) { - // Get container node and calc offset - container = sibling; - offset = container.nodeValue.length; + function findEndPoint(start) { + var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position; + + // Setup temp range and collapse it + checkRng = ieRange.duplicate(); + checkRng.collapse(start); + + // Create marker and insert it at the end of the endpoints parent + marker = dom.create('a'); + parent = checkRng.parentElement(); + parent.appendChild(marker); + checkRng.moveToElementText(marker); + position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng); + if (position > 0) { + // The position is after the end of the parent element. + // This is the case where IE puts the caret to the left edge of a table. + domRange[start ? 'setStartAfter' : 'setEndAfter'](parent); dom.remove(marker); - - // Merge text nodes to reduce DOM fragmentation - sibling = container.nextSibling; - if (sibling && sibling.nodeType == 3) { - isMerged = TRUE; - container.appendData(sibling.nodeValue); - dom.remove(sibling); - } + return; + } + + // Setup node list and endIndex + nodes = tinymce.grep(parent.childNodes); + endIndex = nodes.length - 1; + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Insert marker and check it's position relative to the selection + parent.insertBefore(marker, nodes[index]); + checkRng.moveToElementText(marker); + position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng); + if (position > 0) { + // Marker is to the right + startIndex = index + 1; + } else if (position < 0) { + // Marker is to the left + endIndex = index - 1; + } else { + // Maker is where we are + found = true; + break; + } + } + + // Setup container + container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling; + + // Handle element selection + if (container.nodeType == 1) { + dom.remove(marker); + + // Find offset and container + offset = dom.nodeIndex(container); + container = container.parentNode; + + // Move the offset if we are setting the end or the position is after an element + if (!start || index > 0) + offset++; } else { - sibling = marker.nextSibling; - - // Is marker before a text node - if (sibling && sibling.nodeType == 3) { - container = sibling; - offset = 0; + // Calculate offset within text node + if (position > 0 || index == 0) { + checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange); + offset = checkRng.text.length; } else { - // Is marker before an element - if (sibling) - offset = dom.nodeIndex(sibling) - 1; - else - offset = dom.nodeIndex(marker); - - container = marker.parentNode; + checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange); + offset = container.nodeValue.length - checkRng.text.length; } dom.remove(marker); } - // Set start of range - if (start) - domRange.setStart(container, offset); - - // Set end of range or automatically if it's collapsed to increase performance - if (!start || collapsed) - domRange.setEnd(container, offset); + domRange[start ? 'setStart' : 'setEnd'](container, offset); }; - // Set start of range - setEndPoint(TRUE); - - // Set end of range if needed + // Find start point + findEndPoint(true); + + // Find end point if needed if (!collapsed) - setEndPoint(FALSE); - - // Restore selection if the range contents was merged - // since the selection was then moved since the text nodes got changed - if (isMerged) - t.addRange(domRange); + findEndPoint(); return domRange; }; @@ -3489,9 +3473,17 @@ // If same text container then we can do a more simple move if (sc == ec && sc.nodeType == 3) { - ieRng.moveEnd('character', eo - so); - ieRng.select(); - ieRng.scrollIntoView(); + try { + ieRng.moveEnd('character', eo - so); + ieRng.select(); + ieRng.scrollIntoView(); + } catch (ex) { + // Some times a Runtime error of the 800a025e type gets thrown + // especially when the caret is placed before a table. + // This is a somewhat strange location for the caret. + // TODO: Find a better solution for this would possible require a rewrite of the setRng method + } + return; } @@ -3518,13 +3510,23 @@ this.getRangeAt = function() { // Setup new range if the cache is empty - if (!range || !compareRanges(lastIERng, selection.getRng())) { + if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) { range = getRange(); // Store away text range for next call lastIERng = selection.getRng(); } + // IE will say that the range is equal then produce an invalid argument exception + // if you perform specific operations in a keyup event. For example Ctrl+Del. + // This hack will invalidate the range cache if the exception occurs + try { + range.startContainer.nextSibling; + } catch (ex) { + range = getRange(); + lastIERng = null; + } + // Return cached range return range; }; @@ -5066,17 +5068,21 @@ h += '<span id="__caret">_</span>'; // Delete and insert new node - if (r.startContainer == d && r.endContainer == d) { + + if (r.startContainer == d && r.endContainer == d) { // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents d.body.innerHTML = h; } else { r.deleteContents(); - r.insertNode(t.getRng().createContextualFragment(h)); + if (d.body.childNodes.length == 0) { + d.body.innerHTML = h; + } else { + r.insertNode(r.createContextualFragment(h)); + } } // Move to caret marker c = t.dom.get('__caret'); - // Make sure we wrap it compleatly, Opera fails with a simple select call r = d.createRange(); r.setStartBefore(c); @@ -5187,10 +5193,10 @@ point.push(offset); } else { childNodes = container.childNodes; - - if (offset >= childNodes.length) { + + if (offset >= childNodes.length && childNodes.length) { after = 1; - offset = childNodes.length - 1; + offset = Math.max(0, childNodes.length - 1); } point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); @@ -5269,7 +5275,7 @@ }, moveToBookmark : function(bookmark) { - var t = this, dom = t.dom, marker1, marker2, rng, root; + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; // Clear selection cache if (t.tridentSel) @@ -5301,8 +5307,6 @@ t.setRng(rng); } else if (bookmark.id) { - rng = dom.createRng(); - function restoreEndPoint(suffix) { var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; @@ -5317,8 +5321,8 @@ idx = 1; } - rng.setStart(node, idx); - rng.setEnd(node, idx); + startContainer = endContainer = node; + startOffset = endOffset = idx; } else { if (!keep) { idx = dom.nodeIndex(marker); @@ -5327,7 +5331,8 @@ idx = 1; } - rng.setEnd(node, idx); + endContainer = node; + endOffset = idx; } if (!keep) { @@ -5352,10 +5357,12 @@ dom.remove(next); if (suffix == 'start') { - rng.setStart(prev, idx); - rng.setEnd(prev, idx); - } else - rng.setEnd(prev, idx); + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } } } } @@ -5365,6 +5372,9 @@ restoreEndPoint('start'); restoreEndPoint('end'); + rng = dom.createRng(); + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); t.setRng(rng); } else if (bookmark.name) { t.select(dom.select(bookmark.name)[bookmark.index]); @@ -5469,18 +5479,30 @@ if (!r) r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange(); + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; + } + } return r; }, setRng : function(r) { var s, t = this; - + if (!t.tridentSel) { s = t.getSel(); if (s) { + t.explicitRange = r; s.removeAllRanges(); s.addRange(r); + t.selectedRange = s.getRangeAt(0); } } else { // Is W3C Range @@ -7210,6 +7232,26 @@ }; */ }; + + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; })(tinymce); (function(tinymce) { @@ -9157,6 +9199,12 @@ if (!t.getElement()) return; + // Is a iPad/iPhone, then skip initialization. We need to sniff here since the + // browser says it has contentEditable support but there is no visible caret + // We will remove this check ones Apple implements full contentEditable support + if (tinymce.isIDevice) + return; + // Add hidden input for non input elements inside form elements if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); @@ -9522,6 +9570,7 @@ hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, fontname : {inline : 'span', styles : {fontFamily : '%value'}}, fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, removeformat : [ @@ -9800,13 +9849,28 @@ focus : function(sf) { - var oed, t = this, ce = t.settings.content_editable; + var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); if (!sf) { + // Get selected control element + ieRng = t.selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + // Is not content editable if (!ce) t.getWin().focus(); + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + } if (tinymce.activeEditor != t) { @@ -10593,11 +10657,9 @@ } // Add default shortcuts for gecko - if (isGecko) { - t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); - t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); - t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); - } + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); // BlockFormat shortcuts keys for (i=1; i<=6; i++) @@ -10944,7 +11006,7 @@ } // Present alert message about clipboard access not being available - if (failed || !doc.queryCommandEnabled(command)) { + if (failed || !doc.queryCommandSupported(command)) { if (tinymce.isGecko) { editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { if (state) @@ -11048,7 +11110,7 @@ var node = value || selection.getNode(); // Make sure that the body node isn't removed - if (node != ed.getBody()) { + if (node != editor.getBody()) { storeSelection(); editor.dom.remove(node, TRUE); restoreSelection(); @@ -11153,8 +11215,16 @@ if (value.href) dom.setAttribs(link, value); else - ed.dom.remove(link, TRUE); - } + editor.dom.remove(link, TRUE); + } + }, + + selectAll : function() { + var root = dom.getRoot(); + var rng = dom.createRng(); + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + editor.selection.setRng(rng); } }); @@ -11499,7 +11569,7 @@ }; ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { insertBr(ed); Event.cancel(e); } @@ -11609,6 +11679,13 @@ } } } else { + // Force control range into text range + if (r.item) { + tr = d.body.createTextRange(); + tr.moveToElementText(r.item(0)); + r = tr; + } + tr = d.body.createTextRange(); tr.moveToElementText(b); tr.collapse(1); @@ -12630,7 +12707,8 @@ // Move startContainer/startOffset in to a suitable node if (container.nodeType == 1 || container.nodeValue === "") { - walker = new TreeWalker(container.childNodes[offset]); + container = container.nodeType == 1 ? container.childNodes[offset] : container; + walker = new TreeWalker(container, container.parentNode); for (node = walker.current(); node; node = walker.next()) { if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { rng.setStart(node, 0); @@ -12713,7 +12791,7 @@ } }); - // Contine processing if a selector match wasn't found and a inline element is defined + // Continue processing if a selector match wasn't found and a inline element is defined if (!format.inline || found) { currentWrapElm = 0; return; @@ -12806,14 +12884,23 @@ }); }); + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + // Look for parent with similar style format - dom.getParent(node.parentNode, function(parent) { - if (matchNode(parent, name, vars)) { - dom.remove(node, 1); - node = 0; - return TRUE; - } - }); + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> if (node) { @@ -12940,7 +13027,13 @@ var node = dom.get(start ? '_start' : '_end'), out = node[start ? 'firstChild' : 'lastChild']; - dom.remove(node, 1); + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); return out; }; @@ -13009,7 +13102,7 @@ apply(name, vars, node); }; - function matchNode(node, name, vars) { + function matchNode(node, name, vars, similar) { var formatList = get(name), format, i, classes; function matchItems(node, format, item_name) { @@ -13026,7 +13119,10 @@ else value = getStyle(node, key); - if (!isEq(value, replaceVars(items[key], vars))) + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) return; } } @@ -13069,7 +13165,7 @@ function matchParents(node) { // Find first node with similar format settings node = dom.getParent(node, function(node) { - return !!matchNode(node, name, vars); + return !!matchNode(node, name, vars, true); }); // Do an exact check on the similar format element @@ -13691,7 +13787,7 @@ }; function isTextBlock(name) { - return /^(h[1-6]|p|div|pre|address)$/.test(name); + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); }; function getContainer(rng, start) { @@ -13757,6 +13853,7 @@ // Pending apply or remove formats if (hasPending()) { ed.getDoc().execCommand('FontName', false, 'mceinline'); + pendingFormats.lastRng = selection.getRng(); // IE will convert the current word each(dom.select('font,span'), function(node) { @@ -13776,9 +13873,10 @@ each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { ed[event].addToTop(function(ed, e) { - if (hasPending()) { + // Do we have pending formats and is the selection moved has moved + if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) { each(dom.select('font,span'), function(node) { - var bookmark, textNode, rng; + var textNode, rng; // Look for marker if (isCaretNode(node)) {