TinyMCE 3.5.8 update; some fixes

This commit is contained in:
Zach Prezkuta 2013-01-27 14:26:43 -07:00
commit d39a2e8a9d
48 changed files with 2213 additions and 2054 deletions

View file

@ -6,9 +6,9 @@
var tinymce = {
majorVersion : '3',
minorVersion : '5.0.1',
minorVersion : '5.8',
releaseDate : '2012-05-10',
releaseDate : '2012-11-20',
_init : function() {
var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
@ -107,12 +107,16 @@
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) {
var i;
@ -880,12 +884,12 @@ tinymce.create('tinymce.util.Dispatcher', {
((s) ? "; secure" : "");
},
remove : function(n, p) {
var d = new Date();
remove : function(name, path, domain) {
var date = new Date();
d.setTime(d.getTime() - 1000);
date.setTime(date.getTime() - 1000);
this.set(n, '', d, p, d);
this.set(name, '', date, path, domain);
}
});
})();
@ -921,7 +925,7 @@ tinymce.create('tinymce.util.Dispatcher', {
}
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);
@ -1083,12 +1087,18 @@ tinymce.create('static tinymce.util.XHR', {
modifierPressed: function (e) {
return e.shiftKey || e.ctrlKey || e.altKey;
},
metaKeyPressed: function(e) {
// Check if ctrl or meta key is pressed also check if alt is false for Polish users
return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
}
};
})(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 {
@ -1098,6 +1108,16 @@ tinymce.util.Quirks = function(editor) {
}
}
function getDocumentMode() {
var documentMode = editor.getDoc().documentMode;
return documentMode ? documentMode : 6;
};
function isDefaultPrevented(e) {
return e.isDefaultPrevented();
};
function cleanupStylesWhenDeleting() {
function removeMergedFormatSpans(isDelete) {
var rng, blockElm, node, clonedSpan;
@ -1108,46 +1128,54 @@ tinymce.util.Quirks = function(editor) {
blockElm = dom.getParent(rng.startContainer, dom.isBlock);
// On delete clone the root span of the next block element
if (isDelete)
if (isDelete) {
blockElm = dom.getNext(blockElm, dom.isBlock);
}
// 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;
// Ignore empty text nodes
while (node && node.nodeType == 3 && node.nodeValue.length === 0)
while (node && node.nodeType == 3 && node.nodeValue.length === 0) {
node = node.nextSibling;
}
if (node && node.nodeName === 'SPAN') {
clonedSpan = node.cloneNode(false);
}
}
each(dom.select('span', blockElm), function(span) {
span.setAttribute('data-mce-mark', '1');
});
// Do the backspace/delete action
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) {
each(dom.select('span', blockElm), function(span) {
var bm = selection.getBookmark();
if (clonedSpan) {
dom.replace(clonedSpan.cloneNode(false), span, true);
} else {
} else if (!span.getAttribute('data-mce-mark')) {
dom.remove(span, true);
} else {
span.removeAttribute('data-mce-mark');
}
// Restore the selection
selection.moveToBookmark(bm);
});
};
}
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);
}
@ -1157,74 +1185,58 @@ tinymce.util.Quirks = function(editor) {
};
function emptyEditorWhenDeleting() {
function getEndPointNode(rng, start) {
var container, offset, prefix = start ? 'start' : 'end';
function serializeRng(rng) {
var body = dom.create("body");
var contents = rng.cloneContents();
body.appendChild(contents);
return selection.serializer.serialize(body, {format: 'html'});
}
container = rng[prefix + 'Container'];
offset = rng[prefix + 'Offset'];
function allContentsSelected(rng) {
var selection = serializeRng(rng);
// Resolve indexed container
if (container.nodeType == 1 && container.hasChildNodes()) {
container = container.childNodes[Math.min(start ? offset : (offset > 0 ? offset - 1 : 0), container.childNodes.length - 1)]
}
var allRng = dom.createRng();
allRng.selectNode(editor.getBody());
return container;
};
var allSelection = serializeRng(allRng);
return selection === allSelection;
}
function isAtStartEndOfBody(rng, start) {
var container, offset, root, childNode, prefix = start ? 'start' : 'end', isAfter;
editor.onKeyDown.add(function(editor, e) {
var keyCode = e.keyCode, isCollapsed;
container = rng[prefix + 'Container'];
offset = rng[prefix + 'Offset'];
root = dom.getRoot();
// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
isCollapsed = editor.selection.isCollapsed();
// Resolve indexed container
if (container.nodeType == 1) {
isAfter = offset >= container.childNodes.length;
container = getEndPointNode(rng, start);
if (container.nodeType == 3) {
offset = start && !isAfter ? 0 : container.nodeValue.length;
}
}
// Check if start/end is in the middle of text
if (container.nodeType == 3 && ((start && offset > 0) || (!start && offset < container.nodeValue.length))) {
return false;
}
// Walk up the DOM tree to see if the endpoint is at the beginning/end of body
while (container !== root) {
childNode = container.parentNode[start ? 'firstChild' : 'lastChild'];
// If first/last element is a BR then jump to it's sibling in case: <p>x<br></p>
if (childNode.nodeName == "BR") {
childNode = childNode[start ? 'nextSibling' : 'previousSibling'] || childNode;
// Selection is collapsed but the editor isn't empty
if (isCollapsed && !dom.isEmpty(editor.getBody())) {
return;
}
// If the childNode isn't the container node then break in case <p><span>A</span>[X]</p>
if (childNode !== container) {
return false;
// IE deletes all contents correctly when everything is selected
if (tinymce.isIE && !isCollapsed) {
return;
}
container = container.parentNode;
// Selection isn't collapsed but not all the contents is selected
if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
return;
}
// Manually empty the editor
editor.setContent('');
editor.selection.setCursorLocation(editor.getBody(), 0);
editor.nodeChanged();
}
});
};
return true;
};
editor.onKeyDown.addToTop(function(editor, e) {
var rng, keyCode = e.keyCode;
if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
rng = selection.getRng(true);
if (isAtStartEndOfBody(rng, true) && isAtStartEndOfBody(rng, false) &&
(rng.collapsed || dom.findCommonAncestor(getEndPointNode(rng, true), getEndPointNode(rng)) === dom.getRoot())) {
editor.setContent('');
editor.nodeChanged();
e.preventDefault();
}
function selectAll() {
editor.onKeyDown.add(function(editor, e) {
if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
e.preventDefault();
editor.execCommand('SelectAll');
}
});
};
@ -1248,7 +1260,7 @@ tinymce.util.Quirks = function(editor) {
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;
@ -1267,7 +1279,7 @@ tinymce.util.Quirks = function(editor) {
// 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
@ -1311,7 +1323,7 @@ tinymce.util.Quirks = function(editor) {
if (target !== editor.getBody()) {
dom.setAttrib(target, "style", null);
tinymce.each(template, function(attr) {
each(template, function(attr) {
target.setAttributeNode(attr.cloneNode(true));
});
}
@ -1319,7 +1331,7 @@ tinymce.util.Quirks = function(editor) {
}
function isSelectionAcrossElements() {
return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
}
function blockEvent(editor, e) {
@ -1330,7 +1342,7 @@ tinymce.util.Quirks = function(editor) {
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();
@ -1342,7 +1354,7 @@ tinymce.util.Quirks = function(editor) {
dom.bind(editor.getDoc(), 'cut', function(e) {
var applyAttributes;
if (isSelectionAcrossElements()) {
if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
applyAttributes = getAttributeApplyFunction();
editor.onKeyUp.addToTop(blockEvent);
@ -1381,7 +1393,7 @@ tinymce.util.Quirks = function(editor) {
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") {
@ -1393,20 +1405,19 @@ tinymce.util.Quirks = function(editor) {
}
function addNewLinesBeforeBrInPre() {
var documentMode = editor.getDoc().documentMode;
// IE8+ rendering mode does the right thing with BR in PRE
if (documentMode && documentMode > 7) {
if (getDocumentMode() > 7) {
return;
}
// Enable display: none in area and add a specific class that hides all BR elements in PRE to
// avoid the caret from getting stuck at the BR elements while pressing the right arrow key
setEditorCommandState('RespectVisibilityInDesign', true);
editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
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--) {
@ -1427,7 +1438,7 @@ tinymce.util.Quirks = function(editor) {
});
// 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--) {
@ -1470,7 +1481,7 @@ tinymce.util.Quirks = function(editor) {
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;
@ -1510,7 +1521,7 @@ tinymce.util.Quirks = function(editor) {
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;
}
@ -1534,10 +1545,10 @@ tinymce.util.Quirks = function(editor) {
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);
}
});
};
@ -1562,7 +1573,7 @@ tinymce.util.Quirks = function(editor) {
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) {
@ -1588,6 +1599,14 @@ tinymce.util.Quirks = function(editor) {
editor.onSetContent.add(selection.onSetContent.add(fixLinks));
};
function setDefaultBlockType() {
if (settings.forced_root_block) {
editor.onInit.add(function() {
setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
});
}
}
function removeGhostSelection() {
function repaint(sender, args) {
if (!sender || !args.initial) {
@ -1600,17 +1619,309 @@ tinymce.util.Quirks = function(editor) {
editor.onSetContent.add(repaint);
};
function deleteImageOnBackSpace() {
function deleteControlItemOnBackSpace() {
editor.onKeyDown.add(function(editor, e) {
if (!e.isDefaultPrevented() && e.keyCode == 8 && selection.getNode().nodeName == 'IMG') {
e.preventDefault();
editor.undoManager.beforeChange();
dom.remove(selection.getNode());
editor.undoManager.add();
var rng;
if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
rng = editor.getDoc().selection.createRange();
if (rng && rng.item) {
e.preventDefault();
editor.undoManager.beforeChange();
dom.remove(rng.item(0));
editor.undoManager.add();
}
}
});
};
function renderEmptyBlocksFix() {
var emptyBlocksCSS;
// IE10+
if (getDocumentMode() >= 10) {
emptyBlocksCSS = '';
each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
});
editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
}
};
function fakeImageResize() {
var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
if (!settings.object_resizing || settings.webkit_fake_resize === false) {
return;
}
// Try disabling object resizing if WebKit implements resizing in the future
setEditorCommandState("enableObjectResizing", false);
// Details about each resize handle how to scale etc
resizeHandles = {
// Name: x multiplier, y multiplier, delta size x, delta size y
n: [.5, 0, 0, -1],
e: [1, .5, 1, 0],
s: [.5, 1, 0, 1],
w: [0, .5, -1, 0],
nw: [0, 0, -1, -1],
ne: [1, 0, 1, -1],
se: [1, 1, 1, 1],
sw : [0, 1, -1, 1]
};
function resizeElement(e) {
var deltaX, deltaY;
// Calc new width/height
deltaX = e.screenX - startX;
deltaY = e.screenY - startY;
// Calc new size
width = deltaX * selectedHandle[2] + startW;
height = deltaY * selectedHandle[3] + startH;
// Never scale down lower than 5 pixels
width = width < 5 ? 5 : width;
height = height < 5 ? 5 : height;
// Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
width = Math.round(height / ratio);
height = Math.round(width * ratio);
}
// Update ghost size
dom.setStyles(selectedElmGhost, {
width: width,
height: height
});
// Update ghost X position if needed
if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
}
// Update ghost Y position if needed
if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
}
}
function endResize() {
function setSizeProp(name, value) {
if (value) {
// Resize by using style or attribute
if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
dom.setStyle(selectedElm, name, value);
} else {
dom.setAttrib(selectedElm, name, value);
}
}
}
// Set width/height properties
setSizeProp('width', width);
setSizeProp('height', height);
dom.unbind(editableDoc, 'mousemove', resizeElement);
dom.unbind(editableDoc, 'mouseup', endResize);
if (rootDocument != editableDoc) {
dom.unbind(rootDocument, 'mousemove', resizeElement);
dom.unbind(rootDocument, 'mouseup', endResize);
}
// Remove ghost and update resize handle positions
dom.remove(selectedElmGhost);
showResizeRect(selectedElm);
}
function showResizeRect(targetElm) {
var position, targetWidth, targetHeight;
hideResizeRect();
// Get position and size of target
position = dom.getPos(targetElm);
selectedElmX = position.x;
selectedElmY = position.y;
targetWidth = targetElm.offsetWidth;
targetHeight = targetElm.offsetHeight;
// Reset width/height if user selects a new image/table
if (selectedElm != targetElm) {
selectedElm = targetElm;
width = height = 0;
}
each(resizeHandles, function(handle, name) {
var handleElm;
// Get existing or render resize handle
handleElm = dom.get('mceResizeHandle' + name);
if (!handleElm) {
handleElm = dom.add(editableDoc.documentElement, 'div', {
id: 'mceResizeHandle' + name,
'class': 'mceResizeHandle',
style: 'cursor:' + name + '-resize; margin:0; padding:0'
});
dom.bind(handleElm, 'mousedown', function(e) {
e.preventDefault();
endResize();
startX = e.screenX;
startY = e.screenY;
startW = selectedElm.clientWidth;
startH = selectedElm.clientHeight;
ratio = startH / startW;
selectedHandle = handle;
selectedElmGhost = selectedElm.cloneNode(true);
dom.addClass(selectedElmGhost, 'mceClonedResizable');
dom.setStyles(selectedElmGhost, {
left: selectedElmX,
top: selectedElmY,
margin: 0
});
editableDoc.documentElement.appendChild(selectedElmGhost);
dom.bind(editableDoc, 'mousemove', resizeElement);
dom.bind(editableDoc, 'mouseup', endResize);
if (rootDocument != editableDoc) {
dom.bind(rootDocument, 'mousemove', resizeElement);
dom.bind(rootDocument, 'mouseup', endResize);
}
});
} else {
dom.show(handleElm);
}
// Position element
dom.setStyles(handleElm, {
left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
});
});
// Only add resize rectangle on WebKit and only on images
if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
selectedElm.setAttribute('data-mce-selected', '1');
}
}
function hideResizeRect() {
if (selectedElm) {
selectedElm.removeAttribute('data-mce-selected');
}
for (var name in resizeHandles) {
dom.hide('mceResizeHandle' + name);
}
}
// Add CSS for resize handles, cloned element and selected
editor.contentStyles.push(
'.mceResizeHandle {' +
'position: absolute;' +
'border: 1px solid black;' +
'background: #FFF;' +
'width: 5px;' +
'height: 5px;' +
'z-index: 10000' +
'}' +
'.mceResizeHandle:hover {' +
'background: #000' +
'}' +
'img[data-mce-selected] {' +
'outline: 1px solid black' +
'}' +
'img.mceClonedResizable, table.mceClonedResizable {' +
'position: absolute;' +
'outline: 1px dashed black;' +
'opacity: .5;' +
'z-index: 10000' +
'}'
);
function updateResizeRect() {
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
each(dom.select('img[data-mce-selected]'), function(img) {
img.removeAttribute('data-mce-selected');
});
if (controlElm) {
showResizeRect(controlElm);
} else {
hideResizeRect();
}
}
// Show/hide resize rect when image is selected
editor.onNodeChange.add(updateResizeRect);
// Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
dom.bind(editableDoc, 'selectionchange', updateResizeRect);
// Remove the internal attribute when serializing the DOM
editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
var i = nodes.length;
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);
}
}
}
});
}
}
// All browsers
disableBackspaceIntoATable();
removeBlockQuoteOnBackSpace();
@ -1622,10 +1933,14 @@ tinymce.util.Quirks = function(editor) {
cleanupStylesWhenDeleting();
inputMethodFocus();
selectControlElements();
setDefaultBlockType();
// iOS
if (tinymce.isIDevice) {
selectionChangeNodeChanged();
} else {
fakeImageResize();
selectAll();
}
}
@ -1635,7 +1950,9 @@ tinymce.util.Quirks = function(editor) {
ensureBodyHasRoleApplication();
addNewLinesBeforeBrInPre();
removePreSerializedStylesWhenSelectingControls();
deleteImageOnBackSpace();
deleteControlItemOnBackSpace();
renderEmptyBlocksFix();
keepNoScriptContents();
}
// Gecko
@ -1647,6 +1964,11 @@ tinymce.util.Quirks = function(editor) {
addBrAfterLastLinks();
removeGhostSelection();
}
// Opera
if (tinymce.isOpera) {
fakeImageResize();
}
};
(function(tinymce) {
var namedEntities, baseEntities, reverseEntities,
@ -2098,9 +2420,12 @@ tinymce.html.Styles = function(settings, schema) {
if (!html5) {
html5 = mapCache.html5 = unpack({
A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',
B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',
C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
}, 'html[A|manifest][body|head]' +
'head[A][base|command|link|meta|noscript|script|style|title]' +
'title[A][#]' +
@ -2136,7 +2461,7 @@ tinymce.html.Styles = function(settings, schema) {
'dl[A][dd|dt]' +
'dt[A][B]' +
'dd[A][C]' +
'a[A|href|target|ping|rel|media|type][C]' +
'a[A|href|target|ping|rel|media|type][B]' +
'em[A][B]' +
'strong[A][B]' +
'small[A][B]' +
@ -2182,7 +2507,8 @@ tinymce.html.Styles = function(settings, schema) {
'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|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
'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]' +
'datalist[A][B|option]' +
@ -2196,7 +2522,7 @@ tinymce.html.Styles = function(settings, schema) {
'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
'mathml[A][]' +
'svg[A][]' +
'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
'caption[A][C]' +
'colgroup[A|span][col]' +
'col[A|span][]' +
@ -2205,7 +2531,8 @@ tinymce.html.Styles = function(settings, schema) {
'tbody[A][tr]' +
'tr[A][th|td]' +
'th[A|headers|rowspan|colspan|scope][B]' +
'td[A|headers|rowspan|colspan][C]'
'td[A|headers|rowspan|colspan][C]' +
'wbr[A][]'
);
}
@ -2384,14 +2711,15 @@ tinymce.html.Styles = function(settings, schema) {
}
// Setup map objects
whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options 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');
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');
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) {
@ -2565,8 +2893,15 @@ tinymce.html.Styles = function(settings, schema) {
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) {
@ -2691,6 +3026,10 @@ tinymce.html.Styles = function(settings, schema) {
return blockElementsMap;
};
self.getTextBlockElements = function() {
return textBlockElementsMap;
};
self.getShortEndedElements = function() {
return shortEndedElementsMap;
};
@ -2713,6 +3052,36 @@ tinymce.html.Styles = function(settings, schema) {
return !!(parent && parent[child]);
};
self.isValid = function(name, attr) {
var attrPatterns, i, rule = getElementRule(name);
// Check if it's a valid element
if (rule) {
if (attr) {
// Check if attribute name exists
if (rule.attributes[attr]) {
return true;
}
// Check if attribute matches a regexp pattern
attrPatterns = rule.attributePatterns;
if (attrPatterns) {
i = attrPatterns.length;
while (i--) {
if (attrPatterns[i].pattern.test(name)) {
return true;
}
}
}
} else {
return true;
}
}
// No match
return false;
};
self.getElementRule = getElementRule;
self.getCustomElements = function() {
@ -2726,6 +3095,8 @@ tinymce.html.Styles = function(settings, schema) {
self.addCustomElements = addCustomElements;
self.addValidChildren = addValidChildren;
self.elements = elements;
};
})(tinymce);
@ -2824,10 +3195,10 @@ tinymce.html.Styles = function(settings, schema) {
'(?:!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,
@ -2836,7 +3207,7 @@ tinymce.html.Styles = function(settings, schema) {
// Setup lookup tables for empty elements and boolean attributes
shortEndedElements = schema.getShortEndedElements();
selfClosing = schema.getSelfClosingElements();
selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
fillAttrsMap = schema.getBoolAttrs();
validate = settings.validate;
removeInternalElements = settings.remove_internals;
@ -3310,7 +3681,7 @@ tinymce.html.Styles = function(settings, schema) {
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;
}
}
@ -3366,18 +3737,41 @@ tinymce.html.Styles = function(settings, schema) {
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];
for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
@ -3584,9 +3978,23 @@ tinymce.html.Styles = function(settings, schema) {
}
};
function cloneAndExcludeBlocks(input) {
var name, output = {};
for (name in input) {
if (name !== 'li' && name != 'p') {
output[name] = input[name];
}
}
return output;
};
parser = new tinymce.html.SaxParser({
validate : validate,
fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
cdata: function(text) {
node.append(createNode('#cdata', 4)).value = text;
@ -3741,7 +4149,8 @@ tinymce.html.Styles = function(settings, schema) {
}
// Trim start white space
textNode = node.prev;
// Removed due to: #5424
/*textNode = node.prev;
if (textNode && textNode.type === 3) {
text = textNode.value.replace(startWhiteSpaceRegExp, '');
@ -3749,7 +4158,7 @@ tinymce.html.Styles = function(settings, schema) {
textNode.value = text;
else
textNode.remove();
}
}*/
}
// Check if we exited a whitespace preserved element
@ -3764,7 +4173,7 @@ tinymce.html.Styles = function(settings, schema) {
node.empty().append(new Node('#text', '3')).value = '\u00a0';
else {
// Leave nodes that have a name like <a name="name">
if (!node.attributes.map.name) {
if (!node.attributes.map.name && !node.attributes.map.id) {
tempNode = node.parent;
node.empty().remove();
node = tempNode;
@ -3916,12 +4325,12 @@ tinymce.html.Styles = function(settings, schema) {
// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
if (!settings.allow_html_in_named_anchor) {
self.addAttributeFilter('name', function(nodes, name) {
self.addAttributeFilter('id,name', function(nodes, name) {
var i = nodes.length, sibling, prevSibling, parent, node;
while (i--) {
node = nodes[i];
if (node.name === 'a' && node.firstChild) {
if (node.name === 'a' && node.firstChild && !node.attr('href')) {
parent = node.parent;
// Move children after current node
@ -4259,6 +4668,12 @@ tinymce.dom = {};
}
}
// Page already loaded then fire it directly
if (doc.readyState == "complete") {
readyHandler();
return;
}
// Use W3C method
if (w3cEventModel) {
addEvent(win, 'DOMContentLoaded', readyHandler);
@ -4600,7 +5015,7 @@ tinymce.dom = {};
// Old API supported multiple targets
if (target && target instanceof Array) {
var i = target;
var i = target.length;
while (i--) {
self.add(target[i], events, func, scope);
@ -4660,12 +5075,20 @@ tinymce.dom = {};
};
self.prevent = function(e) {
if (!e.preventDefault) {
e = fix(e);
}
e.preventDefault();
return false;
};
self.stop = function(e) {
if (!e.stopPropagation) {
e = fix(e);
}
e.stopPropagation();
return false;
@ -4794,6 +5217,11 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
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;
@ -5434,6 +5862,32 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
return this.styles.serialize(o, name);
},
addStyle: function(cssText) {
var doc = this.doc, head;
// Create style element if needed
styleElm = doc.getElementById('mceDefaultStyles');
if (!styleElm) {
styleElm = doc.createElement('style'),
styleElm.id = 'mceDefaultStyles';
styleElm.type = 'text/css';
head = doc.getElementsByTagName('head')[0];
if (head.firstChild) {
head.insertBefore(styleElm, head.firstChild);
} else {
head.appendChild(styleElm);
}
}
// Append style data to old or new style element
if (styleElm.styleSheet) {
styleElm.styleSheet.cssText += cssText;
} else {
styleElm.appendChild(doc.createTextNode(cssText));
}
},
loadCSS : function(u) {
var t = this, d = t.doc, head;
@ -5557,13 +6011,13 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
// This seems to fix this problem
// Create new div with HTML contents and a BR infront to keep comments
element = self.create('div');
element.innerHTML = '<br />' + html;
var newElement = self.create('div');
newElement.innerHTML = '<br />' + html;
// Add all children from div to target
each (element.childNodes, function(node, i) {
each (tinymce.grep(newElement.childNodes), function(node, i) {
// Skip br element
if (i)
if (i && element.canHaveHTML)
element.appendChild(node);
});
}
@ -6158,7 +6612,8 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
cloneContents : cloneContents,
insertNode : insertNode,
surroundContents : surroundContents,
cloneRange : cloneRange
cloneRange : cloneRange,
toStringIE : toStringIE
});
function createDocumentFragment() {
@ -6798,9 +7253,20 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
n.parentNode.removeChild(n);
};
function toStringIE() {
return dom.create('body', null, cloneContents()).outerText;
}
return t;
};
ns.Range = Range;
// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
Range.prototype.toString = function() {
return this.toStringIE();
};
})(tinymce.dom);
(function() {
@ -7156,7 +7622,8 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
};
this.addRange = function(rng) {
var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
function setEndPoint(start) {
var container, offset, marker, tmpRng, nodes;
@ -7214,11 +7681,25 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
// Trick to place the caret inside an empty block element like <p></p>
if (startOffset == endOffset && !startContainer.hasChildNodes()) {
if (startContainer.canHaveHTML) {
// Check if previous sibling is an empty block if it is then we need to render it
// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
sibling = startContainer.previousSibling;
if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
sibling.innerHTML = '\uFEFF';
} else {
sibling = null;
}
startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
ieRng.moveToElementText(startContainer.lastChild);
ieRng.select();
dom.doc.selection.clear();
startContainer.innerHTML = '';
if (sibling) {
sibling.innerHTML = '';
}
return;
} else {
startOffset = dom.nodeIndex(startContainer);
@ -7228,10 +7709,17 @@ tinymce.dom.TreeWalker = function(start_node, root_node) {
if (startOffset == endOffset - 1) {
try {
ctrlElm = startContainer.childNodes[startOffset];
ctrlRng = body.createControlRange();
ctrlRng.addElement(startContainer.childNodes[startOffset]);
ctrlRng.addElement(ctrlElm);
ctrlRng.select();
return;
// Check if the range produced is on the correct element and is a control range
// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
nativeRng = selection.getRng();
if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
return;
}
} catch (ex) {
// Ignore
}
@ -8816,12 +9304,13 @@ window.tinymce.dom.Sizzle = Sizzle;
var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
tinymce.create('tinymce.dom.Selection', {
Selection : function(dom, win, serializer) {
Selection : function(dom, win, serializer, editor) {
var t = this;
t.dom = dom;
t.win = win;
t.serializer = serializer;
t.editor = editor;
// Add events
each([
@ -8977,7 +9466,7 @@ window.tinymce.dom.Sizzle = Sizzle;
},
getStart : function() {
var rng = this.getRng(), startElement, parentElement, checkRng, node;
var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
if (rng.duplicate || rng.item) {
// Control selection, return first item
@ -8988,6 +9477,9 @@ window.tinymce.dom.Sizzle = Sizzle;
checkRng = rng.duplicate();
checkRng.collapse(1);
startElement = checkRng.parentElement();
if (startElement.ownerDocument !== self.dom.doc) {
startElement = self.dom.getRoot();
}
// Check if range parent is inside the start element, then return the inner parent element
// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
@ -9014,31 +9506,34 @@ window.tinymce.dom.Sizzle = Sizzle;
},
getEnd : function() {
var t = this, r = t.getRng(), e, eo;
var self = this, rng = self.getRng(), endElement, endOffset;
if (r.duplicate || r.item) {
if (r.item)
return r.item(0);
if (rng.duplicate || rng.item) {
if (rng.item)
return rng.item(0);
r = r.duplicate();
r.collapse(0);
e = r.parentElement();
rng = rng.duplicate();
rng.collapse(0);
endElement = rng.parentElement();
if (endElement.ownerDocument !== self.dom.doc) {
endElement = self.dom.getRoot();
}
if (e && e.nodeName == 'BODY')
return e.lastChild || e;
if (endElement && endElement.nodeName == 'BODY')
return endElement.lastChild || endElement;
return e;
return endElement;
} else {
e = r.endContainer;
eo = r.endOffset;
endElement = rng.endContainer;
endOffset = rng.endOffset;
if (e.nodeType == 1 && e.hasChildNodes())
e = e.childNodes[eo > 0 ? eo - 1 : eo];
if (endElement.nodeType == 1 && endElement.hasChildNodes())
endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
if (e && e.nodeType == 3)
return e.parentNode;
if (endElement && endElement.nodeType == 3)
return endElement.parentNode;
return e;
return endElement;
}
},
@ -9784,14 +10279,76 @@ window.tinymce.dom.Sizzle = Sizzle;
}
},
destroy : function(s) {
var t = this;
selectorChanged: function(selector, callback) {
var self = this, currentSelectors;
t.win = null;
if (!self.selectorChangedData) {
self.selectorChangedData = {};
currentSelectors = {};
self.editor.onNodeChange.addToTop(function(ed, cm, node) {
var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
// Check for new matching selectors
each(self.selectorChangedData, function(callbacks, selector) {
each(parents, function(node) {
if (dom.is(node, selector)) {
if (!currentSelectors[selector]) {
// Execute callbacks
each(callbacks, function(callback) {
callback(true, {node: node, selector: selector, parents: parents});
});
currentSelectors[selector] = callbacks;
}
matchedSelectors[selector] = callbacks;
return false;
}
});
});
// Check if current selectors still match
each(currentSelectors, function(callbacks, selector) {
if (!matchedSelectors[selector]) {
delete currentSelectors[selector];
each(callbacks, function(callback) {
callback(false, {node: node, selector: selector, parents: parents});
});
}
});
});
}
// Add selector listeners
if (!self.selectorChangedData[selector]) {
self.selectorChangedData[selector] = [];
}
self.selectorChangedData[selector].push(callback);
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;
self.win = null;
// Manual destroy then remove unload handler
if (!s)
tinymce.removeUnload(t.destroy);
if (!manual)
tinymce.removeUnload(self.destroy);
},
// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
@ -9956,6 +10513,18 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
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);
}
}
});
// Force script into CDATA sections and remove the mce- prefix also add comments around styles
htmlParser.addNodeFilter('script,style', function(nodes, name) {
var i = nodes.length, node, value;
@ -10113,7 +10682,7 @@ window.tinymce.dom.Sizzle = Sizzle;
// Replace all BOM characters for now until we can find a better solution
if (!args.cleanup)
args.content = args.content.replace(/\uFEFF|\u200B/g, '');
args.content = args.content.replace(/\uFEFF/g, '');
// Post process
if (!args.no_events)
@ -10207,11 +10776,10 @@ window.tinymce.dom.Sizzle = Sizzle;
}
// Create new script element
elm = dom.create('script', {
id : id,
type : 'text/javascript',
src : tinymce._addVer(url)
});
elm = document.createElement('script');
elm.id = id;
elm.type = 'text/javascript';
elm.src = tinymce._addVer(url);
// Add onload listener for non IE browsers since IE9
// fires onload event before the script is parsed and executed
@ -10575,12 +11143,15 @@ window.tinymce.dom.Sizzle = Sizzle;
t.destroy = function() {
each(items, function(item) {
dom.unbind(dom.get(item.id), 'focus', itemFocussed);
dom.unbind(dom.get(item.id), 'blur', itemBlurred);
var elm = dom.get(item.id);
dom.unbind(elm, 'focus', itemFocussed);
dom.unbind(elm, 'blur', itemBlurred);
});
dom.unbind(dom.get(root), 'focus', rootFocussed);
dom.unbind(dom.get(root), 'keydown', rootKeydown);
var rootElm = dom.get(root);
dom.unbind(rootElm, 'focus', rootFocussed);
dom.unbind(rootElm, 'keydown', rootKeydown);
items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
t.destroy = function() {};
@ -10659,21 +11230,23 @@ window.tinymce.dom.Sizzle = Sizzle;
// Set up state and listeners for each item.
each(items, function(item, idx) {
var tabindex;
var tabindex, elm;
if (!item.id) {
item.id = dom.uniqueId('_mce_item_');
}
elm = dom.get(item.id);
if (excludeFromTabOrder) {
dom.bind(item.id, 'blur', itemBlurred);
dom.bind(elm, 'blur', itemBlurred);
tabindex = '-1';
} else {
tabindex = (idx === 0 ? '0' : '-1');
}
dom.setAttrib(item.id, 'tabindex', tabindex);
dom.bind(dom.get(item.id), 'focus', itemFocussed);
elm.setAttribute('tabindex', tabindex);
dom.bind(elm, 'focus', itemFocussed);
});
// Setup initial state for root element.
@ -10682,10 +11255,11 @@ window.tinymce.dom.Sizzle = Sizzle;
}
dom.setAttrib(root, 'tabindex', '-1');
// Setup listeners for root element.
dom.bind(dom.get(root), 'focus', rootFocussed);
dom.bind(dom.get(root), 'keydown', rootKeydown);
var rootElm = dom.get(root);
dom.bind(rootElm, 'focus', rootFocussed);
dom.bind(rootElm, 'keydown', rootKeydown);
}
});
})(tinymce);
@ -11326,7 +11900,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
l = DOM.encode(s.label || '');
h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
else
h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
@ -12034,6 +12608,16 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
}
t.keyboardNav = new tinymce.ui.KeyboardNavigation({
root: t.id + '_menu',
items: DOM.select('a', t.id + '_menu'),
onCancel: function() {
t.hideMenu();
t.focus();
}
});
t.keyboardNav.focus();
t.isMenuVisible = 1;
},
@ -12054,6 +12638,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
t.isMenuVisible = 0;
t.onHideMenu.dispatch();
t.keyboardNav.destroy();
}
},
@ -12118,15 +12703,6 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
DOM.addClass(m, 'mceColorSplitMenu');
new tinymce.ui.KeyboardNavigation({
root: t.id + '_menu',
items: DOM.select('a', t.id + '_menu'),
onCancel: function() {
t.hideMenu();
t.focus();
}
});
// Prevent IE from scrolling and hindering click to occur #4019
Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
@ -12168,11 +12744,17 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
},
destroy : function() {
this.parent();
var self = this;
Event.clear(this.id + '_menu');
Event.clear(this.id + '_more');
DOM.remove(this.id + '_menu');
self.parent();
Event.clear(self.id + '_menu');
Event.clear(self.id + '_more');
DOM.remove(self.id + '_menu');
if (self.keyboardNav) {
self.keyboardNav.destroy();
}
}
});
})(tinymce);
@ -12483,11 +13065,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
};
s = extend({
theme : "simple",
language : "en"
}, s);
t.settings = s;
// Legacy call
@ -12589,6 +13166,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
if (id === undef)
return this.editors;
if (!this.editors.hasOwnProperty(id))
return undef;
return this.editors[id];
},
@ -12767,7 +13347,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.settings = settings = extend({
id : id,
language : 'en',
theme : 'simple',
theme : 'advanced',
skin : 'default',
delta_width : 0,
delta_height : 0,
@ -12798,8 +13378,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
inline_styles : TRUE,
convert_fonts_to_spans : TRUE,
indent : 'simple',
indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',
indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',
indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
validate : TRUE,
entity_encoding : 'named',
url_converter : self.convertURL,
@ -12821,6 +13401,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.contentCSS = [];
self.contentStyles = [];
// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
self.setupEvents();
@ -12859,6 +13441,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
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);
// Hide target element early to prevent content flashing
if (!s.content_editable) {
t.orgVisibility = t.getElement().style.visibility;
t.getElement().style.visibility = 'hidden';
}
if (tinymce.WindowManager)
t.windowManager = new tinymce.WindowManager(t);
@ -12920,7 +13508,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
if (s.language && s.language_load !== false)
sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
each(explode(s.plugins), function(p) {
@ -12954,20 +13542,25 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
init : function() {
var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
tinymce.add(t);
s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
if (s.theme) {
s.theme = s.theme.replace(/-/, '');
o = ThemeManager.get(s.theme);
t.theme = new o();
if (typeof s.theme != "function") {
s.theme = s.theme.replace(/-/, '');
o = ThemeManager.get(s.theme);
t.theme = new o();
if (t.theme.init)
t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
if (t.theme.init)
t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
} else {
t.theme = s.theme;
}
}
function initPlugin(p) {
var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
if (c && tinymce.inArray(initializedPlugins,p) === -1) {
@ -13001,36 +13594,68 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
t.controlManager = new tinymce.ControlManager(t);
t.onExecCommand.add(function(ed, c) {
// Don't refresh the select lists until caret move
if (!/^(FontName|FontSize)$/.test(c))
t.nodeChanged();
});
// Enables users to override the control factory
t.onBeforeRenderUI.dispatch(t, t.controlManager);
// Measure box
if (s.render_ui && t.theme) {
w = s.width || e.style.width || e.offsetWidth;
h = s.height || e.style.height || e.offsetHeight;
t.orgDisplay = e.style.display;
re = /^[0-9\.]+(|px)$/i;
if (re.test('' + w))
w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
if (typeof s.theme != "function") {
w = s.width || e.style.width || e.offsetWidth;
h = s.height || e.style.height || e.offsetHeight;
mh = s.min_height || 100;
re = /^[0-9\.]+(|px)$/i;
if (re.test('' + h))
h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), 100);
if (re.test('' + w))
w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
// Render UI
o = t.theme.renderUI({
targetNode : e,
width : w,
height : h,
deltaWidth : s.delta_width,
deltaHeight : s.delta_height
});
if (re.test('' + h))
h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
// Render UI
o = t.theme.renderUI({
targetNode : e,
width : w,
height : h,
deltaWidth : s.delta_width,
deltaHeight : s.delta_height
});
// Resize editor
DOM.setStyles(o.sizeContainer || o.editorContainer, {
width : w,
height : h
});
h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
if (h < mh)
h = mh;
} else {
o = s.theme(t, e);
// Convert element type to id:s
if (o.editorContainer.nodeType) {
o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
}
// Convert element type to id:s
if (o.iframeContainer.nodeType) {
o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
}
// Use specified iframe height or the targets offsetHeight
h = o.iframeHeight || e.offsetHeight;
// 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();
});
});
}
}
t.editorContainer = o.editorContainer;
}
@ -13042,6 +13667,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
});
}
// Load specified content CSS last
if (s.content_style) {
t.contentStyles.push(s.content_style);
}
// Content editable mode ends here
if (s.content_editable) {
e = n = o = null; // Fix IE leak
@ -13052,16 +13682,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
if (document.domain && location.hostname != document.domain)
tinymce.relaxedDomain = document.domain;
// Resize editor
DOM.setStyles(o.sizeContainer || o.editorContainer, {
width : w,
height : h
});
h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
if (h < 100)
h = 100;
t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
// We only need to override paths if we have to
@ -13070,10 +13690,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
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" />';
@ -13120,7 +13742,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
});
t.contentAreaContainer = o.iframeContainer;
DOM.get(o.editorContainer).style.display = t.orgDisplay;
if (o.editorContainer) {
DOM.get(o.editorContainer).style.display = t.orgDisplay;
}
// Restore visibility on target element
e.style.visibility = t.orgVisibility;
DOM.get(t.id).style.display = 'none';
DOM.setAttrib(t.id, 'aria-hidden', true);
@ -13131,7 +13760,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
initContentBody : function() {
var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body;
var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
// Setup iframe body
if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
@ -13230,7 +13859,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer);
self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
self.formatter = new tinymce.Formatter(self);
@ -13240,6 +13869,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.enterKey = new tinymce.EnterKey(self);
self.editorCommands = new tinymce.EditorCommands(self);
self.onExecCommand.add(function(editor, command) {
// Don't refresh the select lists until caret move
if (!/^(FontName|FontSize)$/.test(command))
self.nodeChanged();
});
// Pass through
self.serializer.onPreProcess.add(function(se, o) {
return self.onPreProcess.dispatch(self, o, se);
@ -13251,7 +13886,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.onPreInit.dispatch(self);
if (!settings.gecko_spellcheck)
if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
doc.body.spellcheck = false;
if (!settings.readonly) {
@ -13302,6 +13937,17 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.focus(true);
self.nodeChanged({initial : true});
// Add editor specific CSS styles
if (self.contentStyles.length > 0) {
contentCssText = '';
each(self.contentStyles, function(style) {
contentCssText += style + "\r\n";
});
self.dom.addStyle(contentCssText);
}
// Load specified content CSS last
each(self.contentCSS, function(url) {
self.dom.loadCSS(url);
@ -13327,6 +13973,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
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);
}
// Get selected control element
ieRng = selection.getRng();
if (ieRng.item) {
@ -13673,6 +14323,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// 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);
},
@ -13789,13 +14441,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
if (!args.no_events)
self.onSetContent.dispatch(self, args);
self.selection.normalize();
// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
if (!self.settings.content_editable || document.activeElement === self.getBody()) {
self.selection.normalize();
}
return args.content;
},
getContent : function(args) {
var self = this, content;
var self = this, content, body = self.getBody();
// Setup args object
args = args || {};
@ -13809,11 +14464,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// Get raw contents or by default the cleaned contents
if (args.format == 'raw')
content = self.getBody().innerHTML;
content = body.innerHTML;
else if (args.format == 'text')
content = body.innerText || body.textContent;
else
content = self.serializer.serialize(self.getBody(), args);
content = self.serializer.serialize(body, args);
args.content = tinymce.trim(content);
// Trim whitespace in beginning/end of HTML
if (args.format != 'text') {
args.content = tinymce.trim(content);
} else {
args.content = content;
}
// Do post processing
if (!args.no_events)
@ -13922,14 +14584,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
return;
case 'A':
value = dom.getAttrib(elm, 'name');
cls = 'mceItemAnchor';
if (!dom.getAttrib(elm, 'href', false)) {
value = dom.getAttrib(elm, 'name') || elm.id;
cls = 'mceItemAnchor';
if (value) {
if (self.hasVisual)
dom.addClass(elm, cls);
else
dom.removeClass(elm, cls);
if (value) {
if (self.hasVisual)
dom.addClass(elm, cls);
else
dom.removeClass(elm, cls);
}
}
return;
@ -13940,22 +14604,29 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
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
if (!self.settings.content_editable) {
Event.clear(self.getWin());
Event.clear(self.getDoc());
Event.unbind(self.getWin());
Event.unbind(self.getDoc());
}
Event.clear(self.getBody());
Event.clear(self.formElement);
Event.unbind(elm);
Event.unbind(self.getBody());
Event.clear(elm);
self.execCallback('remove_instance_callback', self);
self.onRemove.dispatch(self);
@ -14157,8 +14828,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// Handle legacy handle_event_callback option
if (settings.handle_event_callback) {
self.onEvent.add(function(ed, e, o) {
if (self.execCallback('handle_event_callback', e, ed, o) === false)
Event.cancel(e);
if (self.execCallback('handle_event_callback', e, ed, o) === false) {
e.preventDefault();
e.stopPropagation();
}
});
}
@ -14224,9 +14897,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
self.focus(true);
};
function nodeChanged() {
// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i>
self.selection.normalize();
function nodeChanged(ed, e) {
// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
self.selection.normalize();
}
self.nodeChanged();
}
@ -14270,7 +14946,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
var keyCode = e.keyCode;
if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
nodeChanged();
nodeChanged(ed, e);
});
// Add reset handler
@ -14623,7 +15299,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// Insert bookmark node and get the parent
selection.setContent(bookmarkHtml);
parentNode = editor.selection.getNode();
parentNode = selection.getNode();
rootNode = editor.getBody();
// Opera will return the document node when selection is in root
@ -14697,6 +15373,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
},
mceToggleFormat : function(command, ui, value) {
toggleFormat(value);
},
mceSetContent : function(command, ui, value) {
editor.setContent(value);
},
@ -14786,10 +15466,15 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
selectAll : function() {
var root = dom.getRoot(), rng = dom.createRng();
rng.setStart(root, 0);
rng.setEnd(root, root.childNodes.length);
// Old IE does a better job with selectall than new versions
if (selection.getRng().setStart) {
rng.setStart(root, 0);
rng.setEnd(root, root.childNodes.length);
editor.selection.setRng(rng);
selection.setRng(rng);
} else {
execNativeCommand('SelectAll');
}
}
});
@ -14828,7 +15513,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
'InsertUnorderedList,InsertOrderedList' : function(command) {
return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
var list = dom.getParent(selection.getNode(), 'ul,ol');
return list &&
(command === 'insertunorderedlist' && list.tagName === 'UL'
|| command === 'insertorderedlist' && list.tagName === 'OL');
}
}, 'state');
@ -14878,9 +15566,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
};
// Create event instances
onAdd = new Dispatcher(self);
onUndo = new Dispatcher(self);
onRedo = new Dispatcher(self);
onBeforeAdd = new Dispatcher(self);
onAdd = new Dispatcher(self);
onUndo = new Dispatcher(self);
onRedo = new Dispatcher(self);
// Pass though onAdd event from UndoManager to Editor as onChange
onAdd.add(function(undoman, level) {
@ -14920,7 +15609,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// 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();
}
@ -14969,6 +15658,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
data : data,
typing : false,
onBeforeAdd: onBeforeAdd,
onAdd : onAdd,
@ -14985,6 +15676,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
level = level || {};
level.content = getContent();
self.onBeforeAdd.dispatch(self, level);
// Add undo level if needed
lastLevel = data[index];
@ -15080,7 +15773,7 @@ tinymce.ForceBlocks = function(editor) {
var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
function addRootBlocks() {
var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped;
var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
if (!node || node.nodeType !== 1 || !settings.forced_root_block)
return;
@ -15108,6 +15801,7 @@ tinymce.ForceBlocks = function(editor) {
rng.moveToElementText(node);
}
isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
tmpRng = rng.duplicate();
tmpRng.collapse(true);
startOffset = tmpRng.move('character', offset) * -1;
@ -15123,6 +15817,14 @@ tinymce.ForceBlocks = function(editor) {
node = rootNode.firstChild;
while (node) {
if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
// Remove empty text nodes
if (node.nodeType === 3 && node.nodeValue.length == 0) {
tempNode = node;
node = node.nextSibling;
dom.remove(tempNode);
continue;
}
if (!rootBlockNode) {
rootBlockNode = dom.create(settings.forced_root_block);
node.parentNode.insertBefore(rootBlockNode, node);
@ -15138,28 +15840,30 @@ tinymce.ForceBlocks = function(editor) {
}
}
if (rng.setStart) {
rng.setStart(startContainer, startOffset);
rng.setEnd(endContainer, endOffset);
selection.setRng(rng);
} else {
try {
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(rootNode);
rng.collapse(true);
rng.moveStart('character', startOffset);
if (endOffset > 0)
rng.moveEnd('character', endOffset);
rng.select();
} catch (ex) {
// Ignore
}
}
// Only trigger nodeChange when we wrapped nodes to prevent a forever loop
if (wrapped) {
if (rng.setStart) {
rng.setStart(startContainer, startOffset);
rng.setEnd(endContainer, endOffset);
selection.setRng(rng);
} else {
// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
if (isInEditorDocument) {
try {
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(rootNode);
rng.collapse(true);
rng.moveStart('character', startOffset);
if (endOffset > 0)
rng.moveEnd('character', endOffset);
rng.select();
} catch (ex) {
// Ignore
}
}
}
editor.nodeChanged();
}
};
@ -15227,28 +15931,40 @@ tinymce.ForceBlocks = function(editor) {
return c;
},
createControl : function(n) {
var c, t = this, ed = t.editor;
createControl : function(name) {
var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
each(ed.plugins, function(p) {
if (p.createControl) {
c = p.createControl(n, t);
if (c)
return false;
}
});
switch (n) {
case "|":
case "separator":
return t.createSeparator();
// Build control factory cache
if (!self.controlFactories) {
self.controlFactories = [];
each(editor.plugins, function(plugin) {
if (plugin.createControl) {
self.controlFactories.push(plugin);
}
});
}
if (!c && ed.buttons && (c = ed.buttons[n]))
return t.createButton(n, c);
// Create controls by asking cached factories
factories = self.controlFactories;
for (i = 0, l = factories.length; i < l; i++) {
ctrl = factories[i].createControl(name, self);
return t.add(c);
if (ctrl) {
return self.add(ctrl);
}
}
// Create sepearator
if (name === "|" || name === "separator") {
return self.createSeparator();
}
// Create control from button collection
if (editor.buttons && (ctrl = editor.buttons[name])) {
return self.createButton(name, ctrl);
}
return self.add(ctrl);
},
createDropMenu : function(id, s, cc) {
@ -15676,19 +16392,21 @@ tinymce.ForceBlocks = function(editor) {
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,
INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
INVISIBLE_CHAR = '\uFEFF',
MCE_ATTR_RE = /^(src|href|style)$/,
FALSE = false,
TRUE = true,
formatChangeData,
undef,
getContentEditable = dom.getContentEditable;
function isArray(obj) {
return obj instanceof Array;
};
function isTextBlock(name) {
return !!ed.schema.getTextBlocks()[name.toLowerCase()];
}
function getParents(node, selector) {
return dom.getParents(node, selector, dom.getRoot());
@ -16254,6 +16972,11 @@ tinymce.ForceBlocks = function(editor) {
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)) {
lastContentEditable = contentEditable;
@ -16564,7 +17287,7 @@ tinymce.ForceBlocks = function(editor) {
matchedFormatNames.push(name);
}
}
});
}, dom.getRoot());
return matchedFormatNames;
};
@ -16593,6 +17316,62 @@ tinymce.ForceBlocks = function(editor) {
return FALSE;
};
function formatChanged(formats, callback, similar) {
var currentFormats;
// Setup format node change logic
if (!formatChangeData) {
formatChangeData = {};
currentFormats = {};
ed.onNodeChange.addToTop(function(ed, cm, node) {
var parents = getParents(node), matchedFormats = {};
// Check for new formats
each(formatChangeData, function(callbacks, format) {
each(parents, function(node) {
if (matchNode(node, format, {}, callbacks.similar)) {
if (!currentFormats[format]) {
// Execute callbacks
each(callbacks, function(callback) {
callback(true, {node: node, format: format, parents: parents});
});
currentFormats[format] = callbacks;
}
matchedFormats[format] = callbacks;
return false;
}
});
});
// Check if current formats still match
each(currentFormats, function(callbacks, format) {
if (!matchedFormats[format]) {
delete currentFormats[format];
each(callbacks, function(callback) {
callback(false, {node: node, format: format, parents: parents});
});
}
});
});
}
// Add format listeners
each(formats.split(','), function(format) {
if (!formatChangeData[format]) {
formatChangeData[format] = [];
formatChangeData[format].similar = similar;
}
formatChangeData[format].push(callback);
});
return this;
};
// Expose to public
tinymce.extend(this, {
get : get,
@ -16603,7 +17382,8 @@ tinymce.ForceBlocks = function(editor) {
match : match,
matchAll : matchAll,
matchNode : matchNode,
canApply : canApply
canApply : canApply,
formatChanged: formatChanged
});
// Initialize
@ -16690,6 +17470,10 @@ tinymce.ForceBlocks = function(editor) {
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) {
@ -16704,7 +17488,7 @@ tinymce.ForceBlocks = function(editor) {
// 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;
}
}
@ -16863,7 +17647,7 @@ tinymce.ForceBlocks = function(editor) {
// 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)
@ -17509,6 +18293,21 @@ tinymce.ForceBlocks = function(editor) {
}
};
// Checks if the parent caret container node isn't empty if that is the case it
// will remove the bogus state on all children that isn't empty
function unmarkBogusCaretParents() {
var i, caretContainer, node;
caretContainer = getParentCaretContainer(selection.getStart());
if (caretContainer && !dom.isEmpty(caretContainer)) {
tinymce.walk(caretContainer, function(node) {
if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
dom.setAttrib(node, 'data-mce-bogus', null);
}
}, 'childNodes');
}
};
// Only bind the caret events once
if (!self._hasCaretEvents) {
// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
@ -17528,6 +18327,7 @@ tinymce.ForceBlocks = function(editor) {
tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
ed[name].addToTop(function() {
removeCaretContainer();
unmarkBogusCaretParents();
});
});
@ -17538,16 +18338,12 @@ tinymce.ForceBlocks = function(editor) {
if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
removeCaretContainer(getParentCaretContainer(selection.getStart()));
}
unmarkBogusCaretParents();
});
// Remove bogus state if they got filled by contents using editor.selection.setContent
selection.onSetContent.add(function() {
dom.getParent(selection.getStart(), function(node) {
if (node.id !== caretContainerId && dom.getAttrib(node, 'data-mce-bogus') && !dom.isEmpty(node)) {
dom.setAttrib(node, 'data-mce-bogus', null);
}
});
});
selection.onSetContent.add(unmarkBogusCaretParents);
self._hasCaretEvents = true;
}
@ -17664,21 +18460,63 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
var TreeWalker = tinymce.dom.TreeWalker;
tinymce.EnterKey = function(editor) {
var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager;
var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
function handleEnterKey(evt) {
var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
// Returns true if the block can be split into two blocks or not
function canSplitBlock(node) {
return node &&
dom.isBlock(node) &&
!/^(TD|TH|CAPTION)$/.test(node.nodeName) &&
!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
!/^(fixed|absolute)/i.test(node.style.position) &&
dom.getContentEditable(node) !== "true";
};
// Renders empty block on IE
function renderBlockOnIE(block) {
var oldRng;
if (tinymce.isIE && dom.isBlock(block)) {
oldRng = selection.getRng();
block.appendChild(dom.create('span', null, '\u00a0'));
selection.select(block);
block.lastChild.outerHTML = '';
selection.setRng(oldRng);
}
};
// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
function trimInlineElementsOnLeftSideOfBlock(block) {
var node = block, firstChilds = [], i;
// Find inner most first child ex: <p><i><b>*</b></i></p>
while (node = node.firstChild) {
if (dom.isBlock(node)) {
return;
}
if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
firstChilds.push(node);
}
}
i = firstChilds.length;
while (i--) {
node = firstChilds[i];
if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
dom.remove(node);
} else {
// Remove <a> </a> see #5381
if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
dom.remove(node);
}
}
}
};
// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
function moveToCaretPosition(root) {
var walker, node, rng, y, viewPort, lastNode = root, tempElm;
@ -17695,7 +18533,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
break;
}
if (/^(BR|IMG)$/.test(node.nodeName)) {
if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
rng.setStartBefore(node);
rng.setEndBefore(node);
break;
@ -17756,6 +18594,11 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
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
@ -17772,7 +18615,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// BR is needed in empty blocks on non IE browsers
if (!tinymce.isIE) {
caretNode.innerHTML = '<br>';
caretNode.innerHTML = '<br data-mce-bogus="1">';
}
return block;
@ -17792,6 +18635,11 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
return true;
}
// If the caret if before the first element in parentBlock
if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
return true;
}
// Caret can be before/after a table
if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
@ -17799,21 +18647,35 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// Walk the DOM and look for text nodes or non empty elements
walker = new TreeWalker(container, parentBlock);
while (node = (start ? walker.prev() : walker.next())) {
// If caret is in beginning or end of a text block then jump to the next/previous node
if (container.nodeType == 3) {
if (start && offset == 0) {
walker.prev();
} else if (!start && offset == container.nodeValue.length) {
walker.next();
}
}
while (node = walker.current()) {
if (node.nodeType === 1) {
// Ignore bogus elements
if (node.getAttribute('data-mce-bogus')) {
continue;
}
// Keep empty elements like <img />
name = node.nodeName.toLowerCase();
if (name === 'IMG') {
return false;
if (!node.getAttribute('data-mce-bogus')) {
// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
name = node.nodeName.toLowerCase();
if (nonEmptyElementsMap[name] && name !== 'br') {
return false;
}
}
} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
return false;
}
if (start) {
walker.prev();
} else {
walker.next();
}
}
return true;
@ -17897,6 +18759,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
} else if (isFirstOrLastLi()) {
// Last LI in list then temove LI and add text block after list
dom.insertAfter(newBlock, containerBlock);
renderBlockOnIE(newBlock);
} else {
// Middle LI in list the split the list and insert a text block in the middle
// Extract after fragment and insert it after the current block
@ -17928,12 +18791,12 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// 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()) {
brElm = dom.create('br')
brElm = dom.create('br');
rng.insertNode(brElm);
rng.setStartAfter(brElm);
rng.setEndAfter(brElm);
@ -17949,6 +18812,12 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
}
// Insert temp marker and scroll to that
marker = dom.create('span', {}, '&nbsp;');
brElm.parentNode.insertBefore(marker, brElm);
selection.scrollIntoView(marker);
dom.remove(marker);
if (!extraBr) {
rng.setStartAfter(brElm);
rng.setEndAfter(brElm);
@ -17988,6 +18857,22 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
return parent !== root ? editableRoot : root;
};
// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
function addBrToBlockIfNeeded(block) {
var lastChild;
// IE will render the blocks correctly other browsers needs a BR
if (!tinymce.isIE) {
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
lastChild = block.lastChild;
if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
dom.add(block, 'br');
}
}
};
// Delete any selected contents
if (!rng.collapsed) {
editor.execCommand('Delete');
@ -18002,15 +18887,20 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// Setup range items and newBlockName
container = rng.startContainer;
offset = rng.startOffset;
newBlockName = settings.forced_root_block;
newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
documentMode = dom.doc.documentMode;
shiftKey = evt.shiftKey;
// Resolve node index
if (container.nodeType == 1 && container.hasChildNodes()) {
isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
offset = 0;
if (isAfterLastNodeInContainer && container.nodeType == 3) {
offset = container.nodeValue.length;
} else {
offset = 0;
}
}
// Get editable root node normaly the body element but sometimes a div or span
@ -18025,7 +18915,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// If editable root isn't block nor the root of the editor
if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
if (!newBlockName || evt.shiftKey) {
if (!newBlockName || shiftKey) {
insertBr();
}
@ -18035,7 +18925,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
// Wrap the current node and it's sibling in a default block if it's needed.
// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
// This won't happen if root blocks are disabled or the shiftKey is pressed
if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
}
@ -18047,26 +18937,40 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
// Handle enter inside an empty list item
if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
// Let the list plugin or browser handle nested lists for now
if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
return false;
// 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) {
insertBr();
return;
}
handleEmptyListItem();
return;
// Handle enter inside an empty list item
if (dom.isEmpty(parentBlock)) {
// Let the list plugin or browser handle nested lists for now
if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
return false;
}
handleEmptyListItem();
return;
}
}
// Don't split PRE tags but insert a BR instead easier when writing code samples etc
if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
if (!evt.shiftKey) {
if (!shiftKey) {
insertBr();
return;
}
} else {
// If no root block is configured then insert a BR by default or if the shiftKey is pressed
if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
insertBr();
return;
}
@ -18091,9 +18995,12 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
} else {
dom.insertAfter(newBlock, parentBlock);
}
moveToCaretPosition(newBlock);
} else if (isCaretAtStartOrEndOfBlock(true)) {
// Insert new block before
newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
renderBlockOnIE(newBlock);
} else {
// Extract after fragment and insert it after the current block
tmpRng = rng.cloneRange();
@ -18102,10 +19009,12 @@ tinymce.onAddEditor.add(function(tinymce, ed) {
trimLeadingLineBreaks(fragment);
newBlock = fragment.firstChild;
dom.insertAfter(fragment, parentBlock);
trimInlineElementsOnLeftSideOfBlock(newBlock);
addBrToBlockIfNeeded(parentBlock);
moveToCaretPosition(newBlock);
}
dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
moveToCaretPosition(newBlock);
undoManager.add();
}