var tinymce = {\r
majorVersion : '3',\r
\r
- minorVersion : '5.8',\r
+ minorVersion : '5.11',\r
\r
- releaseDate : '2012-11-20',\r
+ releaseDate : '2014-05-08',\r
\r
_init : function() {\r
var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;\r
\r
+ t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1);\r
+\r
t.isOpera = win.opera && opera.buildNumber;\r
\r
t.isWebKit = /WebKit/.test(ua);\r
\r
- t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);\r
+ t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11;\r
\r
t.isIE6 = t.isIE && /MSIE [56]/.test(ua);\r
\r
\r
t.isIE9 = t.isIE && /MSIE [9]/.test(ua);\r
\r
- t.isGecko = !t.isWebKit && /Gecko/.test(ua);\r
+ t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);\r
\r
t.isMac = ua.indexOf('Mac') != -1;\r
\r
\r
function cleanupStylesWhenDeleting() {\r
function removeMergedFormatSpans(isDelete) {\r
- var rng, blockElm, node, clonedSpan;\r
+ var rng, blockElm, wrapperElm, bookmark, container, offset, elm;\r
\r
- rng = selection.getRng();\r
+ function isAtStartOrEndOfElm() {\r
+ if (container.nodeType == 3) {\r
+ if (isDelete && offset == container.length) {\r
+ return true;\r
+ }\r
+\r
+ if (!isDelete && offset === 0) {\r
+ return true;\r
+ }\r
+ }\r
+ }\r
\r
- // Find root block\r
- blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
+ rng = selection.getRng();\r
+ var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];\r
\r
- // On delete clone the root span of the next block element\r
- if (isDelete) {\r
- blockElm = dom.getNext(blockElm, dom.isBlock);\r
+ if (!rng.collapsed) {\r
+ isDelete = true;\r
}\r
\r
- // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace\r
- if (blockElm) {\r
- node = blockElm.firstChild;\r
+ container = rng[(isDelete ? 'start' : 'end') + 'Container'];\r
+ offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];\r
\r
- // Ignore empty text nodes\r
- while (node && node.nodeType == 3 && node.nodeValue.length === 0) {\r
- node = node.nextSibling;\r
+ if (container.nodeType == 3) {\r
+ blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
+\r
+ // On delete clone the root span of the next block element\r
+ if (isDelete) {\r
+ blockElm = dom.getNext(blockElm, dom.isBlock);\r
}\r
\r
- if (node && node.nodeName === 'SPAN') {\r
- clonedSpan = node.cloneNode(false);\r
+ if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {\r
+ // Wrap children of block in a EM and let WebKit stick is\r
+ // runtime styles junk into that EM\r
+ wrapperElm = dom.create('em', {'id': '__mceDel'});\r
+\r
+ each(tinymce.grep(blockElm.childNodes), function(node) {\r
+ wrapperElm.appendChild(node);\r
+ });\r
+\r
+ blockElm.appendChild(wrapperElm);\r
}\r
}\r
\r
- each(dom.select('span', blockElm), function(span) {\r
- span.setAttribute('data-mce-mark', '1');\r
- });\r
-\r
// Do the backspace/delete action\r
+ rng = dom.createRng();\r
+ rng.setStart(tmpRng[0], tmpRng[1]);\r
+ rng.setEnd(tmpRng[2], tmpRng[3]);\r
+ selection.setRng(rng);\r
editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
\r
- // Find all odd apple-style-spans\r
- blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
- each(dom.select('span', blockElm), function(span) {\r
- var bm = selection.getBookmark();\r
+ // Remove temp wrapper element\r
+ if (wrapperElm) {\r
+ bookmark = selection.getBookmark();\r
\r
- if (clonedSpan) {\r
- dom.replace(clonedSpan.cloneNode(false), span, true);\r
- } else if (!span.getAttribute('data-mce-mark')) {\r
- dom.remove(span, true);\r
- } else {\r
- span.removeAttribute('data-mce-mark');\r
+ while (elm = dom.get('__mceDel')) {\r
+ dom.remove(elm, true);\r
}\r
\r
- // Restore the selection\r
- selection.moveToBookmark(bm);\r
- });\r
+ selection.moveToBookmark(bookmark);\r
+ }\r
}\r
\r
editor.onKeyDown.add(function(editor, e) {\r
// Override delete if the start container is a text node and is at the beginning of text or\r
// just before/after the last character to be deleted in collapsed mode\r
if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {\r
+ // Edge case when deleting <p><b><img> |x</b></p>\r
+ sibling = container.previousSibling;\r
+ if (sibling && sibling.nodeName == "IMG") {\r
+ return;\r
+ }\r
+\r
nonEmptyElements = editor.schema.getNonEmptyElements();\r
\r
// Prevent default logic since it's broken\r
}\r
}\r
\r
+ function bodyHeight() {\r
+ editor.contentStyles.push('body {min-height: 100px}');\r
+ editor.onClick.add(function(ed, e) {\r
+ if (e.target.nodeName == 'HTML') {\r
+ editor.execCommand('SelectAll');\r
+ editor.selection.collapse(true);\r
+ editor.nodeChanged();\r
+ }\r
+ });\r
+ }\r
+\r
+ function fixControlSelection() {\r
+ editor.onInit.add(function() {\r
+ var selectedRng;\r
+\r
+ editor.getBody().addEventListener('mscontrolselect', function(e) {\r
+ setTimeout(function() {\r
+ if (editor.selection.getNode() != e.target) {\r
+ selectedRng = editor.selection.getRng();\r
+ selection.fakeRng = editor.dom.createRng();\r
+ selection.fakeRng.setStartBefore(e.target);\r
+ selection.fakeRng.setEndAfter(e.target);\r
+ }\r
+ }, 0);\r
+ }, false);\r
+\r
+ editor.getDoc().addEventListener('selectionchange', function(e) {\r
+ if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) {\r
+ selection.fakeRng = selectedRng = null;\r
+ }\r
+ }, false);\r
+ });\r
+ }\r
+\r
// All browsers\r
disableBackspaceIntoATable();\r
removeBlockQuoteOnBackSpace();\r
}\r
\r
// IE\r
- if (tinymce.isIE) {\r
+ if (tinymce.isIE && !tinymce.isIE11) {\r
removeHrOnBackspace();\r
ensureBodyHasRoleApplication();\r
addNewLinesBeforeBrInPre();\r
keepNoScriptContents();\r
}\r
\r
+ // IE 11+\r
+ if (tinymce.isIE11) {\r
+ bodyHeight();\r
+ fixControlSelection();\r
+ }\r
+\r
// Gecko\r
- if (tinymce.isGecko) {\r
+ if (tinymce.isGecko && !tinymce.isIE11) {\r
removeHrOnBackspace();\r
focusBody();\r
removeStylesWhenDeletingAccrossBlockElements();\r
function compress(prefix, suffix) {\r
var top, right, bottom, left;\r
\r
+ // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>\r
+ // So lets asume it shouldn't be there\r
+ if (styles['border-image'] === 'none') {\r
+ delete styles['border-image'];\r
+ }\r
+\r
// Get values and check it it needs compressing\r
top = styles[prefix + '-top' + suffix];\r
if (!top)\r
selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');\r
shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');\r
boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');\r
- nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);\r
+ nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);\r
textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + \r
'blockquote center dir fieldset header footer article section hgroup aside nav figure');\r
blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + \r
fixDoc: function(doc) {\r
var settings = this.settings, name;\r
\r
- if (isIE && settings.schema) {\r
+ if (isIE && !tinymce.isIE11 && settings.schema) {\r
// Add missing HTML 4/5 elements to IE\r
('abbr article aside audio canvas ' +\r
'details figcaption figure footer ' +\r
var self = this, clone, doc;\r
\r
// TODO: Add feature detection here in the future\r
- if (!isIE || node.nodeType !== 1 || deep) {\r
+ if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) {\r
return node.cloneNode(deep);\r
}\r
\r
switch (na) {\r
case 'opacity':\r
// IE specific opacity\r
- if (isIE) {\r
+ if (isIE && ! tinymce.isIE11) {\r
s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";\r
\r
if (!n.currentStyle || !n.currentStyle.hasLayout)\r
break;\r
\r
case 'float':\r
- isIE ? s.styleFloat = v : s.cssFloat = v;\r
+ (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v;\r
break;\r
\r
default:\r
// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug\r
// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading\r
// It's ugly but it seems to work fine.\r
- if (isIE && d.documentMode && d.recalc) {\r
+ if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) {\r
link.onload = function() {\r
if (d.recalc)\r
d.recalc();\r
\r
// Import\r
case 3:\r
- addClasses(r.styleSheet);\r
+ try {\r
+ addClasses(r.styleSheet);\r
+ } catch (ex) {\r
+ // Ignore\r
+ }\r
+\r
break;\r
}\r
});\r
if (!t.win.getSelection)\r
t.tridentSel = new tinymce.dom.TridentSelection(t);\r
\r
- if (tinymce.isIE && dom.boxModel)\r
+ if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel)\r
this._fixIESelection();\r
\r
// Prevent leaks\r
}\r
\r
// Handle simple range\r
- if (type)\r
- return {rng : t.getRng()};\r
+ if (type) {\r
+ rng = t.getRng();\r
+\r
+ if (rng.setStart) {\r
+ rng = {\r
+ startContainer: rng.startContainer,\r
+ startOffset: rng.startOffset,\r
+ endContainer: rng.endContainer,\r
+ endOffset: rng.endOffset\r
+ };\r
+ }\r
+\r
+ return {rng : rng};\r
+ }\r
\r
rng = t.getRng();\r
id = dom.uniqueId();\r
},\r
\r
moveToBookmark : function(bookmark) {\r
- var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;\r
+ var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset;\r
\r
function setEndPoint(start) {\r
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;\r
}\r
} else if (bookmark.name) {\r
t.select(dom.select(bookmark.name)[bookmark.index]);\r
- } else if (bookmark.rng)\r
- t.setRng(bookmark.rng);\r
+ } else if (bookmark.rng) {\r
+ rng = bookmark.rng;\r
+\r
+ if (rng.startContainer) {\r
+ rng2 = t.dom.createRng();\r
+\r
+ try {\r
+ rng2.setStart(rng.startContainer, rng.startOffset);\r
+ rng2.setEnd(rng.endContainer, rng.endOffset);\r
+ } catch (e) {\r
+ // Might fail with index error\r
+ }\r
+\r
+ rng = rng2;\r
+ }\r
+\r
+ t.setRng(rng);\r
+ }\r
}\r
},\r
\r
getRng : function(w3c) {\r
var self = this, selection, rng, elm, doc = self.win.document;\r
\r
+ // Workaround for IE 11 not being able to select images properly see #6613 see quirk fix\r
+ if (self.fakeRng) {\r
+ return self.fakeRng;\r
+ }\r
+\r
// Found tridentSel object then we need to use that one\r
if (w3c && self.tridentSel) {\r
return self.tridentSel.getRangeAt(0);\r
}\r
\r
// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet\r
- if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {\r
+ if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) {\r
elm = doc.selection.createRange().item(0);\r
rng = doc.createRange();\r
rng.setStartBefore(elm);\r
\r
// Add onload listener for non IE browsers since IE9\r
// fires onload event before the script is parsed and executed\r
- if (!tinymce.isIE)\r
+ if (!tinymce.isIE || tinymce.isIE11)\r
elm.onload = done;\r
\r
// Add onerror event will get fired on some browsers but not all of them\r
switch (evt.keyCode) {\r
case DOM_VK_LEFT:\r
if (enableLeftRight) t.moveFocus(-1);\r
+ Event.cancel(evt);\r
break;\r
\r
case DOM_VK_RIGHT:\r
if (enableLeftRight) t.moveFocus(1);\r
+ Event.cancel(evt);\r
break;\r
\r
case DOM_VK_UP:\r
if (enableUpDown) t.moveFocus(-1);\r
+ Event.cancel(evt);\r
break;\r
\r
case DOM_VK_DOWN:\r
if (enableUpDown) t.moveFocus(1);\r
+ Event.cancel(evt);\r
break;\r
\r
case DOM_VK_ESCAPE:\r
else\r
h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
\r
- h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; \r
+ h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';\r
h += '</a>';\r
return h;\r
},\r
return s.onclick.call(s.scope, e);\r
}\r
});\r
- tinymce.dom.Event.add(t.id, 'keyup', function(e) {\r
- if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)\r
+ tinymce.dom.Event.add(t.id, 'keydown', function(e) {\r
+ if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) {\r
+ tinymce.dom.Event.cancel(e);\r
return s.onclick.call(s.scope, e);\r
+ }\r
});\r
}\r
});\r
\r
// Accessibility keyhandler\r
Event.add(t.id, 'keydown', function(e) {\r
- var bf;\r
+ 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;\r
\r
Event.remove(t.id, 'change', ch);\r
changeListenerAdded = false;\r
Event.remove(t.id, 'blur', bf);\r
});\r
\r
- //prevent default left and right keys on chrome - so that the keyboard navigation is used.\r
- if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {\r
- return Event.prevent(e);\r
- }\r
- \r
- if (e.keyCode == 13 || e.keyCode == 32) {\r
+ if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) {\r
onChange(e);\r
return Event.cancel(e);\r
+ } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) {\r
+ // allow native implementation (navigate select element options)\r
+ e.stopImmediatePropagation();\r
}\r
});\r
\r
ed.render();\r
\r
// Fix IE memory leaks\r
- if (tinymce.isIE) {\r
+ if (tinymce.isIE && ! tinymce.isIE11) {\r
w.attachEvent('onunload', clr);\r
}\r
\r
// Store away the selection when it's changed to it can be restored later with a editor.focus() call\r
if (isIE) {\r
t.onInit.add(function(ed) {\r
- ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {\r
- ed.lastIERng = ed.selection.getRng();\r
+ ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() {\r
+ ed.bookmark = ed.selection.getBookmark(1);\r
});\r
});\r
+\r
+ t.onNodeChange.add(function(ed) {\r
+ if (document.activeElement.id == ed.id + "_ifr") {\r
+ ed.bookmark = ed.selection.getBookmark(1);\r
+ }\r
+ });\r
}\r
}\r
\r
var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;\r
\r
if (!skip_focus) {\r
- if (self.lastIERng) {\r
- selection.setRng(self.lastIERng);\r
+ if (self.bookmark) {\r
+ selection.moveToBookmark(self.bookmark);\r
+ self.bookmark = null;\r
}\r
\r
// Get selected control element\r
body = self.getBody();\r
\r
// Check for setActive since it doesn't scroll to the element\r
- if (body.setActive) {\r
+ if (body.setActive && ! tinymce.isIE11) {\r
body.setActive();\r
} else {\r
body.focus();\r
getContentEditable = dom.getContentEditable;\r
\r
function isTextBlock(name) {\r
- return !!ed.schema.getTextBlocks()[name.toLowerCase()];\r
+ if (name.nodeType) {\r
+ name = name.nodeName;\r
+ }\r
+\r
+ return !!ed.schema.getTextBlockElements()[name.toLowerCase()];\r
}\r
\r
function getParents(node, selector) {\r
\r
// Is it valid to wrap this item\r
if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
- !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {\r
+ !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) {\r
// Start wrapping\r
if (!currentWrapElm) {\r
// Wrap the node\r
return next;\r
};\r
\r
- function isTextBlock(name) {\r
- return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);\r
- };\r
-\r
function getContainer(rng, start) {\r
var container, offset, lastIdx, walker;\r
\r
node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));\r
node = node.firstChild;\r
\r
- // Insert caret container after the formated node\r
- dom.insertAfter(caretContainer, formatNode);\r
+ var block = dom.getParent(formatNode, isTextBlock);\r
+\r
+ if (block && dom.isEmpty(block)) {\r
+ // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>\r
+ formatNode.parentNode.replaceChild(caretContainer, formatNode);\r
+ } else {\r
+ // Insert caret container after the formated node\r
+ dom.insertAfter(caretContainer, formatNode);\r
+ }\r
\r
// Move selection to text node\r
selection.setCursorLocation(node, 1);\r
+\r
+ // If the formatNode is empty, we can remove it safely. \r
+ if (dom.isEmpty(formatNode)) {\r
+ dom.remove(formatNode);\r
+ }\r
}\r
};\r
\r
function renderBlockOnIE(block) {\r
var oldRng;\r
\r
- if (tinymce.isIE && dom.isBlock(block)) {\r
+ if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) {\r
oldRng = selection.getRng();\r
block.appendChild(dom.create('span', null, '\u00a0'));\r
selection.select(block);\r
}\r
\r
// BR is needed in empty blocks on non IE browsers\r
- if (!tinymce.isIE) {\r
+ if (!tinymce.isIE || tinymce.isIE11) {\r
caretNode.innerHTML = '<br data-mce-bogus="1">';\r
}\r
\r
undoManager.add();\r
};\r
\r
- // Walks the parent block to the right and look for BR elements\r
- function hasRightSideBr() {\r
+ // Walks the parent block to the right and look for any contents\r
+ function hasRightSideContent() {\r
var walker = new TreeWalker(container, parentBlock), node;\r
\r
- while (node = walker.current()) {\r
- if (node.nodeName == 'BR') {\r
+ while (node = walker.next()) {\r
+ if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {\r
return true;\r
}\r
-\r
- node = walker.next();\r
}\r
}\r
- \r
+\r
// Inserts a BR element if the forced_root_block option is set to false or empty string\r
function insertBr() {\r
var brElm, extraBr, marker;\r
\r
if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {\r
// Insert extra BR element at the end block elements\r
- if (!tinymce.isIE && !hasRightSideBr()) {\r
+ if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) {\r
brElm = dom.create('br');\r
rng.insertNode(brElm);\r
rng.setStartAfter(brElm);\r
rng.insertNode(brElm);\r
\r
// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it\r
- if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {\r
+ if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {\r
brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);\r
}\r
\r
var lastChild;\r
\r
// IE will render the blocks correctly other browsers needs a BR\r
- if (!tinymce.isIE) {\r
+ if (!tinymce.isIE || tinymce.isIE11) {\r
block.normalize(); // Remove empty text nodes that got left behind by the extract\r
\r
// Check if the block is empty or contains a floated last child\r