From d86aa138d09c772f9506bf3c0ca2c65a8f33bdfa Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Mon, 25 Jun 2012 06:08:59 -0400 Subject: [PATCH] Update to TinyMCE 3.5.4.1 --- program/js/tiny_mce/tiny_mce_src.js | 570 ++++++++++++++++++++++++++++++++++++++++---------------- 1 files changed, 403 insertions(+), 167 deletions(-) diff --git a/program/js/tiny_mce/tiny_mce_src.js b/program/js/tiny_mce/tiny_mce_src.js index 1f1fc02..32129db 100644 --- a/program/js/tiny_mce/tiny_mce_src.js +++ b/program/js/tiny_mce/tiny_mce_src.js @@ -6,9 +6,9 @@ var tinymce = { majorVersion : '3', - minorVersion : '5.2', + minorVersion : '5.4.1', - releaseDate : '2012-05-31', + releaseDate : '2012-06-24', _init : function() { var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; @@ -1083,6 +1083,10 @@ modifierPressed: function (e) { return e.shiftKey || e.ctrlKey || e.altKey; + }, + + metaKeyPressed: function(e) { + return tinymce.isMac ? e.metaKey : e.ctrlKey; } }; })(tinymce); @@ -1097,6 +1101,12 @@ // Ignore } } + + function getDocumentMode() { + var documentMode = editor.getDoc().documentMode; + + return documentMode ? documentMode : 6; + }; function cleanupStylesWhenDeleting() { function removeMergedFormatSpans(isDelete) { @@ -1157,74 +1167,58 @@ }; 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);//console.log(selection, "----", allSelection); + 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(); - - // 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; - } - - // If the childNode isn't the container node then break in case <p><span>A</span>[X]</p> - if (childNode !== container) { - return false; - } - - container = container.parentNode; - } - - return true; - }; - - editor.onKeyDown.addToTop(function(editor, e) { - var rng, keyCode = e.keyCode; - + // Empty the editor if it's needed for example backspace at <p><b>|</b></p> if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { - rng = selection.getRng(true); + isCollapsed = editor.selection.isCollapsed(); - if (isAtStartEndOfBody(rng, true) && isAtStartEndOfBody(rng, false) && - (rng.collapsed || dom.findCommonAncestor(getEndPointNode(rng, true), getEndPointNode(rng)) === dom.getRoot())) { - editor.setContent(''); - editor.nodeChanged(); - e.preventDefault(); + // Selection is collapsed but the editor isn't empty + if (isCollapsed && !dom.isEmpty(editor.getBody())) { + return; } + + // IE deletes all contents correctly when everything is selected + if (tinymce.isIE && !isCollapsed) { + return; + } + + // 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(); + } + }); + }; + + function selectAll() { + editor.onKeyDown.add(function(editor, e) { + if (e.keyCode == 65 && VK.metaKeyPressed(e)) { + e.preventDefault(); + editor.execCommand('SelectAll'); } }); }; @@ -1393,16 +1387,15 @@ } 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 @@ -1608,13 +1601,105 @@ editor.onSetContent.add(repaint); }; - function deleteImageOnBackSpace() { + function deleteControlItemOnBackSpace() { editor.onKeyDown.add(function(editor, e) { - if (!e.isDefaultPrevented() && e.keyCode == 8 && selection.getNode().nodeName == 'IMG') { + var rng; + + if (!e.isDefaultPrevented() && 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 = ''; + tinymce.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 mouseDownImg, startX, startY, startW, startH; + + if (!settings.object_resizing || settings.webkit_fake_resize === false) { + return; + } + + editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); + + function resizeImage(e) { + var deltaX, deltaY, ratio, width, height; + + if (mouseDownImg) { + deltaX = e.screenX - startX; + deltaY = e.screenY - startY; + ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); + + // Only update styles if the user draged one pixel or more + if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { + // Constrain proportions + width = Math.round(startW * ratio); + height = Math.round(startH * ratio); + + // Resize by using style or attribute + if (mouseDownImg.style.width) { + dom.setStyle(mouseDownImg, 'width', width); + } else { + dom.setAttrib(mouseDownImg, 'width', width); + } + + // Resize by using style or attribute + if (mouseDownImg.style.height) { + dom.setStyle(mouseDownImg, 'height', height); + } else { + dom.setAttrib(mouseDownImg, 'height', height); + } + + if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { + dom.addClass(editor.getBody(), 'mceResizeImages'); + } + } + } + }; + + editor.onMouseDown.add(function(editor, e) { + var target = e.target; + + if (target.nodeName == "IMG") { + mouseDownImg = target; + startX = e.screenX; + startY = e.screenY; + startW = mouseDownImg.clientWidth; + startH = mouseDownImg.clientHeight; + dom.bind(editor.getDoc(), 'mousemove', resizeImage); e.preventDefault(); - editor.undoManager.beforeChange(); - dom.remove(selection.getNode()); - editor.undoManager.add(); + } + }); + + // Unbind events on node change and restore resize cursor + editor.onNodeChange.add(function() { + if (mouseDownImg) { + mouseDownImg = null; + dom.unbind(editor.getDoc(), 'mousemove', resizeImage); + } + + if (selection.getNode().nodeName == "IMG") { + dom.addClass(editor.getBody(), 'mceResizeImages'); + } else { + dom.removeClass(editor.getBody(), 'mceResizeImages'); } }); }; @@ -1635,6 +1720,9 @@ // iOS if (tinymce.isIDevice) { selectionChangeNodeChanged(); + } else { + fakeImageResize(); + selectAll(); } } @@ -1644,7 +1732,8 @@ ensureBodyHasRoleApplication(); addNewLinesBeforeBrInPre(); removePreSerializedStylesWhenSelectingControls(); - deleteImageOnBackSpace(); + deleteControlItemOnBackSpace(); + renderEmptyBlocksFix(); } // Gecko @@ -2107,9 +2196,9 @@ if (!html5) { html5 = mapCache.html5 = unpack({ - A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title', + 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', + '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' @@ -2218,7 +2307,8 @@ '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][]' ); } @@ -2399,7 +2489,7 @@ // Setup map objects whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script 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'); + 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 ' + @@ -4657,7 +4747,7 @@ // 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); @@ -5499,6 +5589,32 @@ 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; @@ -5622,13 +5738,13 @@ // 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); }); } @@ -9069,7 +9185,7 @@ }, 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 @@ -9080,6 +9196,9 @@ 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 @@ -9106,31 +9225,34 @@ }, 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; } }, @@ -11475,7 +11597,7 @@ 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>' : ''); @@ -12183,6 +12305,16 @@ 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; }, @@ -12203,6 +12335,7 @@ t.isMenuVisible = 0; t.onHideMenu.dispatch(); + t.keyboardNav.destroy(); } }, @@ -12267,15 +12400,6 @@ } 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);}); @@ -12317,11 +12441,17 @@ }, 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); @@ -12965,6 +13095,8 @@ self.contentCSS = []; + self.contentStyles = []; + // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic self.setupEvents(); @@ -13156,12 +13288,6 @@ 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); @@ -13321,7 +13447,7 @@ }, 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) { @@ -13430,6 +13556,12 @@ 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); @@ -13491,6 +13623,17 @@ self.execCallback('init_instance_callback', self); 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) { @@ -14119,7 +14262,7 @@ return; case 'A': - if (!elm.href) { + if (!dom.getAttrib(elm, 'href', false)) { value = dom.getAttrib(elm, 'name') || elm.id; cls = 'mceItemAnchor'; @@ -14148,13 +14291,12 @@ // 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); @@ -14356,8 +14498,10 @@ // 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(); + } }); } @@ -14423,9 +14567,12 @@ 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(); } @@ -14469,7 +14616,7 @@ 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 @@ -14896,6 +15043,10 @@ 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); }, @@ -15284,7 +15435,7 @@ 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; @@ -15312,6 +15463,7 @@ rng.moveToElementText(node); } + isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); tmpRng = rng.duplicate(); tmpRng.collapse(true); startOffset = tmpRng.move('character', offset) * -1; @@ -15342,28 +15494,30 @@ } } - 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(); } }; @@ -17949,7 +18103,7 @@ 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, @@ -17959,11 +18113,48 @@ 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); + } + } + }; + // 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; @@ -17980,7 +18171,7 @@ break; } - if (/^(BR|IMG)$/.test(node.nodeName)) { + if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { rng.setStartBefore(node); rng.setEndBefore(node); break; @@ -18077,6 +18268,11 @@ 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); @@ -18084,20 +18280,34 @@ // 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(); } } @@ -18182,6 +18392,7 @@ } 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 @@ -18273,6 +18484,22 @@ 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'); @@ -18295,7 +18522,11 @@ 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 @@ -18376,9 +18607,12 @@ } 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(); @@ -18387,10 +18621,12 @@ 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(); } -- Gitblit v1.9.1