app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.1.2 vs app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.1.3

- old
+ new

@@ -1,6 +1,6 @@ -// 4.1.2 (2014-07-15) +// 4.1.3 (2014-07-29) /** * Compiled inline version. (Library mode) */ @@ -452,10 +452,11 @@ callbackList = events[id][name]; if (!callbackList) { events[id][name] = callbackList = [{func: callback, scope: scope}]; callbackList.fakeName = fakeName; callbackList.capture = capture; + //callbackList.callback = callback; // Add the nativeHandler to the callback list so that we can later unbind it callbackList.nativeHandler = nativeHandler; // Check if the target has native events support @@ -1448,11 +1449,11 @@ 'for': 'htmlFor', 'class': 'className', 'readonly': 'readOnly' }; var cssFix = { - float: 'cssFloat' + 'float': 'cssFloat' }; var attrHooks = {}, cssHooks = {}; function DomQuery(selector, context) { @@ -2776,27 +2777,27 @@ DomQuery.fn.init.prototype = DomQuery.fn; DomQuery.overrideDefaults = function(callback) { var defaults; - function jQuerySub(selector, context) { + function sub(selector, context) { defaults = defaults || callback(); if (arguments.length === 0) { selector = defaults.element; } if (!context) { context = defaults.context; } - return new jQuerySub.fn.init(selector, context); + return new sub.fn.init(selector, context); } - DomQuery.extend(jQuerySub, this); + DomQuery.extend(sub, this); - return jQuerySub; + return sub; }; function appendHooks(targetHooks, prop, hooks) { each(hooks, function(name, func) { targetHooks[name] = targetHooks[name] || {}; @@ -2851,11 +2852,13 @@ } }); } if (Env.ie && Env.ie < 9) { - cssFix.float = 'styleFloat'; + /*jshint sub:true */ + /*eslint dot-notation: 0*/ + cssFix['float'] = 'styleFloat'; appendHooks(cssHooks, 'set', { opacity: function(elm, value) { var style = elm.style; @@ -5302,10 +5305,14 @@ } elm = self.$$(elm); originalValue = elm.attr(name); + if (!elm.length) { + return; + } + hook = self.attrHooks[name]; if (hook && hook.set) { hook.set(elm, value, name); } else { elm.attr(name, value); @@ -5355,15 +5362,18 @@ getAttrib: function(elm, name, defaultVal) { var self = this, hook, value; elm = self.$$(elm); - hook = self.attrHooks[name]; - if (hook && hook.get) { - value = hook.get(elm, name); - } else { - value = elm.attr(name); + if (elm.length) { + hook = self.attrHooks[name]; + + if (hook && hook.get) { + value = hook.get(elm, name); + } else { + value = elm.attr(name); + } } if (typeof value == 'undefined') { value = defaultVal || ''; } @@ -5378,25 +5388,26 @@ * @param {Element/String} elm HTML element or element id to get x, y position from. * @param {Element} rootElm Optional root element to stop calculations at. * @return {object} Absolute position of the specified element object with x, y fields. */ getPos: function(elm, rootElm) { - var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos; + var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos; elm = self.get(elm); - rootElm = rootElm || doc.body; + rootElm = rootElm || body; if (elm) { // Use getBoundingClientRect if it exists since it's faster than looping offset nodes - if (rootElm === doc.body && elm.getBoundingClientRect) { + // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root + if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') { pos = elm.getBoundingClientRect(); - rootElm = self.boxModel ? doc.documentElement : doc.body; + rootElm = self.boxModel ? doc.documentElement : body; // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position - x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft; - y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop; + x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft; + y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop; return {x: x, y: y}; } offsetParent = elm; @@ -7463,11 +7474,11 @@ return false; } // Gecko doesn't support the "selectionchange" event if (!('onselectionchange' in editor.getDoc())) { - editor.on('NodeChange Click MouseUp KeyUp', function(e) { + editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) { var nativeRng, fakeRng; // Since DOM Ranges mutate on modification // of the DOM we need to clone it's contents nativeRng = editor.selection.getRng(); @@ -7493,18 +7504,25 @@ editor.on('contextmenu', function() { editor.fire('SelectionChange'); }); editor.on('SelectionChange', function() { - var startElm = editor.selection.getStart(); + var startElm = editor.selection.getStart(true); // Selection change might fire when focus is lost so check if the start is still within the body if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) { editor.nodeChanged({selectionChange: true}); } }); + // Fire an extra nodeChange on mouseup for compatibility reasons + editor.on('MouseUp', function(e) { + if (!e.isDefaultPrevented()) { + editor.nodeChanged(); + } + }); + /** * Distpaches out a onNodeChange event to all observers. This method should be called when you * need to update the UI states or element path etc. * * @method nodeChanged @@ -8071,11 +8089,11 @@ * @version 3.4 */ define("tinymce/html/Schema", [ "tinymce/util/Tools" ], function(Tools) { - var mapCache = {}; + var mapCache = {}, dummyObj = {}; var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; function split(items, delim) { return items ? items.split(delim || ' ') : []; } @@ -8092,15 +8110,15 @@ var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; function add(name, attributes, children) { var ni, i, attributesOrder, args = arguments; - function arrayToMap(array) { + function arrayToMap(array, obj) { var map = {}, i, l; for (i = 0, l = array.length; i < l; i++) { - map[array[i]] = {}; + map[array[i]] = obj || {}; } return map; } @@ -8125,11 +8143,11 @@ while (ni--) { attributesOrder = [].concat(globalAttributes, split(attributes)); schema[name[ni]] = { attributes: arrayToMap(attributesOrder), attributesOrder: attributesOrder, - children: arrayToMap(children) + children: arrayToMap(children, dummyObj) }; } } function addAttrs(name, attributes) { @@ -11848,10 +11866,11 @@ if (!isIE || selectedElm.nodeName == "TABLE") { showResizeRect(selectedElm); } editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); + dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style')); editor.nodeChanged(); } function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { var position, targetWidth, targetHeight, e, rect; @@ -11995,11 +12014,11 @@ } } } function updateResizeRect(e) { - var controlElm; + var startElm, controlElm; function isChildOrEqual(node, parent) { if (node) { do { if (node === parent) { @@ -12017,13 +12036,14 @@ controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0]; if (isChildOrEqual(controlElm, rootElement)) { disableGeckoResize(); + startElm = selection.getStart(true); - if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) { - if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) { + if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) { + if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) { showResizeRect(controlElm); return; } } } @@ -12179,11 +12199,11 @@ } }); } } - editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect); + editor.on('nodechange ResizeEditor', updateResizeRect); // Update resize rect while typing in a table editor.on('keydown keyup', function(e) { if (selectedElm && selectedElm.nodeName == "TABLE") { updateResizeRect(e); @@ -12851,13 +12871,14 @@ /** * Returns the start element of a selection range. If the start is in a text * node the parent element will be returned. * * @method getStart + * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. * @return {Element} Start element of selection range. */ - getStart: function() { + getStart: function(real) { var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; if (rng.duplicate || rng.item) { // Control selection, return first item if (rng.item) { @@ -12885,11 +12906,13 @@ return startElement; } else { startElement = rng.startContainer; if (startElement.nodeType == 1 && startElement.hasChildNodes()) { - startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + if (!real || !rng.collapsed) { + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + } } if (startElement && startElement.nodeType == 3) { return startElement.parentNode; } @@ -12901,13 +12924,14 @@ /** * Returns the end element of a selection range. If the end is in a text * node the parent element will be returned. * * @method getEnd + * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. * @return {Element} End element of selection range. */ - getEnd: function() { + getEnd: function(real) { var self = this, rng = self.getRng(), endElement, endOffset; if (rng.duplicate || rng.item) { if (rng.item) { return rng.item(0); @@ -12928,11 +12952,13 @@ } else { endElement = rng.endContainer; endOffset = rng.endOffset; if (endElement.nodeType == 1 && endElement.hasChildNodes()) { - endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; + if (!real || !rng.collapsed) { + endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; + } } if (endElement && endElement.nodeType == 3) { return endElement.parentNode; } @@ -13520,12 +13546,12 @@ } return; } - // BR/IMG/INPUT elements - if (nonEmptyElementsMap[node.nodeName]) { + // BR/IMG/INPUT elements but not table cells + if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) { if (start) { rng.setStartBefore(node); } else { if (node.nodeName == 'BR') { rng.setEndBefore(node); @@ -13977,12 +14003,12 @@ strikethrough: [ {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true}, {inline: 'strike', remove: 'all'} ], - forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false, remove_similar: true}, - hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false, remove_similar: true}, + forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true}, + hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true}, fontname: {inline: 'span', styles: {fontFamily: '%value'}}, fontsize: {inline: 'span', styles: {fontSize: '%value'}}, fontsize_class: {inline: 'span', attributes: {'class': '%value'}}, blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'}, subscript: {inline: 'sub'}, @@ -14314,26 +14340,16 @@ // Process siblings from range each(nodes, process); }); - // Wrap links inside as well, for example color inside a link when the wrapper is around the link - if (format.wrap_links === false) { + // Apply formats to links as well to get the color of the underline to change as well + if (format.links === true) { each(newWrappers, function(node) { function process(node) { - var i, currentWrapElm, children; - if (node.nodeName === 'A') { - currentWrapElm = dom.clone(wrapElm, FALSE); - newWrappers.push(currentWrapElm); - - children = grep(node.childNodes); - for (i = 0; i < children.length; i++) { - currentWrapElm.appendChild(children[i]); - } - - node.appendChild(currentWrapElm); + setElementFormat(node, format); } each(grep(node.childNodes), process); } @@ -14399,28 +14415,14 @@ each(formatList, function(format) { // Merge all children of similar type will move styles from child to parent // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> each(dom.select(format.inline, node), function(child) { - var parent; - if (isBookmarkNode(child)) { return; } - // When wrap_links is set to false we don't want - // to remove the format on children within links - if (format.wrap_links === false) { - parent = child.parentNode; - - do { - if (parent.nodeName === 'A') { - return; - } - } while ((parent = parent.parentNode)); - } - removeFormat(format, vars, child, format.exact ? child : null); }); }); // Remove child if direct parent is of same type @@ -14958,11 +14960,11 @@ ed.on('NodeChange', function(e) { var parents = getParents(e.element), matchedFormats = {}; // Ignore bogus nodes like the <a> tag created by moveStart() parents = Tools.grep(parents, function(node) { - return !node.getAttribute('data-mce-bogus'); + return node.nodeType == 1 && !node.getAttribute('data-mce-bogus'); }); // Check for new formats each(formatChangeData, function(callbacks, format) { each(parents, function(node) { @@ -15038,12 +15040,12 @@ }); // Initialize defaultFormats(); addKeyboardShortcuts(); - ed.on('BeforeGetContent', function() { - if (markCaretContainersBogus) { + ed.on('BeforeGetContent', function(e) { + if (markCaretContainersBogus && e.format != 'raw') { markCaretContainersBogus(); } }); ed.on('mouseup keydown', function(e) { if (disableCaretContainer) { @@ -15529,10 +15531,14 @@ endContainer: endContainer, endOffset: endOffset }; } + function isColorFormatAndAnchor(node, format) { + return format.links && node.tagName == 'A'; + } + /** * Removes the specified format for the specified node. It will also remove the node if it doesn't have * any attributes if the format specifies it to do so. * * @private @@ -15544,11 +15550,11 @@ */ function removeFormat(format, vars, node, compare_node) { var i, attrs, stylesModified; // Check if node matches format - if (!matchName(node, format)) { + if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { return FALSE; } // Should we compare with format attribs and styles if (format.remove != 'all') { @@ -18757,11 +18763,11 @@ ], function(Tools) { var nativeEvents = Tools.makeMap( "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + "draggesture dragdrop drop drag submit " + - "compositionstart compositionend compositionupdate", + "compositionstart compositionend compositionupdate touchstart touchend", ' ' ); function Dispatcher(settings) { var self = this, scope, bindings = {}, toggleEvent; @@ -19951,11 +19957,10 @@ "tinymce/ui/Collection", "tinymce/ui/DomUtils" ], function(Class, Tools, EventDispatcher, Collection, DomUtils) { "use strict"; - var elementIdCache = {}; var hasMouseWheelEventSupport = "onmousewheel" in document; var hasWheelEventSupport = false; var classPrefix = "mce-"; function getEventDispatcher(obj) { @@ -19981,11 +19986,10 @@ return obj._eventDispatcher; } var Control = Class.extend({ Statics: { - elementIdCache: elementIdCache, classPrefix: classPrefix }, isRtl: function() { return Control.rtl; @@ -20024,10 +20028,11 @@ // Initial states self._id = settings.id || DomUtils.id(); self._text = self._name = ''; self._width = self._height = 0; self._aria = {role: settings.role}; + this._elmCache = {}; // Setup classes classes = settings.classes; if (classes) { classes = classes.split(' '); @@ -20765,19 +20770,20 @@ /** * Returns the control DOM element or sub element. * * @method getEl * @param {String} [suffix] Suffix to get element by. - * @param {Boolean} [dropCache] True if the cache for the element should be dropped. * @return {Element} HTML DOM element for the current control or it's children. */ - getEl: function(suffix, dropCache) { - var elm, id = suffix ? this._id + '-' + suffix : this._id; + getEl: function(suffix) { + var id = suffix ? this._id + '-' + suffix : this._id; - elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id); + if (!this._elmCache[id]) { + this._elmCache[id] = DomUtils.get(id); + } - return elm; + return this._elmCache[id]; }, /** * Sets/gets the visible for the control. * @@ -20984,20 +20990,11 @@ var lookup = self.getRoot().controlIdLookup; if (lookup) { delete lookup[self._id]; } - delete elementIdCache[self._id]; - if (elm && elm.parentNode) { - var nodes = elm.getElementsByTagName('*'); - - i = nodes.length; - while (i--) { - delete elementIdCache[nodes[i].id]; - } - elm.parentNode.removeChild(elm); } self._rendered = false; @@ -23146,12 +23143,19 @@ } } function bindWindowResizeHandler() { if (!windowResizeHandler) { + var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight; + windowResizeHandler = function() { - FloatPanel.hideAll(); + // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized + if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) { + clientWidth = docElm.clientWidth; + clientHeight = docElm.clientHeight; + FloatPanel.hideAll(); + } }; DomUtils.on(window, 'resize', windowResizeHandler); } } @@ -24093,10 +24097,18 @@ } } self.windows = windows; + editor.on('remove', function() { + var i = windows.length; + + while (i--) { + windows[i].close(); + } + }); + /** * Opens a new window. * * @method open * @param {Object} args Optional name/value settings collection contains things like width/height/url etc. @@ -24419,11 +24431,11 @@ elm.setAttribute('mce-data-marked', 1); } // Make sure all elements has a data-mce-style attribute if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) { - editor.dom.setAttrib(elm, 'style', elm.getAttribute('style')); + editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style')); } }); // Observe added nodes and style attribute changes mutationObserver.observe(editor.getDoc(), { @@ -24842,39 +24854,10 @@ } }); } /** - * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange - * event every 50ms since it would other wise update the UI when you type and it hogs the CPU. - */ - function selectionChangeNodeChanged() { - var lastRng, selectionTimer; - - editor.on('selectionchange', function() { - if (selectionTimer) { - clearTimeout(selectionTimer); - selectionTimer = 0; - } - - selectionTimer = window.setTimeout(function() { - if (editor.removed) { - return; - } - - var rng = selection.getRng(); - - // Compare the ranges to see if it was a real change or not - if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) { - editor.nodeChanged(); - lastRng = rng; - } - }, 50); - }); - } - - /** * Screen readers on IE needs to have the role application set on the body. */ function ensureBodyHasRoleApplication() { document.body.setAttribute("role", "application"); } @@ -25397,10 +25380,57 @@ editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}'); } /** + * iOS Safari and possible other browsers have a bug where it won't fire + * a click event when a contentEditable is focused. This function fakes click events + * by using touchstart/touchend and measuring the time and distance travelled. + */ + function touchClickEvent() { + editor.on('touchstart', function(e) { + var elm, time, startTouch, changedTouches; + + elm = e.target; + time = new Date().getTime(); + changedTouches = e.changedTouches; + + if (!changedTouches || changedTouches.length > 1) { + return; + } + + startTouch = changedTouches[0]; + + editor.once('touchend', function(e) { + var endTouch = e.changedTouches[0], args; + + if (new Date().getTime() - time > 500) { + return; + } + + if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) { + return; + } + + if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) { + return; + } + + args = { + target: elm + }; + + each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) { + args[key] = endTouch[key]; + }); + + args = editor.fire('click', args); + }); + }); + } + + /** * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element. * For example this: <form><button></form> */ function blockFormSubmitInsideEditor() { editor.on('init', function() { @@ -25442,14 +25472,14 @@ selectControlElements(); setDefaultBlockType(); blockFormSubmitInsideEditor(); disableBackspaceIntoATable(); removeAppleInterchangeBrs(); + touchClickEvent(); // iOS if (Env.iOS) { - selectionChangeNodeChanged(); restoreFocusOnKeyDown(); bodyHeight(); tapLinksAndImages(); } else { selectAll(); @@ -25647,84 +25677,134 @@ define("tinymce/EditorObservable", [ "tinymce/util/Observable", "tinymce/dom/DOMUtils", "tinymce/util/Tools" ], function(Observable, DOMUtils, Tools) { - var DOM = DOMUtils.DOM; + var DOM = DOMUtils.DOM, customEventRootDelegates; + /** + * Returns the event target so for the specified event. Some events fire + * only on document, some fire on documentElement etc. This also handles the + * custom event root setting where it returns that element instead of the body. + * + * @private + * @param {tinymce.Editor} editor Editor instance to get event target from. + * @param {String} eventName Name of the event for example "click". + * @return {Element/Document} HTML Element or document target to bind on. + */ function getEventTarget(editor, eventName) { if (eventName == 'selectionchange') { return editor.getDoc(); } // Need to bind mousedown/mouseup etc to document not body in iframe mode // Since the user might click on the HTML element not the BODY if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) { - return editor.getDoc(); + return editor.getDoc().documentElement; } + // Bind to event root instead of body if it's defined + if (editor.settings.event_root) { + if (!editor.eventRoot) { + editor.eventRoot = DOM.select(editor.settings.event_root)[0]; + } + + return editor.eventRoot; + } + return editor.getBody(); } - function bindEventDelegate(editor, name) { - var eventRootSelector = editor.settings.event_root, editorManager = editor.editorManager; - var eventRootElm = editorManager.eventRootElm || getEventTarget(editor, name); + /** + * Binds a event delegate for the specified name this delegate will fire + * the event to the editor dispatcher. + * + * @private + * @param {tinymce.Editor} editor Editor instance to get event target from. + * @param {String} eventName Name of the event for example "click". + */ + function bindEventDelegate(editor, eventName) { + var eventRootElm = getEventTarget(editor, eventName), delegate; - if (eventRootSelector) { - if (!editorManager.rootEvents) { - editorManager.rootEvents = {}; + if (!editor.delegates) { + editor.delegates = {}; + } - editorManager.on('RemoveEditor', function() { - if (!editorManager.activeEditor) { - DOM.unbind(eventRootElm); - delete editorManager.rootEvents; + if (editor.delegates[eventName]) { + return; + } + + if (editor.settings.event_root) { + if (!customEventRootDelegates) { + customEventRootDelegates = {}; + editor.editorManager.on('removeEditor', function() { + var name; + + if (!editor.editorManager.activeEditor) { + if (customEventRootDelegates) { + for (name in customEventRootDelegates) { + editor.dom.unbind(getEventTarget(editor, name)); + } + + customEventRootDelegates = null; + } } }); } - if (editorManager.rootEvents[name]) { + if (customEventRootDelegates[eventName]) { return; } - if (eventRootElm == editor.getBody()) { - eventRootElm = DOM.select(eventRootSelector)[0]; - editorManager.eventRootElm = eventRootElm; - } + delegate = function(e) { + var target = e.target, editors = editor.editorManager.editors, i = editors.length; - editorManager.rootEvents[name] = true; - - DOM.bind(eventRootElm, name, function(e) { - var target = e.target, editors = editorManager.editors, i = editors.length; - while (i--) { var body = editors[i].getBody(); if (body === target || DOM.isChildOf(target, body)) { if (!editors[i].hidden) { - editors[i].fire(name, e); + editors[i].fire(eventName, e); } } } - }); + }; + + customEventRootDelegates[eventName] = delegate; + DOM.bind(eventRootElm, eventName, delegate); } else { - editor.dom.bind(eventRootElm, name, function(e) { + delegate = function(e) { if (!editor.hidden) { - editor.fire(name, e); + editor.fire(eventName, e); } - }); + }; + + DOM.bind(eventRootElm, eventName, delegate); + editor.delegates[eventName] = delegate; } } var EditorObservable = { + /** + * Bind any pending event delegates. This gets executed after the target body/document is created. + * + * @private + */ bindPendingEventDelegates: function() { var self = this; Tools.each(self._pendingNativeEvents, function(name) { bindEventDelegate(self, name); }); }, + /** + * Toggles a native event on/off this is called by the EventDispatcher when + * the first native event handler is added and when the last native event handler is removed. + * + * @private + */ toggleNativeEvent: function(name, state) { var self = this; if (self.settings.readonly) { return; @@ -25744,12 +25824,39 @@ } else { self._pendingNativeEvents.push(name); } } } else if (self.initialized) { - self.dom.unbind(getEventTarget(self, name), name); + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); + delete self.delegates[name]; } + }, + + /** + * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc. + * + * @private + */ + unbindAllNativeEvents: function() { + var self = this, name; + + if (self.delegates) { + for (name in self.delegates) { + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); + } + + delete self.delegates; + } + + if (!self.inline) { + self.getBody().onload = null; + self.dom.unbind(self.getWin()); + self.dom.unbind(self.getDoc()); + } + + self.dom.unbind(self.getBody()); + self.dom.unbind(self.getContainer()); } }; EditorObservable = Tools.extend({}, Observable, EditorObservable); @@ -26552,10 +26659,13 @@ self.fire("load"); }; DOM.setAttrib("src", url || 'javascript:""'); + self.contentAreaContainer = o.iframeContainer; + self.iframeElement = ifr; + n = DOM.add(o.iframeContainer, ifr); // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! if (ie) { @@ -26564,13 +26674,10 @@ } catch (e) { n.src = url = domainRelaxUrl; } } - self.contentAreaContainer = o.iframeContainer; - self.iframeElement = ifr; - if (o.editorContainer) { DOM.get(o.editorContainer).style.display = self.orgDisplay; self.hidden = DOM.isHidden(o.editorContainer); } @@ -26877,16 +26984,19 @@ }); // Handle auto focus if (settings.auto_focus) { setTimeout(function() { - var ed = self.editorManager.get(settings.auto_focus); + var editor; - ed.selection.select(ed.getBody(), 1); - ed.selection.collapse(1); - ed.getBody().focus(); - ed.getWin().focus(); + if (settings.auto_focus === true) { + editor = self; + } else { + editor = self.editorManager.get(settings.auto_focus); + } + + editor.focus(); }, 100); } // Clean up references for IE targetElm = doc = body = null; @@ -27925,10 +28035,11 @@ var self = this; if (!self.removed) { self.save(); self.removed = 1; + self.unbindAllNativeEvents(); // Remove any hidden input if (self.hasHiddenInput) { DOM.remove(self.getElement().nextSibling); } @@ -27940,25 +28051,16 @@ self.getDoc().execCommand('SelectAll', false, null); } DOM.setStyle(self.id, 'display', self.orgDisplay); self.getBody().onload = null; // Prevent #6816 - - // Don't clear the window or document if content editable - // is enabled since other instances might still be present - Event.unbind(self.getWin()); - Event.unbind(self.getDoc()); } - var elm = self.getContainer(); - Event.unbind(self.getBody()); - Event.unbind(elm); - self.fire('remove'); self.editorManager.remove(self); - DOM.remove(elm); + DOM.remove(self.getContainer()); self.destroy(); } }, /** @@ -27982,18 +28084,10 @@ if (!automatic && !self.removed) { self.remove(); return; } - // We must unbind on Gecko since it would otherwise produce the pesky "attempt - // to run compile-and-go script on a cleared scope" message - if (automatic && isGecko) { - Event.unbind(self.getDoc()); - Event.unbind(self.getWin()); - Event.unbind(self.getBody()); - } - if (!automatic) { self.editorManager.off('beforeunload', self._beforeUnload); // Manual destroy if (self.theme && self.theme.destroy) { @@ -28472,10 +28566,11 @@ function purgeDestroyedEditor(editor) { // User has manually destroyed the editor lets clean up the mess if (editor && !(editor.getContainer() || editor.getBody()).parentNode) { removeEditorFromList(editor); + editor.unbindAllNativeEvents(); editor.destroy(true); editor = null; } return editor; @@ -28502,19 +28597,19 @@ * Minor version of TinyMCE build. * * @property minorVersion * @type String */ - minorVersion: '1.2', + minorVersion: '1.3', /** * Release date of TinyMCE build. * * @property releaseDate * @type String */ - releaseDate: '2014-07-15', + releaseDate: '2014-07-29', /** * Collection of editor instances. * * @property editors @@ -29129,22 +29224,31 @@ */ /** * This class enables you to send XMLHTTPRequests cross browser. * @class tinymce.util.XHR + * @mixes tinymce.util.Observable * @static * @example * // Sends a low level Ajax request * tinymce.util.XHR.send({ * url: 'someurl', * success: function(text) { * console.debug(text); * } * }); + * + * // Add custom header to XHR request + * tinymce.util.XHR.on('beforeSend', function(e) { + * e.xhr.setRequestHeader('X-Requested-With', 'Something'); + * }); */ -define("tinymce/util/XHR", [], function() { - return { +define("tinymce/util/XHR", [ + "tinymce/util/Observable", + "tinymce/util/Tools" +], function(Observable, Tools) { + var XHR = { /** * Sends a XMLHTTPRequest. * Consult the Wiki for details on what settings this method takes. * * @method send @@ -29184,16 +29288,18 @@ xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async); if (settings.crossDomain) { xhr.withCredentials = true; } + if (settings.content_type) { xhr.setRequestHeader('Content-Type', settings.content_type); } xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr; xhr.send(settings.data); // Syncronous request if (!settings.async) { return ready(); @@ -29202,10 +29308,14 @@ // Wait for response, onReadyStateChange can not be used since it leaks memory in IE setTimeout(ready, 10); } } }; + + Tools.extend(XHR, Observable); + + return XHR; }); // Included from: js/tinymce/classes/util/JSON.js /** @@ -31068,10 +31178,19 @@ } } }); return self._super(); + }, + + remove: function() { + if (this.panel) { + this.panel.remove(); + this.panel = null; + } + + return this._super(); } }); }); // Included from: js/tinymce/classes/ui/ColorButton.js @@ -32854,10 +32973,9 @@ cut: ['Cut', 'Cut'], copy: ['Copy', 'Copy'], paste: ['Paste', 'Paste'], help: ['Help', 'mceHelp'], selectall: ['Select all', 'SelectAll'], - hr: ['Insert horizontal rule', 'InsertHorizontalRule'], removeformat: ['Clear formatting', 'RemoveFormat'], visualaid: ['Visual aids', 'mceToggleVisualAid'], newdocument: ['New document', 'mceNewDocument'] }, function(item, name) { editor.addButton(name, { \ No newline at end of file