From a2cf7c41b97a587d90188b83e4d15da1567a54b4 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Wed, 09 Apr 2014 02:48:28 -0400 Subject: [PATCH] Fix accidental key replacements --- program/js/tiny_mce/tiny_mce_src.js | 532 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 400 insertions(+), 132 deletions(-) diff --git a/program/js/tiny_mce/tiny_mce_src.js b/program/js/tiny_mce/tiny_mce_src.js index e38fb7e..86b162b 100644 --- a/program/js/tiny_mce/tiny_mce_src.js +++ b/program/js/tiny_mce/tiny_mce_src.js @@ -6,18 +6,20 @@ var tinymce = { majorVersion : '3', - minorVersion : '5.6', + minorVersion : '5.10', - releaseDate : '2012-07-26', + releaseDate : '2013-10-24', _init : function() { var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; + + t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1); t.isOpera = win.opera && opera.buildNumber; t.isWebKit = /WebKit/.test(ua); - t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); + t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11; t.isIE6 = t.isIE && /MSIE [56]/.test(ua); @@ -27,7 +29,7 @@ t.isIE9 = t.isIE && /MSIE [9]/.test(ua); - t.isGecko = !t.isWebKit && /Gecko/.test(ua); + t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua); t.isMac = ua.indexOf('Mac') != -1; @@ -107,10 +109,14 @@ if (!t) return o !== undef; - if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) + if (t == 'array' && tinymce.isArray(o)) return true; return typeof(o) == t; + }, + + isArray: Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; }, makeMap : function(items, delim, map) { @@ -921,7 +927,7 @@ } if (t == 'object') { - if (o.hasOwnProperty && o instanceof Array) { + if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { for (i=0, v = '['; i<o.length; i++) v += (i > 0 ? ',' : '') + serialize(o[i], quote); @@ -1093,7 +1099,8 @@ })(tinymce); tinymce.util.Quirks = function(editor) { - var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, + settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each; function setEditorCommandState(cmd, state) { try { @@ -1109,56 +1116,81 @@ return documentMode ? documentMode : 6; }; + function isDefaultPrevented(e) { + return e.isDefaultPrevented(); + }; + function cleanupStylesWhenDeleting() { function removeMergedFormatSpans(isDelete) { - var rng, blockElm, node, clonedSpan; + var rng, blockElm, wrapperElm, bookmark, container, offset, elm; + + function isAtStartOrEndOfElm() { + if (container.nodeType == 3) { + if (isDelete && offset == container.length) { + return true; + } + + if (!isDelete && offset === 0) { + return true; + } + } + } rng = selection.getRng(); + var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset]; - // Find root block - blockElm = dom.getParent(rng.startContainer, dom.isBlock); + if (!rng.collapsed) { + isDelete = true; + } - // On delete clone the root span of the next block element - if (isDelete) - blockElm = dom.getNext(blockElm, dom.isBlock); + container = rng[(isDelete ? 'start' : 'end') + 'Container']; + offset = rng[(isDelete ? 'start' : 'end') + 'Offset']; - // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace - if (blockElm) { - node = blockElm.firstChild; + if (container.nodeType == 3) { + blockElm = dom.getParent(rng.startContainer, dom.isBlock); - // Ignore empty text nodes - while (node && node.nodeType == 3 && node.nodeValue.length === 0) - node = node.nextSibling; + // On delete clone the root span of the next block element + if (isDelete) { + blockElm = dom.getNext(blockElm, dom.isBlock); + } - if (node && node.nodeName === 'SPAN') { - clonedSpan = node.cloneNode(false); + if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) { + // Wrap children of block in a EM and let WebKit stick is + // runtime styles junk into that EM + wrapperElm = dom.create('em', {'id': '__mceDel'}); + + each(tinymce.grep(blockElm.childNodes), function(node) { + wrapperElm.appendChild(node); + }); + + blockElm.appendChild(wrapperElm); } } // Do the backspace/delete action + rng = dom.createRng(); + rng.setStart(tmpRng[0], tmpRng[1]); + rng.setEnd(tmpRng[2], tmpRng[3]); + selection.setRng(rng); editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); - // Find all odd apple-style-spans - blockElm = dom.getParent(rng.startContainer, dom.isBlock); - tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { - var bm = selection.getBookmark(); + // Remove temp wrapper element + if (wrapperElm) { + bookmark = selection.getBookmark(); - if (clonedSpan) { - dom.replace(clonedSpan.cloneNode(false), span, true); - } else { - dom.remove(span, true); + while (elm = dom.get('__mceDel')) { + dom.remove(elm, true); } - // Restore the selection - selection.moveToBookmark(bm); - }); - }; + selection.moveToBookmark(bookmark); + } + } editor.onKeyDown.add(function(editor, e) { var isDelete; isDelete = e.keyCode == DELETE; - if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { + if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { e.preventDefault(); removeMergedFormatSpans(isDelete); } @@ -1189,7 +1221,7 @@ var keyCode = e.keyCode, isCollapsed; // Empty the editor if it's needed for example backspace at <p><b>|</b></p> - if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { + if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { isCollapsed = editor.selection.isCollapsed(); // Selection is collapsed but the editor isn't empty @@ -1217,7 +1249,7 @@ function selectAll() { editor.onKeyDown.add(function(editor, e) { - if (e.keyCode == 65 && VK.metaKeyPressed(e)) { + if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { e.preventDefault(); editor.execCommand('SelectAll'); } @@ -1243,7 +1275,7 @@ function removeHrOnBackspace() { editor.onKeyDown.add(function(editor, e) { - if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { var node = selection.getNode(); var previousSibling = node.previousSibling; @@ -1262,7 +1294,7 @@ // wouldn't get proper focus if the user clicked on the HTML element if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 editor.onMouseDown.add(function(editor, e) { - if (e.target.nodeName === "HTML") { + if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { var body = editor.getBody(); // Blur the body it's focused but not correctly focused @@ -1306,7 +1338,7 @@ if (target !== editor.getBody()) { dom.setAttrib(target, "style", null); - tinymce.each(template, function(attr) { + each(template, function(attr) { target.setAttributeNode(attr.cloneNode(true)); }); } @@ -1325,7 +1357,7 @@ editor.onKeyPress.add(function(editor, e) { var applyAttributes; - if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { applyAttributes = getAttributeApplyFunction(); editor.getDoc().execCommand('delete', false, null); applyAttributes(); @@ -1337,7 +1369,7 @@ dom.bind(editor.getDoc(), 'cut', function(e) { var applyAttributes; - if (isSelectionAcrossElements()) { + if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { applyAttributes = getAttributeApplyFunction(); editor.onKeyUp.addToTop(blockEvent); @@ -1376,7 +1408,7 @@ function disableBackspaceIntoATable() { editor.onKeyDown.add(function(editor, e) { - if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { var previousSibling = selection.getNode().previousSibling; if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { @@ -1400,7 +1432,7 @@ dom.addClass(editor.getBody(), 'mceHideBrInPre'); // Adds a \n before all BR elements in PRE to get them visual - editor.parser.addNodeFilter('pre', function(nodes, name) { + parser.addNodeFilter('pre', function(nodes, name) { var i = nodes.length, brNodes, j, brElm, sibling; while (i--) { @@ -1421,7 +1453,7 @@ }); // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible - editor.serializer.addNodeFilter('pre', function(nodes, name) { + serializer.addNodeFilter('pre', function(nodes, name) { var i = nodes.length, brNodes, j, brElm, sibling; while (i--) { @@ -1464,7 +1496,7 @@ var isDelete, rng, container, offset, brElm, sibling, collapsed; isDelete = e.keyCode == DELETE; - if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { + if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { rng = selection.getRng(); container = rng.startContainer; offset = rng.startOffset; @@ -1473,6 +1505,12 @@ // Override delete if the start container is a text node and is at the beginning of text or // just before/after the last character to be deleted in collapsed mode if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { + // Edge case when deleting <p><b><img> |x</b></p> + sibling = container.previousSibling; + if (sibling && sibling.nodeName == "IMG") { + return; + } + nonEmptyElements = editor.schema.getNonEmptyElements(); // Prevent default logic since it's broken @@ -1504,7 +1542,7 @@ editor.onKeyDown.add(function(editor, e) { var rng, container, offset, root, parent; - if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { + if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { return; } @@ -1528,10 +1566,10 @@ editor.formatter.toggle('blockquote', null, parent); // Move the caret to the beginning of container + rng = dom.createRng(); rng.setStart(container, 0); rng.setEnd(container, 0); selection.setRng(rng); - selection.collapse(false); } }); }; @@ -1556,7 +1594,7 @@ function addBrAfterLastLinks() { function fixLinks(editor, o) { - tinymce.each(dom.select('a'), function(node) { + each(dom.select('a'), function(node) { var parentNode = node.parentNode, root = dom.getRoot(); if (parentNode.lastChild === node) { @@ -1606,7 +1644,7 @@ editor.onKeyDown.add(function(editor, e) { var rng; - if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { + if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { rng = editor.getDoc().selection.createRange(); if (rng && rng.item) { e.preventDefault(); @@ -1624,7 +1662,7 @@ // IE10+ if (getDocumentMode() >= 10) { emptyBlocksCSS = ''; - tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { + each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; }); @@ -1741,7 +1779,7 @@ width = height = 0; } - tinymce.each(resizeHandles, function(handle, name) { + each(resizeHandles, function(handle, name) { var handleElm; // Get existing or render resize handle @@ -1838,7 +1876,7 @@ var controlElm = dom.getParent(selection.getNode(), 'table,img'); // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v - tinymce.each(dom.select('img[data-mce-selected]'), function(img) { + each(dom.select('img[data-mce-selected]'), function(img) { img.removeAttribute('data-mce-selected'); }); @@ -1861,6 +1899,57 @@ while (i--) { nodes[i].attr(name, null); + } + }); + } + + function keepNoScriptContents() { + if (getDocumentMode() < 9) { + parser.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node, textNode; + + while (i--) { + node = nodes[i]; + textNode = node.firstChild; + + if (textNode) { + node.attr('data-mce-innertext', textNode.value); + } + } + }); + + serializer.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node, textNode, value; + + while (i--) { + node = nodes[i]; + textNode = nodes[i].firstChild; + + if (textNode) { + textNode.value = tinymce.html.Entities.decode(textNode.value); + } else { + // Old IE can't retain noscript value so an attribute is used to store it + value = node.attributes.map['data-mce-innertext']; + if (value) { + node.attr('data-mce-innertext', null); + textNode = new tinymce.html.Node('#text', 3); + textNode.value = value; + textNode.raw = true; + node.append(textNode); + } + } + } + }); + } + } + + function bodyHeight() { + editor.contentStyles.push('body {min-height: 100px}'); + editor.onClick.add(function(ed, e) { + if (e.target.nodeName == 'HTML') { + editor.execCommand('SelectAll'); + editor.selection.collapse(true); + editor.nodeChanged(); } }); } @@ -1888,17 +1977,23 @@ } // IE - if (tinymce.isIE) { + if (tinymce.isIE && !tinymce.isIE11) { removeHrOnBackspace(); ensureBodyHasRoleApplication(); addNewLinesBeforeBrInPre(); removePreSerializedStylesWhenSelectingControls(); deleteControlItemOnBackSpace(); renderEmptyBlocksFix(); + keepNoScriptContents(); + } + + // IE 11+ + if (tinymce.isIE11) { + bodyHeight(); } // Gecko - if (tinymce.isGecko) { + if (tinymce.isGecko && !tinymce.isIE11) { removeHrOnBackspace(); focusBody(); removeStylesWhenDeletingAccrossBlockElements(); @@ -2134,6 +2229,12 @@ function compress(prefix, suffix) { var top, right, bottom, left; + + // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> + // So lets asume it shouldn't be there + if (styles['border-image'] === 'none') { + delete styles['border-image']; + } // Get values and check it it needs compressing top = styles[prefix + '-top' + suffix]; @@ -2449,7 +2550,7 @@ 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 'fieldset[A|disabled|form|name][C|legend]' + 'label[A|form|for][B]' + - 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + + 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + @@ -2653,14 +2754,15 @@ } // Setup map objects - whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); + whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea'); selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); - nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); - blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + - 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + - 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); + nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); + textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + + 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); + blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + + 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap); // Converts a wildcard expression string to a regexp for example *a will become /.*a/. function patternToRegExp(str) { @@ -2834,8 +2936,15 @@ customElementsMap[name] = cloneName; // If it's not marked as inline then add it to valid block elements - if (!inline) + if (!inline) { + blockElementsMap[name.toUpperCase()] = {}; blockElementsMap[name] = {}; + } + + // Add elements clone if needed + if (!elements[name]) { + elements[name] = elements[cloneName]; + } // Add custom elements at span/div positions each(children, function(element, child) { @@ -2960,6 +3069,10 @@ return blockElementsMap; }; + self.getTextBlockElements = function() { + return textBlockElementsMap; + }; + self.getShortEndedElements = function() { return shortEndedElementsMap; }; @@ -3025,6 +3138,8 @@ self.addCustomElements = addCustomElements; self.addValidChildren = addValidChildren; + + self.elements = elements; }; })(tinymce); @@ -3123,10 +3238,10 @@ '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI '(?:\\/([^>]+)>)|' + // End element - '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element + '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element ')', 'g'); - attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; specialElements = { 'script' : /<\/script[^>]*>/gi, 'style' : /<\/style[^>]*>/gi, @@ -3609,7 +3724,7 @@ i = node.attributes.length; while (i--) { name = node.attributes[i].name; - if (name === "name" || name.indexOf('data-') === 0) + if (name === "name" || name.indexOf('data-mce-') === 0) return false; } } @@ -3665,17 +3780,40 @@ function fixInvalidChildren(nodes) { var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, - childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); nonEmptyElements = schema.getNonEmptyElements(); + textBlockElements = schema.getTextBlockElements(); for (ni = 0; ni < nodes.length; ni++) { node = nodes[ni]; - // Already removed - if (!node.parent) + // Already removed or fixed + if (!node.parent || node.fixed) continue; + + // If the invalid element is a text block and the text block is within a parent LI element + // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office + if (textBlockElements[node.name] && node.parent.name == 'li') { + // Move sibling text blocks after LI element + sibling = node.next; + while (sibling) { + if (textBlockElements[sibling.name]) { + sibling.name = 'li'; + sibling.fixed = true; + node.parent.insert(sibling, node.parent); + } else { + break; + } + + sibling = sibling.next; + } + + // Unwrap current text block + node.unwrap(node); + continue; + } // Get list of all parent nodes until we find a valid parent to stick the child into parents = [node]; @@ -4054,7 +4192,8 @@ } // Trim start white space - textNode = node.prev; + // Removed due to: #5424 + /*textNode = node.prev; if (textNode && textNode.type === 3) { text = textNode.value.replace(startWhiteSpaceRegExp, ''); @@ -4062,7 +4201,7 @@ textNode.value = text; else textNode.remove(); - } + }*/ } // Check if we exited a whitespace preserved element @@ -4570,6 +4709,12 @@ event_utils.domLoaded = true; callback(event); } + } + + // Page already loaded then fire it directly + if (doc.readyState == "complete") { + readyHandler(); + return; } // Use W3C method @@ -5115,6 +5260,11 @@ blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; t.isBlock = function(node) { + // Fix for #5446 + if (!node) { + return false; + } + // This function is called in module pattern style since it might be executed with the wrong this scope var type = node.nodeType; @@ -5129,7 +5279,7 @@ fixDoc: function(doc) { var settings = this.settings, name; - if (isIE && settings.schema) { + if (isIE && !tinymce.isIE11 && settings.schema) { // Add missing HTML 4/5 elements to IE ('abbr article aside audio canvas ' + 'details figcaption figure footer ' + @@ -5150,7 +5300,7 @@ var self = this, clone, doc; // TODO: Add feature detection here in the future - if (!isIE || node.nodeType !== 1 || deep) { + if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) { return node.cloneNode(deep); } @@ -5423,7 +5573,7 @@ switch (na) { case 'opacity': // IE specific opacity - if (isIE) { + if (isIE && ! tinymce.isIE11) { s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; if (!n.currentStyle || !n.currentStyle.hasLayout) @@ -5435,7 +5585,7 @@ break; case 'float': - isIE ? s.styleFloat = v : s.cssFloat = v; + (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v; break; default: @@ -5801,7 +5951,7 @@ // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading // It's ugly but it seems to work fine. - if (isIE && d.documentMode && d.recalc) { + if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) { link.onload = function() { if (d.recalc) d.recalc(); @@ -6120,7 +6270,12 @@ // Import case 3: - addClasses(r.styleSheet); + try { + addClasses(r.styleSheet); + } catch (ex) { + // Ignore + } + break; } }); @@ -9222,7 +9377,7 @@ if (!t.win.getSelection) t.tridentSel = new tinymce.dom.TridentSelection(t); - if (tinymce.isIE && dom.boxModel) + if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel) this._fixIESelection(); // Prevent leaks @@ -9514,8 +9669,20 @@ } // Handle simple range - if (type) - return {rng : t.getRng()}; + if (type) { + rng = t.getRng(); + + if (rng.setStart) { + rng = { + startContainer: rng.startContainer, + startOffset: rng.startOffset, + endContainer: rng.endContainer, + endOffset: rng.endOffset + }; + } + + return {rng : rng}; + } rng = t.getRng(); id = dom.uniqueId(); @@ -9581,7 +9748,7 @@ }, moveToBookmark : function(bookmark) { - var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; + var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset; function setEndPoint(start) { var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; @@ -9711,8 +9878,24 @@ } } else if (bookmark.name) { t.select(dom.select(bookmark.name)[bookmark.index]); - } else if (bookmark.rng) - t.setRng(bookmark.rng); + } else if (bookmark.rng) { + rng = bookmark.rng; + + if (rng.startContainer) { + rng2 = t.dom.createRng(); + + try { + rng2.setStart(rng.startContainer, rng.startOffset); + rng2.setEnd(rng.endContainer, rng.endOffset); + } catch (e) { + // Might fail with index error + } + + rng = rng2; + } + + t.setRng(rng); + } } }, @@ -9811,7 +9994,7 @@ } // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet - if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { + if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) { elm = doc.selection.createRange().item(0); rng = doc.createRange(); rng.setStartBefore(elm); @@ -10224,6 +10407,16 @@ return self; }, + scrollIntoView: function(elm) { + var y, viewPort, self = this, dom = self.dom; + + viewPort = dom.getViewPort(self.editor.getWin()); + y = dom.getPos(elm).y; + if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { + self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); + } + }, + destroy : function(manual) { var self = this; @@ -10393,6 +10586,18 @@ while (i--) { nodes[i].attr(name, null); + } + }); + + htmlParser.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i].firstChild; + + if (node) { + node.value = tinymce.html.Entities.decode(node.value); + } } }); @@ -10654,7 +10859,7 @@ // Add onload listener for non IE browsers since IE9 // fires onload event before the script is parsed and executed - if (!tinymce.isIE) + if (!tinymce.isIE || tinymce.isIE11) elm.onload = done; // Add onerror event will get fired on some browsers but not all of them @@ -11067,18 +11272,22 @@ switch (evt.keyCode) { case DOM_VK_LEFT: if (enableLeftRight) t.moveFocus(-1); + Event.cancel(evt); break; case DOM_VK_RIGHT: if (enableLeftRight) t.moveFocus(1); + Event.cancel(evt); break; case DOM_VK_UP: if (enableUpDown) t.moveFocus(-1); + Event.cancel(evt); break; case DOM_VK_DOWN: if (enableUpDown) t.moveFocus(1); + Event.cancel(evt); break; case DOM_VK_ESCAPE: @@ -11775,7 +11984,7 @@ else h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); - h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; + h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; h += '</a>'; return h; }, @@ -11800,9 +12009,11 @@ return s.onclick.call(s.scope, e); } }); - tinymce.dom.Event.add(t.id, 'keyup', function(e) { - if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) + tinymce.dom.Event.add(t.id, 'keydown', function(e) { + if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) { + tinymce.dom.Event.cancel(e); return s.onclick.call(s.scope, e); + } }); } }); @@ -12215,7 +12426,7 @@ // Accessibility keyhandler Event.add(t.id, 'keydown', function(e) { - var bf; + var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; Event.remove(t.id, 'change', ch); changeListenerAdded = false; @@ -12227,14 +12438,12 @@ Event.remove(t.id, 'blur', bf); }); - //prevent default left and right keys on chrome - so that the keyboard navigation is used. - if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { - return Event.prevent(e); - } - - if (e.keyCode == 13 || e.keyCode == 32) { + if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) { onChange(e); return Event.cancel(e); + } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) { + // allow native implementation (navigate select element options) + e.stopImmediatePropagation(); } }); @@ -13037,6 +13246,9 @@ if (id === undef) return this.editors; + if (!this.editors.hasOwnProperty(id)) + return undef; + return this.editors[id]; }, @@ -13120,7 +13332,7 @@ ed.render(); // Fix IE memory leaks - if (tinymce.isIE) { + if (tinymce.isIE && ! tinymce.isIE11) { w.attachEvent('onunload', clr); } @@ -13518,9 +13730,15 @@ // Store away the selection when it's changed to it can be restored later with a editor.focus() call if (isIE) { t.onInit.add(function(ed) { - ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { - ed.lastIERng = ed.selection.getRng(); + ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() { + ed.bookmark = ed.selection.getBookmark(1); }); + }); + + t.onNodeChange.add(function(ed) { + if (document.activeElement.id == ed.id + "_ifr") { + ed.bookmark = ed.selection.getBookmark(1); + } }); } } @@ -13558,10 +13776,12 @@ t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. - if (s.ie7_compat) - t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; - else - t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; + if (tinymce.isIE8) { + if (s.ie7_compat) + t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; + else + t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; + } t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; @@ -13839,8 +14059,9 @@ var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; if (!skip_focus) { - if (self.lastIERng) { - selection.setRng(self.lastIERng); + if (self.bookmark) { + selection.moveToBookmark(self.bookmark); + self.bookmark = null; } // Get selected control element @@ -13861,7 +14082,7 @@ body = self.getBody(); // Check for setActive since it doesn't scroll to the element - if (body.setActive) { + if (body.setActive && ! tinymce.isIE11) { body.setActive(); } else { body.focus(); @@ -14189,6 +14410,8 @@ // We must save before we hide so Safari doesn't crash self.save(); + + // defer the call to hide to prevent an IE9 crash #4921 DOM.hide(self.getContainer()); DOM.setStyle(self.id, 'display', self.orgDisplay); }, @@ -14468,11 +14691,19 @@ }, remove : function() { - var self = this, elm = self.getContainer(); + var self = this, elm = self.getContainer(), doc = self.getDoc(); if (!self.removed) { self.removed = 1; // Cancels post remove event execution - self.hide(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && doc) + doc.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + self.save(); + + DOM.setStyle(self.id, 'display', self.orgDisplay); // Don't clear the window or document if content editable // is enabled since other instances might still be present @@ -15465,7 +15696,7 @@ // Add undo level on save contents, drag end and blur/focusout editor.onSaveContent.add(addNonTypingUndoLevel); editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); - editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { + editor.dom.bind(editor.getBody(), 'focusout', function(e) { if (!editor.removed && self.typing) { addNonTypingUndoLevel(); } @@ -16248,6 +16479,7 @@ TreeWalker = tinymce.dom.TreeWalker, rangeUtils = new tinymce.dom.RangeUtils(dom), isValid = ed.schema.isValidChild, + isArray = tinymce.isArray, isBlock = dom.isBlock, forcedRootBlock = ed.settings.forced_root_block, nodeIndex = dom.nodeIndex, @@ -16259,9 +16491,13 @@ undef, getContentEditable = dom.getContentEditable; - function isArray(obj) { - return obj instanceof Array; - }; + function isTextBlock(name) { + if (name.nodeType) { + name = name.nodeName; + } + + return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; + } function getParents(node, selector) { return dom.getParents(node, selector, dom.getRoot()); @@ -16621,7 +16857,7 @@ // Is it valid to wrap this item if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && - !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) { // Start wrapping if (!currentWrapElm) { // Wrap the node @@ -16826,6 +17062,11 @@ // Merges the styles for each node function process(node) { var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; + + // Skip on text nodes as they have neither format to remove nor children + if (node.nodeType === 3) { + return; + } // Node has a contentEditable value if (node.nodeType === 1 && getContentEditable(node)) { @@ -17320,6 +17561,10 @@ siblingName = start ? 'previousSibling' : 'nextSibling'; root = dom.getRoot(); + function isBogusBr(node) { + return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; + }; + // If it's a text node and the offset is inside the text if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { @@ -17334,7 +17579,7 @@ // Walk left/right for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { - if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { return parent; } } @@ -17493,7 +17738,7 @@ // Expand to first wrappable block element or any block element if (!node) - node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock); // Exclude inner lists from wrapping if (node && format[0].wrapper) @@ -17892,10 +18137,6 @@ return next; }; - function isTextBlock(name) { - return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); - }; - function getContainer(rng, start) { var container, offset, lastIdx, walker; @@ -18131,11 +18372,23 @@ node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); node = node.firstChild; - // Insert caret container after the formated node - dom.insertAfter(caretContainer, formatNode); + var block = dom.getParent(formatNode, isTextBlock); + + if (block && dom.isEmpty(block)) { + // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> + formatNode.parentNode.replaceChild(caretContainer, formatNode); + } else { + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + } // Move selection to text node selection.setCursorLocation(node, 1); + + // If the formatNode is empty, we can remove it safely. + if (dom.isEmpty(formatNode)) { + dom.remove(formatNode); + } } }; @@ -18325,7 +18578,7 @@ function renderBlockOnIE(block) { var oldRng; - if (tinymce.isIE && dom.isBlock(block)) { + if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) { oldRng = selection.getRng(); block.appendChild(dom.create('span', null, '\u00a0')); selection.select(block); @@ -18440,6 +18693,11 @@ if (settings.keep_styles !== false) { do { if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + // Never clone a caret containers + if (node.id == '_mce_caret') { + continue; + } + clonedNode = node.cloneNode(false); dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique @@ -18455,8 +18713,8 @@ } // BR is needed in empty blocks on non IE browsers - if (!tinymce.isIE) { - caretNode.innerHTML = '<br>'; + if (!tinymce.isIE || tinymce.isIE11) { + caretNode.innerHTML = '<br data-mce-bogus="1">'; } return block; @@ -18617,26 +18875,24 @@ undoManager.add(); }; - // Walks the parent block to the right and look for BR elements - function hasRightSideBr() { + // Walks the parent block to the right and look for any contents + function hasRightSideContent() { var walker = new TreeWalker(container, parentBlock), node; - while (node = walker.current()) { - if (node.nodeName == 'BR') { + while (node = walker.next()) { + if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { return true; } - - node = walker.next(); } } - + // Inserts a BR element if the forced_root_block option is set to false or empty string function insertBr() { - var brElm, extraBr; + var brElm, extraBr, marker; if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { // Insert extra BR element at the end block elements - if (!tinymce.isIE && !hasRightSideBr()) { + if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) { brElm = dom.create('br'); rng.insertNode(brElm); rng.setStartAfter(brElm); @@ -18649,9 +18905,15 @@ rng.insertNode(brElm); // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it - if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { + if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); } + + // Insert temp marker and scroll to that + marker = dom.create('span', {}, ' '); + brElm.parentNode.insertBefore(marker, brElm); + selection.scrollIntoView(marker); + dom.remove(marker); if (!extraBr) { rng.setStartAfter(brElm); @@ -18697,7 +18959,7 @@ var lastChild; // IE will render the blocks correctly other browsers needs a BR - if (!tinymce.isIE) { + if (!tinymce.isIE || tinymce.isIE11) { block.normalize(); // Remove empty text nodes that got left behind by the extract // Check if the block is empty or contains a floated last child @@ -18772,6 +19034,12 @@ parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 + // Enter inside block contained within a LI then split or insert before/after LI + if (containerBlockName == 'LI' && !evt.ctrlKey) { + parentBlock = containerBlock; + parentBlockName = containerBlockName; + } + // Handle enter in LI if (parentBlockName == 'LI') { if (!newBlockName && shiftKey) { -- Gitblit v1.9.1