app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.0.10 vs app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.0.11

- old
+ new

@@ -1,6 +1,6 @@ -// 4.0.10 (2013-10-28) +// 4.0.11 (2013-11-20) /** * Compiled inline version. (Library mode) */ @@ -188,42 +188,46 @@ */ parse: function(css) { var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter; var urlConverterScope = settings.url_converter_scope || this; - function compress(prefix, suffix) { + function compress(prefix, suffix, noJoin) { var top, right, bottom, left; - // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> - // So lets asume it shouldn't be there - if (styles['border-image'] === 'none') { - delete styles['border-image']; - } - - // Get values and check it it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) { return; } right = styles[prefix + '-right' + suffix]; - if (top != right) { + if (!right) { return; } bottom = styles[prefix + '-bottom' + suffix]; - if (right != bottom) { + if (!bottom) { return; } left = styles[prefix + '-left' + suffix]; - if (bottom != left) { + if (!left) { return; } - // Compress - styles[prefix + suffix] = left; + var box = [top, right, bottom, left]; + i = box.length - 1; + while (i--) { + if (box[i] !== box[i + 1]) { + break; + } + } + + if (i > -1 && noJoin) { + return; + } + + styles[prefix + suffix] = i == -1 ? box[0] : box.join(' '); delete styles[prefix + '-top' + suffix]; delete styles[prefix + '-right' + suffix]; delete styles[prefix + '-bottom' + suffix]; delete styles[prefix + '-left' + suffix]; } @@ -232,11 +236,11 @@ * Checks if the specific style can be compressed in other words if all border-width are equal. */ function canCompress(key) { var value = styles[key], i; - if (!value || value.indexOf(' ') < 0) { + if (!value) { return; } value = value.split(' '); i = value.length; @@ -355,13 +359,12 @@ styles[name] = isEncoded ? decode(value, true) : value; } styleRegExp.lastIndex = matches.index + matches[0].length; } - // Compress the styles to reduce it's size for example IE will expand styles - compress("border", ""); + compress("border", "", true); compress("border", "-width"); compress("border", "-color"); compress("border", "-style"); compress("padding", ""); compress("margin", ""); @@ -369,10 +372,16 @@ // Remove pointless border, IE produces these if (styles.border === 'medium none') { delete styles.border; } + + // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> + // So lets asume it shouldn't be there + if (styles['border-image'] === 'none') { + delete styles['border-image']; + } } return styles; }, @@ -2623,11 +2632,11 @@ * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** - * This class contains various environment constrants like browser versions etc. + * This class contains various environment constants like browser versions etc. * Normally you don't want to sniff specific browser versions but sometimes you have * to when it's impossible to feature detect. So use this with care. * * @class tinymce.Env * @static @@ -5144,13 +5153,18 @@ /** * Loads a language pack for the specified add-on. * * @method requireLangPack * @param {String} name Short name of the add-on. + * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. */ - requireLangPack: function(name) { + requireLangPack: function(name, languages) { if (AddOnManager.language && AddOnManager.languageLoad !== false) { + if (languages && new RegExp('([, ]|\\b)' + AddOnManager.language + '([, ]|\\b)').test(languages) === false) { + return; + } + ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.language + '.js'); } }, /** @@ -6866,11 +6880,12 @@ var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name; var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded; var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns; var attributesRequired, attributesDefault, attributesForced; var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0; - var decode = Entities.decode, fixSelfClosing, filteredAttrs = Tools.makeMap('src,href'); + var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href'); + var scriptUriRegExp = /(java|vb)script:/i; function processEndTag(name) { var pos, i; // Find position of parent of the same type @@ -6932,13 +6947,26 @@ if (attrRule.validValues && !(value in attrRule.validValues)) { return; } } - if (filteredAttrs[name] && !settings.allow_script_urls) { - if (/(java|vb)script:/i.test(decodeURIComponent(value.replace(trimRegExp, '')))) { - return; + // Block any javascript: urls + if (filteredUrlAttrs[name] && !settings.allow_script_urls) { + var uri = value.replace(trimRegExp, ''); + + try { + // Might throw malformed URI sequence + uri = decodeURIComponent(uri); + if (scriptUriRegExp.test(uri)) { + return; + } + } catch (ex) { + // Fallback to non UTF-8 decoder + uri = unescape(uri); + if (scriptUriRegExp.test(uri)) { + return; + } } } // Add attribute to list and map attrList.map[name] = value; @@ -9225,11 +9253,11 @@ 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 (Env.mac ? e.ctrlKey || e.metaKey : e.ctrlKey) && !e.altKey; + return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey; } }; }); // Included from: js/tinymce/classes/dom/ControlSelection.js @@ -9770,21 +9798,28 @@ } Selection.prototype = { /** * Move the selection cursor range to the specified node and offset. + * If there is no node specified it will move it to the first suitable location within the body. * * @method setCursorLocation - * @param {Node} node Node to put the cursor in. - * @param {Number} offset Offset from the start of the node to put the cursor at. + * @param {Node} node Optional node to put the cursor in. + * @param {Number} offset Optional offset from the start of the node to put the cursor at. */ setCursorLocation: function(node, offset) { var self = this, rng = self.dom.createRng(); - rng.setStart(node, offset); - rng.setEnd(node, offset); - self.setRng(rng); - self.collapse(false); + + if (!node) { + self._moveEndPoint(rng, self.editor.getBody(), true); + self.setRng(rng); + } else { + rng.setStart(node, offset); + rng.setEnd(node, offset); + self.setRng(rng); + self.collapse(false); + } }, /** * Returns the selected contents using the DOM serializer passed in to this class. * @@ -10390,58 +10425,15 @@ * @example * // Select the first paragraph in the active editor * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); */ select: function(node, content) { - var self = this, dom = self.dom, rng = dom.createRng(), idx, nonEmptyElementsMap; + var self = this, dom = self.dom, rng = dom.createRng(), idx; // Clear stored range set by FocusManager self.lastFocusBookmark = null; - nonEmptyElementsMap = dom.schema.getNonEmptyElements(); - - function setPoint(node, start) { - var root = node, walker = new TreeWalker(node, root); - - do { - // Text node - if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) { - if (start) { - rng.setStart(node, 0); - } else { - rng.setEnd(node, node.nodeValue.length); - } - - return; - } - - // BR/IMG/INPUT elements - if (nonEmptyElementsMap[node.nodeName]) { - if (start) { - rng.setStartBefore(node); - } else { - if (node.nodeName == 'BR') { - rng.setEndBefore(node); - } else { - rng.setEndAfter(node); - } - } - - return; - } - } while ((node = (start ? walker.next() : walker.prev()))); - - // Failed to find any text node or other suitable location then move to the root of body - if (root.nodeName == 'BODY') { - if (start) { - rng.setStart(root, 0); - } else { - rng.setEnd(root, root.childNodes.length); - } - } - } - if (node) { if (!content && self.controlSelection.controlSelect(node)) { return; } @@ -10449,12 +10441,12 @@ rng.setStart(node.parentNode, idx); rng.setEnd(node.parentNode, idx + 1); // Find first/last text node or BR element if (content) { - setPoint(node, 1); - setPoint(node); + self._moveEndPoint(rng, node, true); + self._moveEndPoint(rng, node); } self.setRng(rng); } @@ -11101,10 +11093,63 @@ if (y < viewPort.y || y + 25 > viewPortY + viewPortH) { self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25); } }, + _moveEndPoint: function(rng, node, start) { + var root = node, walker = new TreeWalker(node, root); + var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements(); + + do { + // Text node + if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) { + if (start) { + rng.setStart(node, 0); + } else { + rng.setEnd(node, node.nodeValue.length); + } + + return; + } + + // BR/IMG/INPUT elements + if (nonEmptyElementsMap[node.nodeName]) { + if (start) { + rng.setStartBefore(node); + } else { + if (node.nodeName == 'BR') { + rng.setEndBefore(node); + } else { + rng.setEndAfter(node); + } + } + + return; + } + + // Found empty text block old IE can place the selection inside those + if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) { + if (start) { + rng.setStart(node, 0); + } else { + rng.setEnd(node, 0); + } + + return; + } + } while ((node = (start ? walker.next() : walker.prev()))); + + // Failed to find any text node or other suitable location then move to the root of body + if (root.nodeName == 'BODY') { + if (start) { + rng.setStart(root, 0); + } else { + rng.setEnd(root, root.childNodes.length); + } + } + }, + destroy: function() { this.win = null; this.controlSelection.destroy(); } }; @@ -14244,10 +14289,19 @@ node = node.nextSibling; } } + // Old IE versions doesn't properly render blocks with br elements in them + // For example <p><br></p> wont be rendered correctly in a contentEditable area + // until you remove the br producing <p></p> + if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) { + if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') { + dom.remove(parentBlock.firstChild); + } + } + if (root.nodeName == 'LI') { var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild); if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) { root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild); @@ -15116,21 +15170,32 @@ failed = TRUE; } // Present alert message about clipboard access not being available if (failed || !doc.queryCommandSupported(command)) { - editor.windowManager.alert( + var msg = editor.translate( "Your browser doesn't support direct access to the clipboard. " + "Please use the Ctrl+X/C/V keyboard shortcuts instead." ); + + if (Env.mac) { + msg = msg.replace(/Ctrl\+/g, '\u2318+'); + } + + editor.windowManager.alert(msg); } }, // Override unlink command unlink: function(command) { if (selection.isCollapsed()) { - selection.select(selection.getNode()); + var elm = selection.getNode(); + if (elm.tagName == 'A') { + editor.dom.remove(elm, true); + } + + return; } execNativeCommand(command); selection.collapse(FALSE); }, @@ -15288,11 +15353,11 @@ } // Setup parser and serializer parser = editor.parser; serializer = new Serializer({}, editor.schema); - bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;</span>'; + bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#200B;</span>'; // Run beforeSetContent handlers on the HTML to be inserted args = {content: value, format: 'html', selection: true}; editor.fire('BeforeSetContent', args); value = args.content; @@ -15304,15 +15369,20 @@ // Replace the caret marker with a span bookmark element value = value.replace(/\{\$caret\}/, bookmarkHtml); // If selection is at <body>|<p></p> then move it into <body><p>|</p> + rng = selection.getRng(); + var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); var body = editor.getBody(); - if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { - body.firstChild.appendChild(dom.doc.createTextNode('\u00a0')); - selection.select(body.firstChild, true); - dom.remove(body.firstChild.lastChild); + if (caretElement === body && selection.isCollapsed()) { + if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { + rng = dom.createRng(); + rng.setStart(body.firstChild, 0); + rng.setEnd(body.firstChild, 0); + selection.setRng(rng); + } } // Insert node maker where we will insert the new HTML and get it's parent if (!selection.isCollapsed()) { editor.getDoc().execCommand('Delete', false, null); @@ -15527,23 +15597,49 @@ formatter.apply('link', value, anchor); } }, selectAll: function() { - var root = dom.getRoot(), rng = dom.createRng(); + var root = dom.getRoot(), rng; - // Old IE does a better job with selectall than new versions if (selection.getRng().setStart) { + rng = dom.createRng(); rng.setStart(root, 0); rng.setEnd(root, root.childNodes.length); - selection.setRng(rng); } else { - execNativeCommand('SelectAll'); + // IE will render it's own root level block elements and sometimes + // even put font elements in them when the user starts typing. So we need to + // move the selection to a more suitable element from this: + // <body>|<p></p></body> to this: <body><p>|</p></body> + rng = selection.getRng(); + if (!rng.item) { + rng.moveToElementText(root); + rng.select(); + } } }, + "delete": function() { + execNativeCommand("Delete"); + + // Check if body is empty after the delete call if so then set the contents + // to an empty string and move the caret to any block produced by that operation + // this fixes the issue with root blocks not being properly produced after a delete call on IE + var body = editor.getBody(); + + if (dom.isEmpty(body)) { + editor.setContent(''); + + if (body.firstChild && dom.isBlock(body.firstChild)) { + editor.selection.setCursorLocation(body.firstChild, 0); + } else { + editor.selection.setCursorLocation(body, 0); + } + } + }, + mceNewDocument: function() { editor.setContent(''); } }); @@ -15671,19 +15767,25 @@ if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { self.source = url; return; } + var isProtocolRelative = url.indexOf('//') === 0; + // Absolute path with no host, fake host and protocol - if (url.indexOf('/') === 0 && url.indexOf('//') !== 0) { + if (url.indexOf('/') === 0 && !isProtocolRelative) { url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url; } // Relative path http:// or protocol relative //path if (!/^[\w\-]*:?\/\//.test(url)) { base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; - url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url); + if (settings.base_uri.protocol === "") { + url = '//mce_host' + self.toAbsPath(base_url, url); + } else { + url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url); + } } // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something @@ -15720,10 +15822,14 @@ } self.source = ''; } + if (isProtocolRelative) { + self.protocol = ''; + } + //t.path = t.path || '/'; } URI.prototype = { /** @@ -15765,18 +15871,19 @@ } uri = new URI(uri, {base_uri: self}); // Not on same domain/port or protocol - if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || self.protocol != uri.protocol) { + if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || + (self.protocol != uri.protocol && uri.protocol !== "")) { return uri.getURI(); } var tu = self.getURI(), uu = uri.getURI(); // Allow usage of the base_uri when relative_urls = true - if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { + if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { return tu; } output = self.toRelPath(self.path, uri.path); @@ -15944,10 +16051,12 @@ s = ''; if (!noProtoHost) { if (self.protocol) { s += self.protocol + '://'; + } else { + s += '//'; } if (self.userInfo) { s += self.userInfo + '@'; } @@ -17491,50 +17600,55 @@ * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { - var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect; + var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round; + // Use Math.round on all values on IE < 9 + round = !document.createRange ? Math.round : function(value) { + return value; + }; + style = self.getEl().style; rect = self._layoutRect; lastRepaintRect = self._lastRepaintRect || {}; borderBox = self._borderBox; borderW = borderBox.left + borderBox.right; borderH = borderBox.top + borderBox.bottom; if (rect.x !== lastRepaintRect.x) { - style.left = rect.x + 'px'; + style.left = round(rect.x) + 'px'; lastRepaintRect.x = rect.x; } if (rect.y !== lastRepaintRect.y) { - style.top = rect.y + 'px'; + style.top = round(rect.y) + 'px'; lastRepaintRect.y = rect.y; } if (rect.w !== lastRepaintRect.w) { - style.width = (rect.w - borderW) + 'px'; + style.width = round(rect.w - borderW) + 'px'; lastRepaintRect.w = rect.w; } if (rect.h !== lastRepaintRect.h) { - style.height = (rect.h - borderH) + 'px'; + style.height = round(rect.h - borderH) + 'px'; lastRepaintRect.h = rect.h; } // Update body if needed if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { bodyStyle = self.getEl('body').style; - bodyStyle.width = (rect.innerW) + 'px'; + bodyStyle.width = round(rect.innerW) + 'px'; lastRepaintRect.innerW = rect.innerW; } if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { bodyStyle = bodyStyle || self.getEl('body').style; - bodyStyle.height = (rect.innerH) + 'px'; + bodyStyle.height = round(rect.innerH) + 'px'; lastRepaintRect.innerH = rect.innerH; } self._lastRepaintRect = lastRepaintRect; self.fire('repaint', {}, false); @@ -20874,18 +20988,23 @@ * * @method remove * @return {tinymce.ui.Control} Current control instance. */ remove: function() { - var self = this; + var self = this, prefix = self.classPrefix; self.dragHelper.destroy(); self._super(); if (self.statusbar) { this.statusbar.remove(); } + + if (self._fullscreen) { + DomUtils.removeClass(document.documentElement, prefix + 'fullscreen'); + DomUtils.removeClass(document.body, prefix + 'fullscreen'); + } } }); return Window; }); @@ -21493,52 +21612,65 @@ body.appendChild(contents); return selection.serializer.serialize(body, {format: 'html'}); } function allContentsSelected(rng) { + if (!rng.setStart) { + if (rng.item) { + return false; + } + + var bodyRng = rng.duplicate(); + bodyRng.moveToElementText(editor.getBody()); + return RangeUtils.compareRanges(rng, bodyRng); + } + var selection = serializeRng(rng); var allRng = dom.createRng(); allRng.selectNode(editor.getBody()); var allSelection = serializeRng(allRng); return selection === allSelection; } editor.on('keydown', function(e) { - var keyCode = e.keyCode, isCollapsed; + var keyCode = e.keyCode, isCollapsed, body; // 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(); + body = editor.getBody(); // Selection is collapsed but the editor isn't empty - if (isCollapsed && !dom.isEmpty(editor.getBody())) { + if (isCollapsed && !dom.isEmpty(body)) { return; } - // IE deletes all contents correctly when everything is selected - if (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 e.preventDefault(); editor.setContent(''); - editor.selection.setCursorLocation(editor.getBody(), 0); + + if (body.firstChild && dom.isBlock(body.firstChild)) { + editor.selection.setCursorLocation(body.firstChild, 0); + } else { + editor.selection.setCursorLocation(body, 0); + } + editor.nodeChanged(); } }); } /** * WebKit doesn't select all the nodes in the body when you press Ctrl+A. + * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438 * This selects the whole body so that backspace/delete logic will delete everything */ function selectAll() { editor.on('keydown', function(e) { if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { @@ -22263,10 +22395,25 @@ } }); } } + /** + * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. + * You might then loose all your work so we need to block that behavior and replace it with our own. + */ + function blockCmdArrowNavigation() { + if (Env.mac) { + editor.on('keydown', function(e) { + if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) { + e.preventDefault(); + editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word'); + } + }); + } + } + // All browsers disableBackspaceIntoATable(); removeBlockQuoteOnBackSpace(); emptyEditorWhenDeleting(); normalizeSelection(); @@ -22281,10 +22428,11 @@ // iOS if (Env.iOS) { selectionChangeNodeChanged(); restoreFocusOnKeyDown(); + bodyHeight(); } else { selectAll(); } } @@ -22302,19 +22450,24 @@ if (Env.ie >= 11) { bodyHeight(); } + if (Env.ie) { + selectAll(); + } + // Gecko if (isGecko) { removeHrOnBackspace(); focusBody(); removeStylesWhenDeletingAcrossBlockElements(); setGeckoEditingOptions(); addBrAfterLastLinks(); removeGhostSelection(); showBrokenImageIcon(); + blockCmdArrowNavigation(); } }; }); // Included from: js/tinymce/classes/util/Observable.js @@ -22592,11 +22745,11 @@ var self = this, shortcuts = {}; editor.on('keyup keypress keydown', function(e) { if (e.altKey || e.ctrlKey || e.metaKey) { each(shortcuts, function(shortcut) { - var ctrlKey = Env.mac ? (e.ctrlKey || e.metaKey) : e.ctrlKey; + var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey; if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { return; } @@ -23198,13 +23351,10 @@ } // Create all plugins each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); - // Enables users to override the control factory - self.fire('BeforeRenderUI'); - // Measure box if (settings.render_ui && self.theme) { self.orgDisplay = elm.style.display; if (typeof settings.theme != "function") { @@ -24214,11 +24364,11 @@ */ hide: function() { var self = this, doc = self.getDoc(); // Fixed bug where IE has a blinking cursor left from the editor - if (ie && doc) { + if (ie && doc && !self.inline) { doc.execCommand('SelectAll'); } // We must save before we hide so Safari doesn't crash self.save(); @@ -24385,10 +24535,11 @@ if (content.length === 0 || /^\s+$/.test(content)) { forcedRootBlockName = self.settings.forced_root_block; // Check if forcedRootBlock is configured and that the block is a valid child of the body if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { + // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">'; content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); } else if (!ie || ie < 11) { // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret content = '<br data-mce-bogus="1">'; @@ -24695,11 +24846,11 @@ DOM.remove(self.getElement().nextSibling); } // Fixed bug where IE has a blinking cursor left from the editor var doc = self.getDoc(); - if (ie && doc) { + if (ie && doc && !self.inline) { doc.execCommand('SelectAll'); } // We must save before we hide so Safari doesn't crash self.save(); @@ -25011,10 +25162,22 @@ function isUIElement(elm) { return !!DOMUtils.DOM.getParent(elm, FocusManager.isEditorUIElement); } + function isNodeInBody(node) { + var body = editor.getBody(); + + while (node) { + if (node == body) { + return true; + } + + node = node.parentNode; + } + } + editor.on('init', function() { // On IE take selection snapshot onbeforedeactivate if ("onbeforedeactivate" in document && Env.ie < 11) { editor.dom.bind(editor.getBody(), 'beforedeactivate', function() { var ieSelection = editor.getDoc().selection; @@ -25026,28 +25189,18 @@ } }); } else if (editor.inline || Env.ie > 10) { // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes editor.on('nodechange keyup', function() { - var isInBody, node = document.activeElement; + var node = document.activeElement; // IE 11 reports active element as iframe not body of iframe if (node && node.id == editor.id + '_ifr') { node = editor.getBody(); } - // Check if selection is within editor body - while (node) { - if (node == editor.getBody()) { - isInBody = true; - break; - } - - node = node.parentNode; - } - - if (isInBody) { + if (isNodeInBody(node)) { lastRng = editor.selection.getRng(); } }); // Handles the issue with WebKit not retaining selection within inline document @@ -25103,11 +25256,18 @@ } lastRng = null; }); - editor.on('focusout', function() { + editor.on('focusout', function(e) { + // Moving focus to elements within the body that have a control seleciton on IE + // will fire an focusout event so we need to check if the event is fired on the body + // or on a sub element see #6456 + if (e.target !== editor.getBody() && isNodeInBody(e.target)) { + return; + } + editor.selection.lastFocusBookmark = createBookmark(lastRng); window.setTimeout(function() { var focusedEditor = editorManager.focusedEditor; @@ -25192,19 +25352,19 @@ * Minor version of TinyMCE build. * * @property minorVersion * @type String */ - minorVersion : '0.10', + minorVersion : '0.11', /** * Release date of TinyMCE build. * * @property releaseDate * @type String */ - releaseDate: '2013-10-28', + releaseDate: '2013-11-20', /** * Collection of editor instances. * * @property editors @@ -25252,11 +25412,15 @@ // Get base where the tinymce script is located var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var src = scripts[i].src; - if (/tinymce(\.jquery|)(\.min|\.dev|)\.js/.test(src)) { + // Script types supported: + // tinymce.js tinymce.min.js tinymce.dev.js + // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js + // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js + if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) { if (src.indexOf('.min') != -1) { suffix = '.min'; } baseURL = src.substring(0, src.lastIndexOf('/')); @@ -26943,10 +27107,47 @@ self.addClass('btn-' + size); } }, /** + * Sets/gets the current button icon. + * + * @method icon + * @param {String} [icon] New icon identifier. + * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance. + */ + icon: function(icon) { + var self = this, prefix = self.classPrefix; + + if (typeof(icon) == 'undefined') { + return self.settings.icon; + } + + self.settings.icon = icon; + icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; + + if (self._rendered) { + var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; + + if (icon) { + if (!iconElm || iconElm != btnElm.firstChild) { + iconElm = document.createElement('i'); + btnElm.insertBefore(iconElm, btnElm.firstChild); + } + + iconElm.className = icon; + } else if (iconElm) { + btnElm.removeChild(iconElm); + } + + self.text(self._text); // Set text again to fix whitespace between icon + text + } + + return self; + }, + + /** * Repaints the button for example after it's been resizes by a layout engine. * * @method repaint */ repaint: function() { @@ -28413,11 +28614,11 @@ ratio = availableSpace / totalFlex; for (i = 0, l = maxSizeItems.length; i < l; i++) { ctrl = maxSizeItems[i]; ctrlLayoutRect = ctrl.layoutRect(); maxSize = ctrlLayoutRect[maxSizeName]; - size = ctrlLayoutRect[minSizeName] + Math.ceil(ctrlLayoutRect.flex * ratio); + size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio; if (size > maxSize) { availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); totalFlex -= ctrlLayoutRect.flex; ctrlLayoutRect.flex = 0; @@ -28472,11 +28673,11 @@ rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; } // Calculate new size based on flex if (ctrlLayoutRect.flex > 0) { - size += Math.ceil(ctrlLayoutRect.flex * ratio); + size += ctrlLayoutRect.flex * ratio; } rect[sizeName] = size; rect[posName] = pos; ctrl.layoutRect(rect); @@ -28595,11 +28796,11 @@ return ''; } // Default preview if (!previewStyles) { - previewStyles = 'font-family font-size font-weight text-decoration ' + + previewStyles = 'font-family font-size font-weight font-style text-decoration ' + 'text-transform color background-color border border-radius'; } // Removes any variables since these can't be previewed function removeVars(val) { @@ -29349,11 +29550,11 @@ } // Calculate new column widths based on flex values var ratio = availableWidth / totalFlex; for (x = 0; x < cols; x++) { - colWidths[x] += flexWidths ? Math.ceil(flexWidths[x] * ratio) : ratio; + colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio; } // Move/resize controls posY = contPaddingBox.top; for (y = 0; y < rows; y++) { @@ -29938,11 +30139,11 @@ var self = this, i, children; if (self._rendered) { children = self.getEl('open').getElementsByTagName('span'); for (i = 0; i < children.length; i++) { - children[i].innerHTML = self.encode(text); + children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text); } } return this._super(text); }, @@ -30104,12 +30305,13 @@ * @class tinymce.ui.MenuItem * @extends tinymce.ui.Control */ define("tinymce/ui/MenuItem", [ "tinymce/ui/Widget", - "tinymce/ui/Factory" -], function(Widget, Factory) { + "tinymce/ui/Factory", + "tinymce/Env" +], function(Widget, Factory, Env) { "use strict"; return Widget.extend({ Defaults: { border: 0, @@ -30299,28 +30501,36 @@ * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text); - var icon = self.settings.icon, image = ''; + var icon = self.settings.icon, image = '', shortcut = settings.shortcut; if (icon) { self.parent().addClass('menu-has-icons'); } if (settings.image) { icon = 'none'; image = ' style="background-image: url(\'' + settings.image + '\')"'; } + if (shortcut && Env.mac) { + // format shortcut for Mac + shortcut = shortcut.replace(/ctrl\+alt\+/i, '&#x2325;&#x2318;'); // ctrl+cmd + shortcut = shortcut.replace(/ctrl\+/i, '&#x2318;'); // ctrl symbol + shortcut = shortcut.replace(/alt\+/i, '&#x2325;'); // cmd symbol + shortcut = shortcut.replace(/shift\+/i, '&#x21E7;'); // shift symbol + } + icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); return ( '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' + (text !== '-' ? '<i class="' + icon + '"' + image + '></i>&nbsp;' : '') + (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') + - (settings.shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + - settings.shortcut + '</div>' : '') + + (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + + shortcut + '</div>' : '') + (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') + '</div>' ); }, \ No newline at end of file