app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.0.19 vs app/assets/source/tinymce/tinymce.jquery.js in tinymce-rails-4.0.26
- old
+ new
@@ -1,6 +1,6 @@
-// 4.0.19 (2014-03-11)
+// 4.0.26 (2014-05-06)
/**
* Compiled inline version. (Library mode)
*/
@@ -312,12 +312,20 @@
return "'" + str.replace(/\'/g, "\\'") + "'";
}
url = decode(url || url2 || url3);
- if (!settings.allow_script_urls && /(java|vb)script:/i.test(url.replace(/[\s\r\n]+/, ''))) {
- return "";
+ if (!settings.allow_script_urls) {
+ var scriptUrl = url.replace(/[\s\r\n]+/, '');
+
+ if (/(java|vb)script:/i.test(scriptUrl)) {
+ return "";
+ }
+
+ if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
+ return "";
+ }
}
// Convert the URL to relative/absolute depending on config
if (urlConverter) {
url = urlConverter.call(urlConverterScope, url, 'style');
@@ -338,12 +346,20 @@
// Parse styles
while ((matches = styleRegExp.exec(css))) {
name = matches[1].replace(trimRightRegExp, '').toLowerCase();
value = matches[2].replace(trimRightRegExp, '');
+ // Decode escaped sequences like \65 -> e
+ /*jshint loopfunc:true*/
+ /*eslint no-loop-func:0 */
+ value = value.replace(/\\[0-9a-f]+/g, function(e) {
+ return String.fromCharCode(parseInt(e.substr(1), 16));
+ });
+
if (name && value.length > 0) {
- if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(/.test(value))) {
+ // Don't allow behavior name or expression/comments within the values
+ if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
continue;
}
// Opera will produce 700 instead of bold in their style values
if (name === 'font-weight' && value === '700') {
@@ -838,14 +854,17 @@
if (callback) {
ci = callbackList.length;
while (ci--) {
if (callbackList[ci].func === callback) {
var nativeHandler = callbackList.nativeHandler;
+ var fakeName = callbackList.fakeName, capture = callbackList.capture;
// Clone callbackList since unbind inside a callback would otherwise break the handlers loop
callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
callbackList.nativeHandler = nativeHandler;
+ callbackList.fakeName = fakeName;
+ callbackList.capture = capture;
eventMap[name] = callbackList;
}
}
}
@@ -2386,11 +2405,11 @@
"tinymce/util/Tools"
], function(Tools) {
var makeMap = Tools.makeMap;
var namedEntities, baseEntities, reverseEntities,
- attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+ attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
rawCharsRegExp = /[<>&\"\']/g,
entityRegExp = /&(#x|#)?([\w]+);/g,
asciiMap = {
128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
@@ -2404,11 +2423,12 @@
baseEntities = {
'\"': '"', // Needs to be escaped since the YUI compressor would otherwise break the code
"'": ''',
'<': '<',
'>': '>',
- '&': '&'
+ '&': '&',
+ '\u0060': '`'
};
// Reverse lookup table for raw entities
reverseEntities = {
'<': '<',
@@ -3388,11 +3408,12 @@
// Is non element
if (elm.nodeType && elm.nodeType != 1) {
return false;
}
- return Sizzle.matches(selector, elm.nodeType ? [elm] : elm).length > 0;
+ var elms = elm.nodeType ? [elm] : elm;
+ return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
},
// #endif
/**
@@ -4676,13 +4697,13 @@
return false;
}
// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
attributes = self.getAttribs(node);
- i = node.attributes.length;
+ i = attributes.length;
while (i--) {
- name = node.attributes[i].nodeName;
+ name = attributes[i].nodeName;
if (name === "name" || name === 'data-mce-bookmark') {
return false;
}
}
}
@@ -4936,11 +4957,11 @@
// Returns the content editable state of a node
getContentEditable: function(node) {
var contentEditable;
// Check type
- if (node.nodeType != 1) {
+ if (!node || node.nodeType != 1) {
return null;
}
// Check for fake content editable
contentEditable = node.getAttribute("data-mce-contenteditable");
@@ -4950,10 +4971,24 @@
// Check for real content editable
return node.contentEditable !== "inherit" ? node.contentEditable : null;
},
+ getContentEditableParent: function(node) {
+ var root = this.getRoot(), state = null;
+
+ for (; node && node !== root; node = node.parentNode) {
+ state = this.getContentEditable(node);
+
+ if (state !== null) {
+ break;
+ }
+ }
+
+ return state;
+ },
+
/**
* Destroys all internal references to the DOM to solve IE leak issues.
*
* @method destroy
*/
@@ -4979,10 +5014,22 @@
}
self.win = self.doc = self.root = self.events = self.frag = null;
},
+ isChildOf: function(node, parent) {
+ while (node) {
+ if (parent === node) {
+ return true;
+ }
+
+ node = node.parentNode;
+ }
+
+ return false;
+ },
+
// #ifdef debug
dumpRng: function(r) {
return (
'startContainer: ' + r.startContainer.nodeName +
@@ -5353,16 +5400,25 @@
* @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, languages) {
- if (AddOnManager.language && AddOnManager.languageLoad !== false) {
- if (languages && new RegExp('([, ]|\\b)' + AddOnManager.language + '([, ]|\\b)').test(languages) === false) {
- return;
+ var language = AddOnManager.language;
+
+ if (language && AddOnManager.languageLoad !== false) {
+ if (languages) {
+ languages = ',' + languages + ',';
+
+ // Load short form sv.js or long form sv_SE.js
+ if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
+ language = language.substr(0, 2);
+ } else if (languages.indexOf(',' + language + ',') == -1) {
+ return;
+ }
}
- ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.language + '.js');
+ ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
}
},
/**
* Adds a instance of the add-on by it's short name.
@@ -6272,12 +6328,12 @@
add("ruby", "", phrasingContent, "rt rp");
add("figcaption", "", flowContent);
add("mark rt rp summary bdi", "", phrasingContent);
add("canvas", "width height", flowContent);
add("video", "src crossorigin poster preload autoplay mediagroup loop " +
- "muted controls width height", flowContent, "track source");
- add("audio", "src crossorigin preload autoplay mediagroup loop muted controls", flowContent, "track source");
+ "muted controls width height buffered", flowContent, "track source");
+ add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
add("source", "src type media");
add("track", "kind src srclang label default");
add("datalist", "", phrasingContent, "option");
add("article section nav aside header footer", "", flowContent);
add("hgroup", "", "h1 h2 h3 h4 h5 h6");
@@ -6332,11 +6388,11 @@
if (type != "html4") {
addAttrs("input button select textarea", "autofocus");
addAttrs("input textarea", "placeholder");
addAttrs("a", "download");
addAttrs("link script img", "crossorigin");
- addAttrs("iframe", "srcdoc sandbox seamless allowfullscreen");
+ addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
}
// Special: iframe, ruby, video, audio, label
// Delete children of the same name from it's parent
@@ -6391,11 +6447,11 @@
mapCache[option] = value;
}
} else {
// Create custom map
- value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
+ value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
}
return value;
}
@@ -6605,10 +6661,13 @@
// Adds custom non HTML elements to the schema
function addCustomElements(custom_elements) {
var customElementRegExp = /^(~)?(.+)$/;
if (custom_elements) {
+ // Flush cached items since we are altering the default maps
+ mapCache.text_block_elements = mapCache.block_elements = null;
+
each(split(custom_elements, ','), function(rule) {
var matches = customElementRegExp.exec(rule),
inline = matches[1] === '~',
cloneName = inline ? 'span' : 'div',
name = matches[2];
@@ -6632,12 +6691,13 @@
elements[name] = customRule;
}
// Add custom elements at span/div positions
- each(children, function(element) {
+ each(children, function(element, elmName) {
if (element[cloneName]) {
+ children[elmName] = element = extend({}, children[elmName]);
element[name] = element[cloneName];
}
});
});
}
@@ -6663,10 +6723,14 @@
parent = children[matches[2]];
each(split(matches[3], '|'), function(child) {
if (prefix === '-') {
+ // Clone the element before we delete
+ // things in it to not mess up default schemas
+ children[matches[2]] = parent = extend({}, children[matches[2]]);
+
delete parent[child];
} else {
parent[child] = {};
}
});
@@ -7081,12 +7145,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, filteredUrlAttrs = Tools.makeMap('src,href');
- var scriptUriRegExp = /(java|vb)script:/i;
+ var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
+ var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
function processEndTag(name) {
var pos, i;
// Find position of parent of the same type
@@ -7148,27 +7212,29 @@
if (attrRule.validValues && !(value in attrRule.validValues)) {
return;
}
}
- // Block any javascript: urls
+ // Block any javascript: urls or non image data uris
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;
- }
}
+
+ if (scriptUriRegExp.test(uri)) {
+ return;
+ }
+
+ if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
+ return;
+ }
}
// Add attribute to list and map
attrList.map[name] = value;
attrList.push({
@@ -8580,10 +8646,21 @@
settings.entity_encoding = settings.entity_encoding || 'named';
settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
htmlParser = new DomParser(settings, schema);
+ // Convert tabindex back to elements when serializing contents
+ htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
+ var i = nodes.length, node;
+
+ while (i--) {
+ node = nodes[i];
+ node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
+ node.attr(name, null);
+ }
+ });
+
// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
var i = nodes.length, node, value, internalName = 'data-mce-' + name;
var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
@@ -9631,10 +9708,12 @@
}
function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody();
+ unbindResizeHandleEvents();
+
// Get position and size of target
position = dom.getPos(targetElm, offsetParent);
selectedElmX = position.x;
selectedElmY = position.y;
rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
@@ -9710,18 +9789,22 @@
// Hides IE move layer cursor
// If we set it on Chrome we get this wounderful bug: #6725
if (Env.ie) {
handleElm.contentEditable = false;
}
+ } else {
+ dom.show(handleElm);
+ }
+ if (!handle.elm) {
dom.bind(handleElm, 'mousedown', function(e) {
e.stopImmediatePropagation();
e.preventDefault();
startDrag(e);
});
- } else {
- dom.show(handleElm);
+
+ handle.elm = handleElm;
}
/*
var halfHandleW = handleElm.offsetWidth / 2;
var halfHandleH = handleElm.offsetHeight / 2;
@@ -9747,10 +9830,12 @@
}
function hideResizeRect() {
var name, handleElm;
+ unbindResizeHandleEvents();
+
if (selectedElm) {
selectedElm.removeAttribute('data-mce-selected');
}
for (name in resizeHandles) {
@@ -9856,10 +9941,21 @@
function detachResizeStartListener() {
detachEvent(selectedElm, 'resizestart', resizeNativeStart);
}
+ function unbindResizeHandleEvents() {
+ for (var name in resizeHandles) {
+ var handle = resizeHandles[name];
+
+ if (handle.elm) {
+ dom.unbind(handle.elm);
+ delete handle.elm;
+ }
+ }
+ }
+
function disableGeckoResize() {
try {
// Disable object resizing on Gecko
editor.getDoc().execCommand('enableObjectResizing', false, false);
} catch (ex) {
@@ -9937,14 +10033,18 @@
if (selectedElm && selectedElm.nodeName == "TABLE") {
updateResizeRect(e);
}
});
+ editor.on('hide', hideResizeRect);
+
// Hide rect on focusout since it would float on top of windows otherwise
//editor.on('focusout', hideResizeRect);
});
+ editor.on('remove', unbindResizeHandleEvents);
+
function destroy() {
selectedElm = selectedElmGhost = null;
if (isIE) {
detachResizeStartListener();
@@ -10217,11 +10317,11 @@
*/
this.normalize = function(rng) {
var normalized, collapsed;
function normalizeEndPoint(start) {
- var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
+ var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
var directionLeft, isAfterNode;
function hasBrBeforeAfter(node, left) {
var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
@@ -10254,10 +10354,15 @@
}
// Walk left until we hit a text node we can move to or a block/br/img
walker = new TreeWalker(startNode, parentBlockContainer);
while ((node = walker[left ? 'prev' : 'next']())) {
+ // Break if we hit a non content editable node
+ if (dom.getContentEditableParent(node) === "false") {
+ return;
+ }
+
// Found text node that has a length
if (node.nodeType === 3 && node.nodeValue.length > 0) {
container = node;
offset = left ? node.nodeValue.length : 0;
normalized = true;
@@ -10300,11 +10405,10 @@
if (container === body) {
// If start is before/after a image, table etc
if (directionLeft) {
node = container.childNodes[offset > 0 ? offset - 1 : 0];
if (node) {
- nodeName = node.nodeName.toLowerCase();
if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
return;
}
}
}
@@ -11702,10 +11806,164 @@
};
return Selection;
});
+// Included from: js/tinymce/classes/fmt/Preview.js
+
+/**
+ * Preview.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * Internal class for generating previews styles for formats.
+ *
+ * Example:
+ * Preview.getCssText(editor, 'bold');
+ *
+ * @class tinymce.fmt.Preview
+ * @private
+ */
+define("tinymce/fmt/Preview", [
+ "tinymce/util/Tools"
+], function(Tools) {
+ var each = Tools.each;
+
+ function getCssText(editor, format) {
+ var name, previewElm, dom = editor.dom;
+ var previewCss = '', parentFontSize, previewStyles;
+
+ previewStyles = editor.settings.preview_styles;
+
+ // No preview forced
+ if (previewStyles === false) {
+ return '';
+ }
+
+ // Default preview
+ if (!previewStyles) {
+ previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
+ 'text-transform color background-color border border-radius outline text-shadow';
+ }
+
+ // Removes any variables since these can't be previewed
+ function removeVars(val) {
+ return val.replace(/%(\w+)/g, '');
+ }
+
+ // Create block/inline element to use for preview
+ if (typeof(format) == "string") {
+ format = editor.formatter.get(format);
+ if (!format) {
+ return;
+ }
+
+ format = format[0];
+ }
+
+ name = format.block || format.inline || 'span';
+ previewElm = dom.create(name);
+
+ // Add format styles to preview element
+ each(format.styles, function(value, name) {
+ value = removeVars(value);
+
+ if (value) {
+ dom.setStyle(previewElm, name, value);
+ }
+ });
+
+ // Add attributes to preview element
+ each(format.attributes, function(value, name) {
+ value = removeVars(value);
+
+ if (value) {
+ dom.setAttrib(previewElm, name, value);
+ }
+ });
+
+ // Add classes to preview element
+ each(format.classes, function(value) {
+ value = removeVars(value);
+
+ if (!dom.hasClass(previewElm, value)) {
+ dom.addClass(previewElm, value);
+ }
+ });
+
+ editor.fire('PreviewFormats');
+
+ // Add the previewElm outside the visual area
+ dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
+ editor.getBody().appendChild(previewElm);
+
+ // Get parent container font size so we can compute px values out of em/% for older IE:s
+ parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
+ parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
+
+ each(previewStyles.split(' '), function(name) {
+ var value = dom.getStyle(previewElm, name, true);
+
+ // If background is transparent then check if the body has a background color we can use
+ if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
+ value = dom.getStyle(editor.getBody(), name, true);
+
+ // Ignore white since it's the default color, not the nicest fix
+ // TODO: Fix this by detecting runtime style
+ if (dom.toHex(value).toLowerCase() == '#ffffff') {
+ return;
+ }
+ }
+
+ if (name == 'color') {
+ // Ignore black since it's the default color, not the nicest fix
+ // TODO: Fix this by detecting runtime style
+ if (dom.toHex(value).toLowerCase() == '#000000') {
+ return;
+ }
+ }
+
+ // Old IE won't calculate the font size so we need to do that manually
+ if (name == 'font-size') {
+ if (/em|%$/.test(value)) {
+ if (parentFontSize === 0) {
+ return;
+ }
+
+ // Convert font size from em/% to px
+ value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
+ value = (value * parentFontSize) + 'px';
+ }
+ }
+
+ if (name == "border" && value) {
+ previewCss += 'padding:0 2px;';
+ }
+
+ previewCss += name + ':' + value + ';';
+ });
+
+ editor.fire('AfterPreviewFormats');
+
+ //previewCss += 'line-height:normal';
+
+ dom.remove(previewElm);
+
+ return previewCss;
+ }
+
+ return {
+ getCssText: getCssText
+ };
+});
+
// Included from: js/tinymce/classes/Formatter.js
/**
* Formatter.js
*
@@ -11731,12 +11989,13 @@
* tinymce.activeEditor.formatter.apply('mycustomformat');
*/
define("tinymce/Formatter", [
"tinymce/dom/TreeWalker",
"tinymce/dom/RangeUtils",
- "tinymce/util/Tools"
-], function(TreeWalker, RangeUtils, Tools) {
+ "tinymce/util/Tools",
+ "tinymce/fmt/Preview"
+], function(TreeWalker, RangeUtils, Tools, Preview) {
/**
* Constructs a new formatter instance.
*
* @constructor Formatter
* @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
@@ -11781,10 +12040,23 @@
return node.nodeType === 1 && node.id === '_mce_caret';
}
function defaultFormats() {
register({
+
+ valigntop: [
+ {selector: 'td,th', styles: {'verticalAlign': 'top'}}
+ ],
+
+ valignmiddle: [
+ {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
+ ],
+
+ valignbottom: [
+ {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
+ ],
+
alignleft: [
{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
{selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
],
@@ -11995,10 +12267,20 @@
each(fmt.styles, function(value, name) {
dom.setStyle(elm, name, replaceVars(value, vars));
});
+ // Needed for the WebKit span spam bug
+ // TODO: Remove this once WebKit/Blink fixes this
+ if (fmt.styles) {
+ var styleVal = dom.getAttrib(elm, 'style');
+
+ if (styleVal) {
+ elm.setAttribute('data-mce-style', styleVal);
+ }
+ }
+
each(fmt.attributes, function(value, name) {
dom.setAttrib(elm, name, replaceVars(value, vars));
});
each(fmt.classes, function(value) {
@@ -12897,10 +13179,24 @@
});
return this;
}
+ /**
+ * Returns a preview css text for the specified format.
+ *
+ * @method getCssText
+ * @param {String/Object} format Format to generate preview css text for.
+ * @return {String} Css text for the specified format.
+ * @example
+ * var cssText1 = editor.formatter.getCssText('bold');
+ * var cssText2 = editor.formatter.getCssText({inline: 'b'});
+ */
+ function getCssText(format) {
+ return Preview.getCssText(ed, format);
+ }
+
// Expose to public
extend(this, {
get: get,
register: register,
apply: apply,
@@ -12908,11 +13204,12 @@
toggle: toggle,
match: match,
matchAll: matchAll,
matchNode: matchNode,
canApply: canApply,
- formatChanged: formatChanged
+ formatChanged: formatChanged,
+ getCssText: getCssText
});
// Initialize
defaultFormats();
addKeyboardShortcuts();
@@ -14161,11 +14458,11 @@
'<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles
'\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
].join('|'), 'gi');
return function(editor) {
- var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, lock;
+ var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
// Returns a trimmed version of the current editor contents
function getContent() {
return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
}
@@ -14201,11 +14498,11 @@
editor.on('ObjectResizeStart', function() {
self.beforeChange();
});
editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
- editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
+ editor.on('DragEnd', addNonTypingUndoLevel);
editor.on('KeyUp', function(e) {
var keyCode = e.keyCode;
if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
@@ -14289,11 +14586,11 @@
* the change has been made.
*
* @method beforeChange
*/
beforeChange: function() {
- if (!lock) {
+ if (!locks) {
beforeBookmark = editor.selection.getBookmark(2, true);
}
},
/**
@@ -14308,20 +14605,20 @@
var i, settings = editor.settings, lastLevel;
level = level || {};
level.content = getContent();
- if (lock || editor.removed) {
+ if (locks || editor.removed) {
return null;
}
- if (editor.fire('BeforeAddUndo', {level: level, originalEvent: event}).isDefaultPrevented()) {
+ lastLevel = data[index];
+ if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
return null;
}
// Add undo level if needed
- lastLevel = data[index];
if (lastLevel && lastLevel.content == level.content) {
return null;
}
// Set before bookmark on previous level
@@ -14459,13 +14756,16 @@
* @param {function} callback Function to execute dom manipulation logic in.
*/
transact: function(callback) {
self.beforeChange();
- lock = true;
- callback();
- lock = false;
+ try {
+ locks++;
+ callback();
+ } finally {
+ locks--;
+ }
self.add();
}
};
@@ -16022,11 +16322,17 @@
* @class tinymce.util.URI
*/
define("tinymce/util/URI", [
"tinymce/util/Tools"
], function(Tools) {
- var each = Tools.each, trim = Tools.trim;
+ var each = Tools.each, trim = Tools.trim,
+ DEFAULT_PORTS = {
+ 'ftp': 21,
+ 'http': 80,
+ 'https': 443,
+ 'mailto': 25
+ };
/**
* Constructs a new URI instance.
*
* @constructor
@@ -16194,14 +16500,38 @@
* var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
*/
toAbsolute: function(uri, noHost) {
uri = new URI(uri, {base_uri: this});
- return uri.getURI(this.host == uri.host && this.protocol == uri.protocol ? noHost : 0);
+ return uri.getURI(noHost && this.isSameOrigin(uri));
},
/**
+ * Determine whether the given URI has the same origin as this URI. Based on RFC-6454.
+ * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they
+ * won't match, if the port specifications differ.
+ *
+ * @method isSameOrigin
+ * @param {tinymce.util.URI} uri Uri instance to compare.
+ * @returns {Boolean} True if the origins are the same.
+ */
+ isSameOrigin: function(uri) {
+ if (this.host == uri.host && this.protocol == uri.protocol){
+ if (this.port == uri.port) {
+ return true;
+ }
+
+ var defaultPort = DEFAULT_PORTS[this.protocol];
+ if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
* Converts a absolute path into a relative path.
*
* @method toRelPath
* @param {String} base Base point to convert the path from.
* @param {String} path Absolute path to convert into a relative path.
@@ -16452,10 +16782,12 @@
}
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
+
+ /*eslint new-cap:0 */
prototype = new self();
initializing = false;
// Add mixins
if (prop.Mixins) {
@@ -16538,10 +16870,273 @@
};
return Class;
});
+// Included from: js/tinymce/classes/util/EventDispatcher.js
+
+/**
+ * EventDispatcher.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * This class lets you add/remove and fire events by name on the specified scope. This makes
+ * it easy to add event listener logic to any class.
+ *
+ * @class tinymce.util.EventDispatcher
+ * @example
+ * var eventDispatcher = new EventDispatcher();
+ *
+ * eventDispatcher.on('click', function() {console.log('data');});
+ * eventDispatcher.fire('click', {data: 123});
+ */
+define("tinymce/util/EventDispatcher", [
+ "tinymce/util/Tools"
+], 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",
+ ' '
+ );
+
+ function Dispatcher(settings) {
+ var self = this, scope, bindings = {}, toggleEvent;
+
+ function returnFalse() {
+ return false;
+ }
+
+ function returnTrue() {
+ return true;
+ }
+
+ settings = settings || {};
+ scope = settings.scope || self;
+ toggleEvent = settings.toggleEvent || returnFalse;
+
+ /**
+ * Fires the specified event by name.
+ *
+ * @method fire
+ * @param {String} name Name of the event to fire.
+ * @param {Object?} args Event arguments.
+ * @return {Object} Event args instance passed in.
+ * @example
+ * instance.fire('event', {...});
+ */
+ function fire(name, args) {
+ var handlers, i, l, callback;
+
+ name = name.toLowerCase();
+ args = args || {};
+ args.type = name;
+
+ // Setup target is there isn't one
+ if (!args.target) {
+ args.target = scope;
+ }
+
+ // Add event delegation methods if they are missing
+ if (!args.preventDefault) {
+ // Add preventDefault method
+ args.preventDefault = function() {
+ args.isDefaultPrevented = returnTrue;
+ };
+
+ // Add stopPropagation
+ args.stopPropagation = function() {
+ args.isPropagationStopped = returnTrue;
+ };
+
+ // Add stopImmediatePropagation
+ args.stopImmediatePropagation = function() {
+ args.isImmediatePropagationStopped = returnTrue;
+ };
+
+ // Add event delegation states
+ args.isDefaultPrevented = returnFalse;
+ args.isPropagationStopped = returnFalse;
+ args.isImmediatePropagationStopped = returnFalse;
+ }
+
+ if (settings.beforeFire) {
+ settings.beforeFire(args);
+ }
+
+ handlers = bindings[name];
+ if (handlers) {
+ for (i = 0, l = handlers.length; i < l; i++) {
+ handlers[i] = callback = handlers[i];
+
+ // Stop immediate propagation if needed
+ if (args.isImmediatePropagationStopped()) {
+ args.stopPropagation();
+ return args;
+ }
+
+ // If callback returns false then prevent default and stop all propagation
+ if (callback.call(scope, args) === false) {
+ args.preventDefault();
+ return args;
+ }
+ }
+ }
+
+ return args;
+ }
+
+ /**
+ * Binds an event listener to a specific event by name.
+ *
+ * @method on
+ * @param {String} name Event name or space separated list of events to bind.
+ * @param {callback} callback Callback to be executed when the event occurs.
+ * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
+ * @return {Object} Current class instance.
+ * @example
+ * instance.on('event', function(e) {
+ * // Callback logic
+ * });
+ */
+ function on(name, callback, prepend) {
+ var handlers, names, i;
+
+ if (callback === false) {
+ callback = returnFalse;
+ }
+
+ if (callback) {
+ names = name.toLowerCase().split(' ');
+ i = names.length;
+ while (i--) {
+ name = names[i];
+ handlers = bindings[name];
+ if (!handlers) {
+ handlers = bindings[name] = [];
+ toggleEvent(name, true);
+ }
+
+ if (prepend) {
+ handlers.unshift(callback);
+ } else {
+ handlers.push(callback);
+ }
+ }
+ }
+
+ return self;
+ }
+
+ /**
+ * Unbinds an event listener to a specific event by name.
+ *
+ * @method off
+ * @param {String?} name Name of the event to unbind.
+ * @param {callback?} callback Callback to unbind.
+ * @return {Object} Current class instance.
+ * @example
+ * // Unbind specific callback
+ * instance.off('event', handler);
+ *
+ * // Unbind all listeners by name
+ * instance.off('event');
+ *
+ * // Unbind all events
+ * instance.off();
+ */
+ function off(name, callback) {
+ var i, handlers, bindingName, names, hi;
+
+ if (name) {
+ names = name.toLowerCase().split(' ');
+ i = names.length;
+ while (i--) {
+ name = names[i];
+ handlers = bindings[name];
+
+ // Unbind all handlers
+ if (!name) {
+ for (bindingName in bindings) {
+ toggleEvent(bindingName, false);
+ delete bindings[bindingName];
+ }
+
+ return self;
+ }
+
+ if (handlers) {
+ // Unbind all by name
+ if (!callback) {
+ handlers.length = 0;
+ } else {
+ // Unbind specific ones
+ hi = handlers.length;
+ while (hi--) {
+ if (handlers[hi] === callback) {
+ handlers.splice(hi, 1);
+ }
+ }
+ }
+
+ if (!handlers.length) {
+ toggleEvent(name, false);
+ delete bindings[name];
+ }
+ }
+ }
+ } else {
+ for (name in bindings) {
+ toggleEvent(name, false);
+ }
+
+ bindings = {};
+ }
+
+ return self;
+ }
+
+ /**
+ * Returns true/false if the dispatcher has a event of the specified name.
+ *
+ * @method has
+ * @param {String} name Name of the event to check for.
+ * @return {Boolean} true/false if the event exists or not.
+ */
+ function has(name) {
+ name = name.toLowerCase();
+ return !(!bindings[name] || bindings[name].length === 0);
+ }
+
+ // Expose
+ self.fire = fire;
+ self.on = on;
+ self.off = off;
+ self.has = has;
+ }
+
+ /**
+ * Returns true/false if the specified event name is a native browser event or not.
+ *
+ * @method isNative
+ * @param {String} name Name to check if it's native.
+ * @return {Boolean} true/false if the event is native or not.
+ * @static
+ */
+ Dispatcher.isNative = function(name) {
+ return !!nativeEvents[name.toLowerCase()];
+ };
+
+ return Dispatcher;
+});
+
// Included from: js/tinymce/classes/ui/Selector.js
/**
* Selector.js
*
@@ -17464,25 +18059,48 @@
* @class tinymce.ui.Control
*/
define("tinymce/ui/Control", [
"tinymce/util/Class",
"tinymce/util/Tools",
+ "tinymce/util/EventDispatcher",
"tinymce/ui/Collection",
"tinymce/ui/DomUtils"
-], function(Class, Tools, Collection, DomUtils) {
+], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
"use strict";
- var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
- " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
-
var elementIdCache = {};
var hasMouseWheelEventSupport = "onmousewheel" in document;
var hasWheelEventSupport = false;
+ var classPrefix = "mce-";
+ function getEventDispatcher(obj) {
+ if (!obj._eventDispatcher) {
+ obj._eventDispatcher = new EventDispatcher({
+ scope: obj,
+ toggleEvent: function(name, state) {
+ if (state && EventDispatcher.isNative(name)) {
+ if (!obj._nativeEvents) {
+ obj._nativeEvents = {};
+ }
+
+ obj._nativeEvents[name] = true;
+
+ if (obj._rendered) {
+ obj.bindPendingEvents();
+ }
+ }
+ }
+ });
+ }
+
+ return obj._eventDispatcher;
+ }
+
var Control = Class.extend({
Statics: {
- elementIdCache: elementIdCache
+ elementIdCache: elementIdCache,
+ classPrefix: classPrefix
},
isRtl: function() {
return Control.rtl;
},
@@ -17491,11 +18109,11 @@
* Class/id prefix to use for all controls.
*
* @final
* @field {String} classPrefix
*/
- classPrefix: "mce-",
+ classPrefix: classPrefix,
/**
* Constructs a new control instance with the specified settings.
*
* @constructor
@@ -17948,18 +18566,22 @@
* @param {String} name Name of the event to bind. For example "click".
* @param {String/function} callback Callback function to execute ones the event occurs.
* @return {tinymce.ui.Control} Current control object.
*/
on: function(name, callback) {
- var self = this, bindings, handlers, names, i;
+ var self = this;
function resolveCallbackName(name) {
var callback, scope;
+ if (typeof(name) != 'string') {
+ return name;
+ }
+
return function(e) {
if (!callback) {
- self.parents().each(function(ctrl) {
+ self.parentsAndSelf().each(function(ctrl) {
var callbacks = ctrl.settings.callbacks;
if (callbacks && (callback = callbacks[name])) {
scope = ctrl;
return false;
@@ -17969,46 +18591,12 @@
return callback.call(scope, e);
};
}
- if (callback) {
- if (typeof(callback) == 'string') {
- callback = resolveCallbackName(callback);
- }
+ getEventDispatcher(self).on(name, resolveCallbackName(callback));
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
-
- bindings = self._bindings;
- if (!bindings) {
- bindings = self._bindings = {};
- }
-
- handlers = bindings[name];
- if (!handlers) {
- handlers = bindings[name] = [];
- }
-
- handlers.push(callback);
-
- if (nativeEvents[name]) {
- if (!self._nativeEvents) {
- self._nativeEvents = {name: true};
- } else {
- self._nativeEvents[name] = true;
- }
-
- if (self._rendered) {
- self.bindPendingEvents();
- }
- }
- }
- }
-
return self;
},
/**
* Unbinds the specified event and optionally a specific callback. If you omit the name
@@ -18019,50 +18607,12 @@
* @param {String} [name] Name for the event to unbind.
* @param {function} [callback] Callback function to unbind.
* @return {mxex.ui.Control} Current control object.
*/
off: function(name, callback) {
- var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
-
- if (bindings) {
- if (name) {
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- handlers = bindings[name];
-
- // Unbind all handlers
- if (!name) {
- for (bindingName in bindings) {
- bindings[bindingName].length = 0;
- }
-
- return self;
- }
-
- if (handlers) {
- // Unbind all by name
- if (!callback) {
- handlers.length = 0;
- } else {
- // Unbind specific ones
- hi = handlers.length;
- while (hi--) {
- if (handlers[hi] === callback) {
- handlers.splice(hi, 1);
- }
- }
- }
- }
- }
- } else {
- self._bindings = [];
- }
- }
-
- return self;
+ getEventDispatcher(this).off(name, callback);
+ return this;
},
/**
* Fires the specified event by name and arguments on the control. This will execute all
* bound event handlers.
@@ -18072,82 +18622,29 @@
* @param {Object} [args] Arguments to pass to the event.
* @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
* @return {Object} Current arguments object.
*/
fire: function(name, args, bubble) {
- var self = this, i, l, handlers, parentCtrl;
+ var self = this;
- name = name.toLowerCase();
-
- // Dummy function that gets replaced on the delegation state functions
- function returnFalse() {
- return false;
- }
-
- // Dummy function that gets replaced on the delegation state functions
- function returnTrue() {
- return true;
- }
-
- // Setup empty object if args is omited
args = args || {};
- // Stick type into event object
- if (!args.type) {
- args.type = name;
- }
-
- // Stick control into event
if (!args.control) {
args.control = self;
}
- // Add event delegation methods if they are missing
- if (!args.preventDefault) {
- // Add preventDefault method
- args.preventDefault = function() {
- args.isDefaultPrevented = returnTrue;
- };
+ args = getEventDispatcher(self).fire(name, args);
- // Add stopPropagation
- args.stopPropagation = function() {
- args.isPropagationStopped = returnTrue;
- };
-
- // Add stopImmediatePropagation
- args.stopImmediatePropagation = function() {
- args.isImmediatePropagationStopped = returnTrue;
- };
-
- // Add event delegation states
- args.isDefaultPrevented = returnFalse;
- args.isPropagationStopped = returnFalse;
- args.isImmediatePropagationStopped = returnFalse;
- }
-
- if (self._bindings) {
- handlers = self._bindings[name];
-
- if (handlers) {
- for (i = 0, l = handlers.length; i < l; i++) {
- // Execute callback and break if the callback returns a false
- if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
- break;
- }
- }
+ // Bubble event up to parents
+ if (bubble !== false && self.parent) {
+ var parent = self.parent();
+ while (parent && !args.isPropagationStopped()) {
+ parent.fire(name, args, false);
+ parent = parent.parent();
}
}
- // Bubble event up to parent controls
- if (bubble !== false) {
- parentCtrl = self.parent();
- while (parentCtrl && !args.isPropagationStopped()) {
- parentCtrl.fire(name, args, false);
- parentCtrl = parentCtrl.parent();
- }
- }
-
return args;
},
/**
* Returns true/false if the specified event has any listeners.
@@ -18155,11 +18652,11 @@
* @method hasEventListeners
* @param {String} name Name of the event to check for.
* @return {Boolean} True/false state if the event has listeners.
*/
hasEventListeners: function(name) {
- return name in this._bindings;
+ return getEventDispatcher(this).has(name);
},
/**
* Returns a control collection with all parent controls.
*
@@ -18182,10 +18679,21 @@
return parents;
},
/**
+ * Returns the current control and it's parents.
+ *
+ * @method parentsAndSelf
+ * @param {String} selector Optional selector expression to find parents.
+ * @return {tinymce.ui.Collection} Collection with all parent controls.
+ */
+ parentsAndSelf: function(selector) {
+ return new Collection(this).add(this.parents(selector));
+ },
+
+ /**
* Returns the control next to the current control.
*
* @method next
* @return {tinymce.ui.Control} Next control instance.
*/
@@ -18497,20 +19005,31 @@
* @param {String/Object/Array} text Text to entity encode.
* @param {Boolean} [translate=true] False if the contents shouldn't be translated.
* @return {String} Encoded and possible traslated string.
*/
encode: function(text, translate) {
- if (translate !== false && Control.translate) {
- text = Control.translate(text);
+ if (translate !== false) {
+ text = this.translate(text);
}
return (text || '').replace(/[&<>"]/g, function(match) {
return '&#' + match.charCodeAt(0) + ';';
});
},
/**
+ * Returns the translated string.
+ *
+ * @method translate
+ * @param {String} text Text to translate.
+ * @return {String} Translated string or the same as the input.
+ */
+ translate: function(text) {
+ return Control.translate ? Control.translate(text) : text;
+ },
+
+ /**
* Adds items before the current control.
*
* @method before
* @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
* @return {tinymce.ui.Control} Current control instance.
@@ -18849,10 +19368,15 @@
self._eventsRoot = eventRootCtrl;
for (l = i, i = 0; i < l; i++) {
parents[i]._eventsRoot = eventRootCtrl;
}
+ var eventRootDelegates = eventRootCtrl._delegates;
+ if (!eventRootDelegates) {
+ eventRootDelegates = eventRootCtrl._delegates = {};
+ }
+
// Bind native event delegates
for (name in nativeEvents) {
if (!nativeEvents) {
return false;
}
@@ -18873,13 +19397,13 @@
if (!eventRootCtrl._hasMouseEnter) {
DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
eventRootCtrl._hasMouseEnter = 1;
}
- } else if (!eventRootCtrl[name]) {
+ } else if (!eventRootDelegates[name]) {
DomUtils.on(eventRootCtrl.getEl(), name, delegate);
- eventRootCtrl[name] = true;
+ eventRootDelegates[name] = true;
}
// Remove the event once it's bound
nativeEvents[name] = false;
}
@@ -19199,13 +19723,15 @@
* @private
* @param {Element} elm Element to check if it's an text input element or not.
* @return {Boolean} True/false if the element is a text element or not.
*/
function isTextInputElement(elm) {
+ var tagName = elm.tagName.toUpperCase();
+
// Notice: since type can be "email" etc we don't check the type
// So all input elements gets treated as text input elements
- return elm.tagName == "INPUT" || elm.tagName == "TEXTAREA";
+ return tagName == "INPUT" || tagName == "TEXTAREA";
}
/**
* Returns true/false if the specified element can be focused or not.
*
@@ -19443,11 +19969,11 @@
aria = aria || {};
focusedControl.fire('click', {target: focusedElement, aria: aria});
}
root.on('keydown', function(e) {
- function handleNonTabEvent(e, handler) {
+ function handleNonTabOrEscEvent(e, handler) {
// Ignore non tab keys for text elements
if (isTextInputElement(focusedElement)) {
return;
}
@@ -19460,33 +19986,33 @@
return;
}
switch (e.keyCode) {
case 37: // DOM_VK_LEFT
- handleNonTabEvent(e, left);
+ handleNonTabOrEscEvent(e, left);
break;
case 39: // DOM_VK_RIGHT
- handleNonTabEvent(e, right);
+ handleNonTabOrEscEvent(e, right);
break;
case 38: // DOM_VK_UP
- handleNonTabEvent(e, up);
+ handleNonTabOrEscEvent(e, up);
break;
case 40: // DOM_VK_DOWN
- handleNonTabEvent(e, down);
+ handleNonTabOrEscEvent(e, down);
break;
case 27: // DOM_VK_ESCAPE
- handleNonTabEvent(e, cancel);
+ cancel();
break;
case 14: // DOM_VK_ENTER
case 13: // DOM_VK_RETURN
case 32: // DOM_VK_SPACE
- handleNonTabEvent(e, enter);
+ handleNonTabOrEscEvent(e, enter);
break;
case 9: // DOM_VK_TAB
if (tab(e) !== false) {
e.preventDefault();
@@ -20354,11 +20880,11 @@
self._hasBody = false;
}
return (
- '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1" role="group">' +
+ '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
(self._preBodyHtml || '') +
innerHtml +
'</div>'
);
}
@@ -21208,11 +21734,11 @@
if (settings.title) {
headerHtml = (
'<div id="' + id + '-head" class="' + prefix + 'window-head">' +
'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
- '<button type="button" class="' + prefix + 'close" aria-hidden="true">×</button>' +
+ '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
'</div>'
);
}
@@ -21227,11 +21753,11 @@
if (self.statusbar) {
footerHtml = self.statusbar.renderHtml();
}
return (
- '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1">' +
+ '<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
'<div class="' + self.classPrefix + 'reset" role="application">' +
headerHtml +
'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
html +
'</div>' +
@@ -21376,10 +21902,21 @@
if (self._fullscreen) {
DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
DomUtils.removeClass(document.body, prefix + 'fullscreen');
}
+ },
+
+ /**
+ * Returns the contentWindow object of the iframe if it exists.
+ *
+ * @method getContentWindow
+ * @return {Window} window object or null.
+ */
+ getContentWindow: function() {
+ var ifr = this.getEl().getElementsByTagName('iframe')[0];
+ return ifr ? ifr.contentWindow : null;
}
});
return Window;
});
@@ -21684,11 +22221,10 @@
if (!args.url && !args.buttons) {
args.buttons = [
{text: 'Ok', subtype: 'primary', onclick: function() {
win.find('form')[0].submit();
- win.close();
}},
{text: 'Cancel', onclick: function() {
win.close();
}}
@@ -21728,11 +22264,11 @@
win.params = params || {};
// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
editor.nodeChanged();
- return win.renderTo(document.body).reflow();
+ return win.renderTo().reflow();
};
/**
* Creates a alert dialog. Please don't use the blocking behavior of this
* native version use the callback method instead then it can be extended.
@@ -21812,10 +22348,20 @@
self.setParams = function(params) {
if (getTopMostWindow()) {
getTopMostWindow().params = params;
}
};
+
+ /**
+ * Returns the currently opened window objects.
+ *
+ * @method getWindows
+ * @return {Array} Array of the currently opened windows.
+ */
+ self.getWindows = function() {
+ return windows;
+ };
};
});
// Included from: js/tinymce/classes/util/Quirks.js
@@ -21908,13 +22454,49 @@
* This code is a ugly hack since writing full custom delete logic for just this bug
* fix seemed like a huge task. I hope we can remove this before the year 2030.
*/
function cleanupStylesWhenDeleting() {
var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
+ var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
- if (!window.MutationObserver) {
- return;
+ // Add mini polyfill for older WebKits
+ // TODO: Remove this when old Safari versions gets updated
+ if (!MutationObserver) {
+ olderWebKit = true;
+
+ MutationObserver = function() {
+ var records = [], target;
+
+ function nodeInsert(e) {
+ var target = e.relatedNode || e.target;
+ records.push({target: target, addedNodes: [target]});
+ }
+
+ function attrModified(e) {
+ var target = e.relatedNode || e.target;
+ records.push({target: target, attributeName: e.attrName});
+ }
+
+ this.observe = function(node) {
+ target = node;
+ target.addEventListener('DOMSubtreeModified', nodeInsert, false);
+ target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
+ target.addEventListener('DOMNodeInserted', nodeInsert, false);
+ target.addEventListener('DOMAttrModified', attrModified, false);
+ };
+
+ this.disconnect = function() {
+ target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
+ target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
+ target.removeEventListener('DOMNodeInserted', nodeInsert, false);
+ target.removeEventListener('DOMAttrModified', attrModified, false);
+ };
+
+ this.takeRecords = function() {
+ return records;
+ };
+ };
}
function customDelete(isForward) {
var mutationObserver = new MutationObserver(function() {});
@@ -21942,10 +22524,14 @@
var rng = editor.selection.getRng();
var caretElement = rng.startContainer.parentNode;
Tools.each(mutationObserver.takeRecords(), function(record) {
+ if (!dom.isChildOf(record.target, editor.getBody())) {
+ return;
+ }
+
// Restore style attribute to previous value
if (record.attributeName == "style") {
var oldValue = record.target.getAttribute('data-mce-style');
if (oldValue) {
@@ -22021,13 +22607,29 @@
editor.addCommand('ForwardDelete', function() {
customDelete(true);
});
+ // Older WebKits doesn't properly handle the clipboard so we can't add the rest
+ if (olderWebKit) {
+ return;
+ }
+
editor.on('dragstart', function(e) {
+ var selectionHtml;
+
+ if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
+ selection.select(e.target);
+ }
+
+ dragStartRng = selection.getRng();
+ selectionHtml = editor.selection.getContent();
+
// Safari doesn't support custom dataTransfer items so we can only use URL and Text
- e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(editor.selection.getContent()));
+ if (selectionHtml.length > 0) {
+ e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
+ }
});
editor.on('drop', function(e) {
if (!isDefaultPrevented(e)) {
var internalContent = e.dataTransfer.getData('URL');
@@ -22037,14 +22639,30 @@
}
internalContent = unescape(internalContent.substr(urlPrefix.length));
if (doc.caretRangeFromPoint) {
e.preventDefault();
- customDelete();
- editor.selection.setRng(doc.caretRangeFromPoint(e.x, e.y));
- editor.insertContent(internalContent);
+
+ // Safari has a weird issue where drag/dropping images sometimes
+ // produces a green plus icon. When this happens the caretRangeFromPoint
+ // will return "null" even though the x, y coordinate is correct.
+ // But if we detach the insert from the drop event we will get a proper range
+ window.setTimeout(function() {
+ var pointRng = doc.caretRangeFromPoint(e.x, e.y);
+
+ if (dragStartRng) {
+ selection.setRng(dragStartRng);
+ dragStartRng = null;
+ }
+
+ customDelete();
+
+ selection.setRng(pointRng);
+ editor.insertContent(internalContent);
+ }, 0);
}
+
}
});
editor.on('cut', function(e) {
if (!isDefaultPrevented(e) && e.clipboardData) {
@@ -22321,10 +22939,14 @@
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();
@@ -22733,20 +23355,20 @@
}
});
}
/**
- * Fixes selection issues on Gecko where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
+ * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
* this fix will lean the caret right into the closest inline element.
*/
function normalizeSelection() {
// 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
- editor.on('keyup focusin', function(e) {
+ editor.on('keyup focusin mouseup', function(e) {
if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
selection.normalize();
}
- });
+ }, true);
}
/**
* Forces Gecko to render a broken image icon if it fails to load an image.
*/
@@ -22830,18 +23452,50 @@
*
* Example of what happens: <body>text</body> becomes <body>text<br><br></body>
*/
function doubleTrailingBrElements() {
if (!editor.inline) {
- editor.on('focus blur', function() {
+ editor.on('focus blur beforegetcontent', function() {
var br = editor.dom.create('br');
editor.getBody().appendChild(br);
br.parentNode.removeChild(br);
}, true);
}
}
+ /**
+ * iOS 7.1 introduced two new bugs:
+ * 1) It's possible to open links within a contentEditable area by clicking on them.
+ * 2) If you hold down the finger it will display the link/image touch callout menu.
+ */
+ function tapLinksAndImages() {
+ editor.on('click', function(e) {
+ var elm = e.target;
+
+ do {
+ if (elm.tagName === 'A') {
+ e.preventDefault();
+ return;
+ }
+ } while ((elm = elm.parentNode));
+ });
+
+ editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
+ }
+
+ /**
+ * 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() {
+ editor.dom.bind(editor.getBody(), 'submit', function(e) {
+ e.preventDefault();
+ });
+ });
+ }
+
// All browsers
disableBackspaceIntoATable();
removeBlockQuoteOnBackSpace();
emptyEditorWhenDeleting();
normalizeSelection();
@@ -22850,16 +23504,18 @@
if (isWebKit) {
cleanupStylesWhenDeleting();
inputMethodFocus();
selectControlElements();
setDefaultBlockType();
+ blockFormSubmitInsideEditor();
// iOS
if (Env.iOS) {
selectionChangeNodeChanged();
restoreFocusOnKeyDown();
bodyHeight();
+ tapLinksAndImages();
} else {
selectAll();
}
}
@@ -22915,103 +23571,52 @@
* This mixin will add event binding logic to classes.
*
* @mixin tinymce.util.Observable
*/
define("tinymce/util/Observable", [
- "tinymce/util/Tools"
-], function(Tools) {
- var bindingsName = "__bindings";
- var nativeEvents = Tools.makeMap(
- "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" +
- " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag", ' '
- );
+ "tinymce/util/EventDispatcher"
+], function(EventDispatcher) {
+ function getEventDispatcher(obj) {
+ if (!obj._eventDispatcher) {
+ obj._eventDispatcher = new EventDispatcher({
+ scope: obj,
+ toggleEvent: function(name, state) {
+ if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
+ obj.toggleNativeEvent(name, state);
+ }
+ }
+ });
+ }
- function returnFalse() {
- return false;
+ return obj._eventDispatcher;
}
- function returnTrue() {
- return true;
- }
-
return {
/**
* Fires the specified event by name.
*
* @method fire
* @param {String} name Name of the event to fire.
- * @param {tinymce.Event/Object?} args Event arguments.
+ * @param {Object?} args Event arguments.
* @param {Boolean?} bubble True/false if the event is to be bubbled.
- * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance.
+ * @return {Object} Event args instance passed in.
* @example
* instance.fire('event', {...});
*/
fire: function(name, args, bubble) {
- var self = this, handlers, i, l, callback, parent;
+ var self = this;
- if (self.removed) {
- return;
+ // Prevent all events except the remove event after the instance has been removed
+ if (self.removed && name !== "remove") {
+ return args;
}
- name = name.toLowerCase();
- args = args || {};
- args.type = name;
+ args = getEventDispatcher(self).fire(name, args, bubble);
- // Setup target is there isn't one
- if (!args.target) {
- args.target = self;
- }
-
- // Add event delegation methods if they are missing
- if (!args.preventDefault) {
- // Add preventDefault method
- args.preventDefault = function() {
- args.isDefaultPrevented = returnTrue;
- };
-
- // Add stopPropagation
- args.stopPropagation = function() {
- args.isPropagationStopped = returnTrue;
- };
-
- // Add stopImmediatePropagation
- args.stopImmediatePropagation = function() {
- args.isImmediatePropagationStopped = returnTrue;
- };
-
- // Add event delegation states
- args.isDefaultPrevented = returnFalse;
- args.isPropagationStopped = returnFalse;
- args.isImmediatePropagationStopped = returnFalse;
- }
-
- //console.log(name, args);
-
- if (self[bindingsName]) {
- handlers = self[bindingsName][name];
-
- if (handlers) {
- for (i = 0, l = handlers.length; i < l; i++) {
- handlers[i] = callback = handlers[i];
-
- // Stop immediate propagation if needed
- if (args.isImmediatePropagationStopped()) {
- break;
- }
-
- // If callback returns false then prevent default and stop all propagation
- if (callback.call(self, args) === false) {
- args.preventDefault();
- return args;
- }
- }
- }
- }
-
// Bubble event up to parents
if (bubble !== false && self.parent) {
- parent = self.parent();
+ var parent = self.parent();
while (parent && !args.isPropagationStopped()) {
parent.fire(name, args, false);
parent = parent.parent();
}
}
@@ -23031,46 +23636,11 @@
* instance.on('event', function(e) {
* // Callback logic
* });
*/
on: function(name, callback, prepend) {
- var self = this, bindings, handlers, names, i;
-
- if (callback === false) {
- callback = function() {
- return false;
- };
- }
-
- if (callback) {
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
-
- bindings = self[bindingsName];
- if (!bindings) {
- bindings = self[bindingsName] = {};
- }
-
- handlers = bindings[name];
- if (!handlers) {
- handlers = bindings[name] = [];
- if (self.bindNative && nativeEvents[name]) {
- self.bindNative(name);
- }
- }
-
- if (prepend) {
- handlers.unshift(callback);
- } else {
- handlers.push(callback);
- }
- }
- }
-
- return self;
+ return getEventDispatcher(this).on(name, callback, prepend);
},
/**
* Unbinds an event listener to a specific event by name.
*
@@ -23087,71 +23657,154 @@
*
* // Unbind all events
* instance.off();
*/
off: function(name, callback) {
- var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi;
+ return getEventDispatcher(this).off(name, callback);
+ },
- if (bindings) {
- if (name) {
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- handlers = bindings[name];
+ /**
+ * Returns true/false if the object has a event of the specified name.
+ *
+ * @method hasEventListeners
+ * @param {String} name Name of the event to check for.
+ * @return {Boolean} true/false if the event exists or not.
+ */
+ hasEventListeners: function(name) {
+ return getEventDispatcher(this).has(name);
+ }
+ };
+});
- // Unbind all handlers
- if (!name) {
- for (bindingName in bindings) {
- bindings[name].length = 0;
- }
+// Included from: js/tinymce/classes/EditorObservable.js
- return self;
- }
+/**
+ * EditorObservable.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- if (handlers) {
- // Unbind all by name
- if (!callback) {
- handlers.length = 0;
- } else {
- // Unbind specific ones
- hi = handlers.length;
- while (hi--) {
- if (handlers[hi] === callback) {
- handlers.splice(hi, 1);
- }
- }
- }
+/**
+ * This mixin contains the event logic for the tinymce.Editor class.
+ *
+ * @mixin tinymce.EditorObservable
+ * @extends tinymce.util.Observable
+ */
+define("tinymce/EditorObservable", [
+ "tinymce/util/Observable",
+ "tinymce/dom/DOMUtils",
+ "tinymce/util/Tools"
+], function(Observable, DOMUtils, Tools) {
+ var DOM = DOMUtils.DOM;
- if (!handlers.length && self.unbindNative && nativeEvents[name]) {
- self.unbindNative(name);
- delete bindings[name];
- }
- }
+ 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/.test(eventName)) {
+ return editor.getDoc();
+ }
+
+ return editor.getBody();
+ }
+
+ function bindEventDelegate(editor, name) {
+ var eventRootSelector = editor.settings.event_root, editorManager = editor.editorManager;
+ var eventRootElm = editorManager.eventRootElm || getEventTarget(editor, name);
+
+ if (eventRootSelector) {
+ if (!editorManager.rootEvents) {
+ editorManager.rootEvents = {};
+
+ editorManager.on('RemoveEditor', function() {
+ if (!editorManager.activeEditor) {
+ DOM.unbind(eventRootElm);
+ delete editorManager.rootEvents;
}
- } else {
- if (self.unbindNative) {
- for (name in bindings) {
- self.unbindNative(name);
+ });
+ }
+
+ if (editorManager.rootEvents[name]) {
+ return;
+ }
+
+ if (eventRootElm == editor.getBody()) {
+ eventRootElm = DOM.select(eventRootSelector)[0];
+ editorManager.eventRootElm = eventRootElm;
+ }
+
+ 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);
}
}
-
- self[bindingsName] = [];
}
- }
+ });
+ } else {
+ editor.dom.bind(eventRootElm, name, function(e) {
+ if (!editor.hidden) {
+ editor.fire(name, e);
+ }
+ });
+ }
+ }
- return self;
+ var EditorObservable = {
+ bindPendingEventDelegates: function() {
+ var self = this;
+
+ Tools.each(self._pendingNativeEvents, function(name) {
+ bindEventDelegate(self, name);
+ });
},
- hasEventListeners: function(name) {
- var bindings = this[bindingsName];
+ toggleNativeEvent: function(name, state) {
+ var self = this;
- name = name.toLowerCase();
+ if (self.settings.readonly) {
+ return;
+ }
- return !(!bindings || !bindings[name] || bindings[name].length === 0);
+ // Never bind focus/blur since the FocusManager fakes those
+ if (name == "focus" || name == "blur") {
+ return;
+ }
+
+ if (state) {
+ if (self.initialized) {
+ bindEventDelegate(self, name);
+ } else {
+ if (!self._pendingNativeEvents) {
+ self._pendingNativeEvents = [name];
+ } else {
+ self._pendingNativeEvents.push(name);
+ }
+ }
+ } else if (self.initialized) {
+ self.dom.unbind(getEventTarget(self, name), name);
+ }
}
};
+
+ EditorObservable = Tools.extend({}, Observable, EditorObservable);
+
+ return EditorObservable;
});
// Included from: js/tinymce/classes/Shortcuts.js
/**
@@ -23329,39 +23982,25 @@
"tinymce/html/Schema",
"tinymce/html/DomParser",
"tinymce/util/Quirks",
"tinymce/Env",
"tinymce/util/Tools",
- "tinymce/util/Observable",
+ "tinymce/EditorObservable",
"tinymce/Shortcuts"
], function(
DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
URI, ScriptLoader, EventUtils, WindowManager,
- Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
+ Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
) {
// Shorten these names
var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
var Event = EventUtils.Event;
var isGecko = Env.gecko, ie = Env.ie;
- 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/.test(eventName)) {
- return editor.getDoc();
- }
-
- return editor.getBody();
- }
-
/**
* Include documentation for all the events.
*
* @include ../../../tools/docs/tinymce.Editor.js
*/
@@ -23987,16 +24626,14 @@
self.on('remove', function() {
var bodyEl = this.getBody();
DOM.removeClass(bodyEl, 'mce-content-body');
DOM.removeClass(bodyEl, 'mce-edit-focus');
- DOM.setAttrib(bodyEl, 'tabIndex', null);
DOM.setAttrib(bodyEl, 'contentEditable', null);
});
DOM.addClass(targetElm, 'mce-content-body');
- targetElm.tabIndex = -1;
self.contentDocument = doc = settings.content_document || document;
self.contentWindow = settings.content_window || window;
self.bodyElement = targetElm;
// Prevent leak in IE
@@ -24059,11 +24696,11 @@
* @type tinymce.html.DomParser
*/
self.parser = new DomParser(settings, self.schema);
// Convert src and href into data-mce-src, data-mce-href and data-mce-style
- self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
+ self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
var i = nodes.length, node, dom = self.dom, value, internalName;
while (i--) {
node = nodes[i];
value = node.attr(name);
@@ -24071,10 +24708,13 @@
// Add internal attribute if we need to we don't on a refresh of the document
if (!node.attributes.map[internalName]) {
if (name === "style") {
node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
+ } else if (name === "tabindex") {
+ node.attr(internalName, value);
+ node.attr(name, null);
} else {
node.attr(internalName, self.convertURL(value, name, node.name));
}
}
}
@@ -24216,17 +24856,12 @@
* function isEditorInitialized(editor) {
* return editor && editor.initialized;
* }
*/
self.initialized = true;
+ self.bindPendingEventDelegates();
- each(self._pendingNativeEvents, function(name) {
- self.dom.bind(getEventTarget(self, name), name, function(e) {
- self.fire(e.type, e);
- });
- });
-
self.fire('init');
self.focus(true);
self.nodeChanged({initial: true});
self.execCallback('init_instance_callback', self);
@@ -24299,12 +24934,17 @@
// Focus the body as well since it's contentEditable
if (isGecko || contentEditable) {
body = self.getBody();
// Check for setActive since it doesn't scroll to the element
- if (body.setActive && Env.ie < 11) {
- body.setActive();
+ if (body.setActive) {
+ // IE 11 sometimes throws "Invalid function" then fallback to focus
+ try {
+ body.setActive();
+ } catch (ex) {
+ body.focus();
+ }
} else {
body.focus();
}
if (contentEditable) {
@@ -24474,13 +25114,11 @@
self.fire('NodeChange', {element: node, parents: parents});
}
},
/**
- * Adds a button that later gets created by the ControlManager. This is a shorter and easier method
- * of adding buttons without the need to deal with the ControlManager directly. But it's also less
- * powerfull if you need more control use the ControlManagers factory methods instead.
+ * Adds a button that later gets created by the theme in the editors toolbars.
*
* @method addButton
* @param {String} name Button name to add.
* @param {Object} settings Settings object with title, cmd etc.
* @example
@@ -24518,11 +25156,13 @@
settings.tooltip = settings.tooltip || settings.title;
self.buttons[name] = settings;
},
/**
- * Adds a menu item to be used in the menus of the modern theme.
+ * Adds a menu item to be used in the menus of the theme. There might be multiple instances
+ * of this menu item for example it might be used in the main menus of the theme but also in
+ * the context menu so make sure that it's self contained and supports multiple instances.
*
* @method addMenuItem
* @param {String} name Menu item name to add.
* @param {Object} settings Settings object with title, cmd etc.
* @example
@@ -24788,46 +25428,68 @@
* @method show
*/
show: function() {
var self = this;
- DOM.show(self.getContainer());
- DOM.hide(self.id);
- self.load();
- self.fire('show');
+ if (self.hidden) {
+ self.hidden = false;
+
+ if (self.inline) {
+ self.getBody().contentEditable = true;
+ } else {
+ DOM.show(self.getContainer());
+ DOM.hide(self.id);
+ }
+
+ self.load();
+ self.fire('show');
+ }
},
/**
* Hides the editor and shows any textarea/div that the editor is supposed to replace.
*
* @method hide
*/
hide: function() {
var self = this, doc = self.getDoc();
- // Fixed bug where IE has a blinking cursor left from the editor
- if (ie && doc && !self.inline) {
- doc.execCommand('SelectAll');
- }
+ if (!self.hidden) {
+ self.hidden = true;
- // We must save before we hide so Safari doesn't crash
- self.save();
+ // Fixed bug where IE has a blinking cursor left from the editor
+ if (ie && doc && !self.inline) {
+ doc.execCommand('SelectAll');
+ }
- // defer the call to hide to prevent an IE9 crash #4921
- DOM.hide(self.getContainer());
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- self.fire('hide');
+ // We must save before we hide so Safari doesn't crash
+ self.save();
+
+ if (self.inline) {
+ self.getBody().contentEditable = false;
+
+ // Make sure the editor gets blurred
+ if (self == self.editorManager.focusedEditor) {
+ self.editorManager.focusedEditor = null;
+ }
+ } else {
+ DOM.hide(self.getContainer());
+ DOM.setStyle(self.id, 'display', self.orgDisplay);
+ }
+
+ self.fire('hide');
+ }
},
/**
* Returns true/false if the editor is hidden or not.
*
* @method isHidden
* @return {Boolean} True/false if the editor is hidden or not.
*/
isHidden: function() {
- return !DOM.isHidden(this.id);
+ return !!this.hidden;
},
/**
* Sets the progress state, this will display a throbber/progess for the editor.
* This is ideal for asycronous operations like an AJAX save call.
@@ -25279,69 +25941,46 @@
*/
remove: function() {
var self = this;
if (!self.removed) {
- self.fire('remove');
- self.off();
- self.removed = 1; // Cancels post remove event execution
+ self.removed = 1;
+ self.save();
// Remove any hidden input
if (self.hasHiddenInput) {
DOM.remove(self.getElement().nextSibling);
}
- // We must save before we hide so Safari doesn't crash
- self.save();
+ if (!self.inline) {
+ // IE 9 has a bug where the selection stops working if you place the
+ // caret inside the editor then remove the iframe
+ if (ie && ie < 10) {
+ self.getDoc().execCommand('SelectAll', false, null);
+ }
- DOM.setStyle(self.id, 'display', self.orgDisplay);
+ 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
- if (!self.settings.content_editable) {
+ // 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);
self.destroy();
}
},
- bindNative: function(name) {
- var self = this;
-
- if (self.settings.readonly) {
- return;
- }
-
- if (self.initialized) {
- self.dom.bind(getEventTarget(self, name), name, function(e) {
- self.fire(name, e);
- });
- } else {
- if (!self._pendingNativeEvents) {
- self._pendingNativeEvents = [name];
- } else {
- self._pendingNativeEvents.push(name);
- }
- }
- },
-
- unbindNative: function(name) {
- var self = this;
-
- if (self.initialized) {
- self.dom.unbind(name);
- }
- },
-
/**
* Destroys the editor instance by removing all events, element references or other resources
* that could leak memory. This method will be called automatically when the page is unloaded
* but you can also call it directly if you know what you are doing.
*
@@ -25432,11 +26071,11 @@
sel = this.selection.getSel();
return (!sel || !sel.rangeCount || sel.rangeCount === 0);
}
};
- extend(Editor.prototype, Observable);
+ extend(Editor.prototype, EditorObservable);
return Editor;
});
// Included from: js/tinymce/classes/util/I18n.js
@@ -25545,11 +26184,11 @@
*/
define("tinymce/FocusManager", [
"tinymce/dom/DOMUtils",
"tinymce/Env"
], function(DOMUtils, Env) {
- var selectionChangeHandler, documentFocusInHandler, DOM = DOMUtils.DOM;
+ var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
/**
* Constructs a new focus manager instance.
*
* @constructor FocusManager
@@ -25566,12 +26205,17 @@
}
}
// We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
// TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
- function createBookmark(rng) {
+ function createBookmark(dom, rng) {
if (rng && rng.startContainer) {
+ // Verify that the range is within the root of the editor
+ if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
+ return;
+ }
+
return {
startContainer: rng.startContainer,
startOffset: rng.startOffset,
endContainer: rng.endContainer,
endOffset: rng.endOffset
@@ -25597,58 +26241,26 @@
function isUIElement(elm) {
return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
}
- function isNodeInBodyOfEditor(node, editor) {
- var body = editor.getBody();
-
- while (node) {
- if (node == body) {
- return true;
- }
-
- node = node.parentNode;
- }
- }
-
function registerEvents(e) {
var editor = e.editor;
editor.on('init', function() {
- // On IE take selection snapshot onbeforedeactivate
- if ("onbeforedeactivate" in document && Env.ie < 11) {
- // Gets fired when the editor is about to be blurred but also when the selection
- // is moved into a table cell so we need to add the range as a pending range then
- // use that pending range on the blur event of the editor body
- editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
- try {
- editor.pendingRng = editor.selection.getRng();
- } catch (ex) {
- // IE throws "Unexcpected call to method or property access" some times so lets ignore it
- }
- });
-
- // Set the pending range as the current last range if the blur event occurs
- editor.dom.bind(editor.getBody(), 'blur', function() {
- if (editor.pendingRng) {
- editor.lastRng = editor.pendingRng;
- editor.selection.lastFocusBookmark = createBookmark(editor.lastRng);
- editor.pendingRng = null;
- }
- });
- } else if (editor.inline || Env.ie > 10) {
+ // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
+ if (editor.inline || Env.ie) {
// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
editor.on('nodechange keyup', function() {
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();
}
- if (isNodeInBodyOfEditor(node, editor)) {
+ if (editor.dom.isChildOf(node, editor.getBody())) {
editor.lastRng = editor.selection.getRng();
}
});
// Handles the issue with WebKit not retaining selection within inline document
@@ -25718,39 +26330,63 @@
}
}
}, 0);
});
+ // Check if focus is moved to an element outside the active editor by checking if the target node
+ // isn't within the body of the activeEditor nor a UI element such as a dialog child control
if (!documentFocusInHandler) {
documentFocusInHandler = function(e) {
var activeEditor = editorManager.activeEditor;
if (activeEditor && e.target.ownerDocument == document) {
// Check to make sure we have a valid selection
if (activeEditor.selection) {
- activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.lastRng);
+ activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
}
// Fire a blur event if the element isn't a UI element
if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
activeEditor.fire('blur', {focusedEditor: null});
editorManager.focusedEditor = null;
}
}
};
- // Check if focus is moved to an element outside the active editor by checking if the target node
- // isn't within the body of the activeEditor nor a UI element such as a dialog child control
DOM.bind(document, 'focusin', documentFocusInHandler);
}
+
+ // Handle edge case when user starts the selection inside the editor and releases
+ // the mouse outside the editor producing a new selection. This weird workaround is needed since
+ // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
+ if (editor.inline && !documentMouseUpHandler) {
+ documentMouseUpHandler = function(e) {
+ var activeEditor = editorManager.activeEditor;
+
+ if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
+ var rng = activeEditor.selection.getRng();
+
+ if (!rng.collapsed) {
+ activeEditor.lastRng = rng;
+ }
+ }
+ };
+
+ DOM.bind(document, 'mouseup', documentMouseUpHandler);
+ }
}
- function unregisterDocumentEvents() {
+ function unregisterDocumentEvents(e) {
+ if (editorManager.focusedEditor == e.editor) {
+ editorManager.focusedEditor = null;
+ }
+
if (!editorManager.activeEditor) {
DOM.unbind(document, 'selectionchange', selectionChangeHandler);
DOM.unbind(document, 'focusin', documentFocusInHandler);
- selectionChangeHandler = documentFocusInHandler = null;
+ DOM.unbind(document, 'mouseup', documentMouseUpHandler);
+ selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
}
}
editorManager.on('AddEditor', registerEvents);
editorManager.on('RemoveEditor', unregisterDocumentEvents);
@@ -25762,11 +26398,12 @@
* @method isEditorUIElement
* @param {Element} elm Element to check if it's part of the UI or not.
* @return {Boolean} True/false state if the element is part of the UI or not.
*/
FocusManager.isEditorUIElement = function(elm) {
- return elm.className.indexOf('mce-') !== -1;
+ // Needs to be converted to string since svg can have focus: #6776
+ return elm.className.toString().indexOf('mce-') !== -1;
};
return FocusManager;
});
@@ -25802,13 +26439,50 @@
"tinymce/util/I18n",
"tinymce/FocusManager"
], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
var DOM = DOMUtils.DOM;
var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
- var instanceCounter = 0, beforeUnloadDelegate;
+ var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
- var EditorManager = {
+ function removeEditorFromList(editor) {
+ var editors = EditorManager.editors, removedFromList;
+
+ delete editors[editor.id];
+
+ for (var i = 0; i < editors.length; i++) {
+ if (editors[i] == editor) {
+ editors.splice(i, 1);
+ removedFromList = true;
+ break;
+ }
+ }
+
+ // Select another editor since the active one was removed
+ if (EditorManager.activeEditor == editor) {
+ EditorManager.activeEditor = editors[0];
+ }
+
+ // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
+ if (EditorManager.focusedEditor == editor) {
+ EditorManager.focusedEditor = null;
+ }
+
+ return removedFromList;
+ }
+
+ function purgeDestroyedEditor(editor) {
+ // User has manually destroyed the editor lets clean up the mess
+ if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
+ removeEditorFromList(editor);
+ editor.destroy(true);
+ editor = null;
+ }
+
+ return editor;
+ }
+
+ EditorManager = {
/**
* Major version of TinyMCE build.
*
* @property majorVersion
* @type String
@@ -25819,19 +26493,19 @@
* Minor version of TinyMCE build.
*
* @property minorVersion
* @type String
*/
- minorVersion : '0.19',
+ minorVersion : '0.26',
/**
* Release date of TinyMCE build.
*
* @property releaseDate
* @type String
*/
- releaseDate: '2014-03-11',
+ releaseDate: '2014-05-06',
/**
* Collection of editor instances.
*
* @property editors
@@ -25860,11 +26534,11 @@
* tinymce.EditorManager.activeEditor.selection.getContent();
*/
activeEditor: null,
setup: function() {
- var self = this, baseURL, documentBaseURL, suffix = "", preInit;
+ var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
// Get base URL for the current document
documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
if (!/[\/\\]$/.test(documentBaseURL)) {
documentBaseURL += '/';
@@ -25877,11 +26551,11 @@
suffix = preInit.suffix;
} else {
// 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;
+ src = scripts[i].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
@@ -25892,10 +26566,22 @@
baseURL = src.substring(0, src.lastIndexOf('/'));
break;
}
}
+
+ // We didn't find any baseURL by looking at the script elements
+ // Try to use the document.currentScript as a fallback
+ if (!baseURL && document.currentScript) {
+ src = document.currentScript.src;
+
+ if (src.indexOf('.min') != -1) {
+ suffix = '.min';
+ }
+
+ baseURL = src.substring(0, src.lastIndexOf('/'));
+ }
}
/**
* Base URL where the root directory if TinyMCE is located.
*
@@ -25968,10 +26654,18 @@
}
return id;
}
+ function createEditor(id, settings) {
+ if (!purgeDestroyedEditor(self.get(id))) {
+ var editor = new Editor(id, settings, self);
+ editors.push(editor);
+ editor.render();
+ }
+ }
+
function execCallback(se, n, s) {
var f = se[n];
if (!f) {
return;
@@ -25993,49 +26687,42 @@
if (settings.types) {
// Process type specific selector
each(settings.types, function(type) {
each(DOM.select(type.selector), function(elm) {
- var editor = new Editor(createId(elm), extend({}, settings, type), self);
- editors.push(editor);
- editor.render(1);
+ createEditor(createId(elm), extend({}, settings, type));
});
});
return;
} else if (settings.selector) {
// Process global selector
each(DOM.select(settings.selector), function(elm) {
- var editor = new Editor(createId(elm), settings, self);
- editors.push(editor);
- editor.render(1);
+ createEditor(createId(elm), settings);
});
return;
}
// Fallback to old setting
switch (settings.mode) {
case "exact":
l = settings.elements || '';
- if(l.length > 0) {
+ if (l.length > 0) {
each(explode(l), function(v) {
if (DOM.get(v)) {
editor = new Editor(v, settings, self);
editors.push(editor);
- editor.render(true);
+ editor.render();
} else {
each(document.forms, function(f) {
each(f.elements, function(e) {
if (e.name === v) {
v = 'mce_editor_' + instanceCounter++;
DOM.setAttrib(e, 'id', v);
-
- editor = new Editor(v, settings, self);
- editors.push(editor);
- editor.render(1);
+ createEditor(v, settings);
}
});
});
}
});
@@ -26048,13 +26735,11 @@
if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
return;
}
if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
- editor = new Editor(createId(elm), settings, self);
- editors.push(editor);
- editor.render(true);
+ createEditor(createId(elm), settings);
}
});
break;
}
@@ -26108,15 +26793,15 @@
* tinymce.EditorManager.get('mytextbox').on('click', function(e) {
* ed.windowManager.alert('Hello world!');
* });
*/
get: function(id) {
- if (id === undefined) {
+ if (!arguments.length) {
return this.editors;
}
- return this.editors[id];
+ return id in this.editors ? this.editors[id] : null;
},
/**
* Adds an editor instance to the editor collection. This will also set it as the active editor.
*
@@ -26183,11 +26868,11 @@
* @method remove
* @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
* @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
*/
remove: function(selector) {
- var self = this, i, editors = self.editors, editor, removedFromList;
+ var self = this, i, editors = self.editors, editor;
// Remove all editors
if (!selector) {
for (i = editors.length - 1; i >= 0; i--) {
self.remove(editors[i]);
@@ -26213,32 +26898,17 @@
// Not in the collection
if (!editors[editor.id]) {
return null;
}
- delete editors[editor.id];
-
- for (i = 0; i < editors.length; i++) {
- if (editors[i] == editor) {
- editors.splice(i, 1);
- removedFromList = true;
- break;
- }
- }
-
- // Select another editor since the active one was removed
- if (self.activeEditor == editor) {
- self.activeEditor = editors[0];
- }
-
/**
* Fires when an editor is removed from EditorManager collection.
*
* @event RemoveEditor
* @param {Object} e Event arguments.
*/
- if (removedFromList) {
+ if (removeEditorFromList(editor)) {
self.fire('RemoveEditor', {editor: editor});
}
if (!editors.length) {
DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
@@ -27389,10 +28059,11 @@
*/
init: function(settings) {
var self = this;
self._super(settings);
+ settings = self.settings;
self.canFocus = true;
if (settings.tooltip && Widget.tooltips !== false) {
self.on('mouseenter', function(e) {
var tooltip = self.tooltip().moveTo(-0xFFFF);
@@ -27624,22 +28295,51 @@
this._super();
},
/**
+ * Sets/gets the current button text.
+ *
+ * @method text
+ * @param {String} [text] New button text.
+ * @return {String|tinymce.ui.Button} Current text or current Button instance.
+ */
+ text: function(text) {
+ var self = this;
+
+ if (self._rendered) {
+ var textNode = self.getEl().lastChild.lastChild;
+ if (textNode) {
+ textNode.data = self.translate(text);
+ }
+ }
+
+ return self._super(text);
+ },
+
+ /**
* Renders the control as a HTML string.
*
* @method renderHtml
* @return {String} HTML representing the control.
*/
renderHtml: function() {
var self = this, id = self._id, prefix = self.classPrefix;
- var icon = self.settings.icon, image = '';
+ var icon = self.settings.icon, image;
- if (self.settings.image) {
+ image = self.settings.image;
+ if (image) {
icon = 'none';
- image = ' style="background-image: url(\'' + self.settings.image + '\')"';
+
+ // Support for [high dpi, low dpi] image sources
+ if (typeof image != "string") {
+ image = window.getSelection ? image[0] : image[1];
+ }
+
+ image = ' style="background-image: url(\'' + image + '\')"';
+ } else {
+ image = '';
}
icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
return (
@@ -28018,16 +28718,16 @@
var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
return (
'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
- '<button role="presentation" hidefocus type="button" tabindex="-1">' +
+ '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
(self._text ? (icon ? ' ' : '') + (self._text) : '') +
'</button>' +
- '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
+ '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
' <i class="' + prefix + 'caret"></i>' +
'</button>' +
'</div>'
);
},
@@ -28205,11 +28905,11 @@
self.menu.on('select', function(e) {
self.value(e.control.value());
});
self.on('focusin', function(e) {
- if (e.target.tagName == 'INPUT') {
+ if (e.target.tagName.toUpperCase() == 'INPUT') {
self.menu.hide();
}
});
self.aria('expanded', true);
@@ -28371,11 +29071,11 @@
text = self._text;
if (icon || text) {
openBtnHtml = (
'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
- '<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' +
+ '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
(text ? (icon ? ' ' : '') + text : '') +
'</button>' +
'</div>'
);
@@ -28384,11 +29084,11 @@
}
return (
'<div id="' + id + '" class="' + self.classes() + '">' +
'<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
- value + '" hidefocus="true"' + extraAttrs + '>' +
+ value + '" hidefocus="1"' + extraAttrs + ' />' +
openBtnHtml +
'</div>'
);
}
});
@@ -28527,11 +29227,11 @@
i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
);
}
if (!html) {
- html = '<div class="' + prefix + 'path-item"> </div>';
+ html = '<div class="' + prefix + 'path-item">\u00a0</div>';
}
return html;
}
});
@@ -28668,11 +29368,11 @@
self.addClass('formitem');
layout.preRender(self);
return (
- '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
+ '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
self.settings.title + '</div>') : '') +
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
(self.settings.html || '') + layout.renderHtml(self) +
'</div>' +
@@ -28887,11 +29587,11 @@
self.preRender();
layout.preRender(self);
return (
- '<fieldset id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
+ '<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
self.settings.title + '</legend>') : '') +
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
(self.settings.html || '') + layout.renderHtml(self) +
'</div>' +
@@ -29345,131 +30045,10 @@
Widget.tooltips = !Env.iOS;
function registerControls(editor) {
var formatMenu;
- // Generates a preview for a format
- function getPreviewCss(format) {
- var name, previewElm, dom = editor.dom;
- var previewCss = '', parentFontSize, previewStyles;
-
- previewStyles = editor.settings.preview_styles;
-
- // No preview forced
- if (previewStyles === false) {
- return '';
- }
-
- // Default preview
- if (!previewStyles) {
- previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
- 'text-transform color background-color border border-radius outline text-shadow';
- }
-
- // Removes any variables since these can't be previewed
- function removeVars(val) {
- return val.replace(/%(\w+)/g, '');
- }
-
- // Create block/inline element to use for preview
- format = editor.formatter.get(format);
- if (!format) {
- return;
- }
-
- format = format[0];
- name = format.block || format.inline || 'span';
- previewElm = dom.create(name);
-
- // Add format styles to preview element
- each(format.styles, function(value, name) {
- value = removeVars(value);
-
- if (value) {
- dom.setStyle(previewElm, name, value);
- }
- });
-
- // Add attributes to preview element
- each(format.attributes, function(value, name) {
- value = removeVars(value);
-
- if (value) {
- dom.setAttrib(previewElm, name, value);
- }
- });
-
- // Add classes to preview element
- each(format.classes, function(value) {
- value = removeVars(value);
-
- if (!dom.hasClass(previewElm, value)) {
- dom.addClass(previewElm, value);
- }
- });
-
- editor.fire('PreviewFormats');
-
- // Add the previewElm outside the visual area
- dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
- editor.getBody().appendChild(previewElm);
-
- // Get parent container font size so we can compute px values out of em/% for older IE:s
- parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
- parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
-
- each(previewStyles.split(' '), function(name) {
- var value = dom.getStyle(previewElm, name, true);
-
- // If background is transparent then check if the body has a background color we can use
- if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
- value = dom.getStyle(editor.getBody(), name, true);
-
- // Ignore white since it's the default color, not the nicest fix
- // TODO: Fix this by detecting runtime style
- if (dom.toHex(value).toLowerCase() == '#ffffff') {
- return;
- }
- }
-
- if (name == 'color') {
- // Ignore black since it's the default color, not the nicest fix
- // TODO: Fix this by detecting runtime style
- if (dom.toHex(value).toLowerCase() == '#000000') {
- return;
- }
- }
-
- // Old IE won't calculate the font size so we need to do that manually
- if (name == 'font-size') {
- if (/em|%$/.test(value)) {
- if (parentFontSize === 0) {
- return;
- }
-
- // Convert font size from em/% to px
- value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
- value = (value * parentFontSize) + 'px';
- }
- }
-
- if (name == "border" && value) {
- previewCss += 'padding:0 2px;';
- }
-
- previewCss += name + ':' + value + ';';
- });
-
- editor.fire('AfterPreviewFormats');
-
- //previewCss += 'line-height:normal';
-
- dom.remove(previewElm);
-
- return previewCss;
- }
-
function createListBoxChangeHandler(items, formatName) {
return function() {
var self = this;
editor.on('nodeChange', function(e) {
@@ -29502,11 +30081,11 @@
});
};
}
function createFormats(formats) {
- formats = formats.split(';');
+ formats = formats.replace(/;$/, '').split(';');
var i = formats.length;
while (i--) {
formats[i] = formats[i].split('=');
}
@@ -29516,17 +30095,17 @@
function createFormatMenu() {
var count = 0, newFormats = [];
var defaultStyleFormats = [
- {title: 'Headers', items: [
- {title: 'Header 1', format: 'h1'},
- {title: 'Header 2', format: 'h2'},
- {title: 'Header 3', format: 'h3'},
- {title: 'Header 4', format: 'h4'},
- {title: 'Header 5', format: 'h5'},
- {title: 'Header 6', format: 'h6'}
+ {title: 'Headings', items: [
+ {title: 'Heading 1', format: 'h1'},
+ {title: 'Heading 2', format: 'h2'},
+ {title: 'Heading 3', format: 'h3'},
+ {title: 'Heading 4', format: 'h4'},
+ {title: 'Heading 5', format: 'h5'},
+ {title: 'Heading 6', format: 'h6'}
]},
{title: 'Inline', items: [
{title: 'Bold', icon: 'bold', format: 'bold'},
{title: 'Italic', icon: 'italic', format: 'italic'},
@@ -29615,11 +30194,11 @@
itemDefaults: {
preview: true,
textStyle: function() {
if (this.settings.format) {
- return getPreviewCss(this.settings.format);
+ return editor.formatter.getCssText(this.settings.format);
}
},
onPostRender: function() {
var self = this, formatName = this.settings.format;
@@ -29852,24 +30431,24 @@
editor.addButton('formatselect', function() {
var items = [], blocks = createFormats(editor.settings.block_formats ||
'Paragraph=p;' +
'Address=address;' +
'Pre=pre;' +
- 'Header 1=h1;' +
- 'Header 2=h2;' +
- 'Header 3=h3;' +
- 'Header 4=h4;' +
- 'Header 5=h5;' +
- 'Header 6=h6'
+ 'Heading 1=h1;' +
+ 'Heading 2=h2;' +
+ 'Heading 3=h3;' +
+ 'Heading 4=h4;' +
+ 'Heading 5=h5;' +
+ 'Heading 6=h6'
);
each(blocks, function(block) {
items.push({
text: block[0],
value: block[1],
textStyle: function() {
- return getPreviewCss(block[1]);
+ return editor.formatter.getCssText(block[1]);
}
});
});
return {
@@ -30225,10 +30804,11 @@
var self = this;
self.addClass('iframe');
self.canFocus = false;
+ /*eslint no-script-url:0 */
return (
'<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
(self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
);
},
@@ -30946,11 +31526,11 @@
self.on('mousedown', function(e) {
e.preventDefault();
});
- if (settings.menu) {
+ if (settings.menu && !settings.ariaHideMenu) {
self.aria('haspopup', true);
}
},
/**
@@ -31090,11 +31670,11 @@
icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
return (
'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
- (text !== '-' ? '<i class="' + icon + '"' + image + '></i> ' : '') +
+ (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
'</div>'
);
@@ -31544,15 +32124,15 @@
var self = this, id = self._id, prefix = self.classPrefix;
var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
return (
'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
- '<button type="button" hidefocus tabindex="-1">' +
+ '<button type="button" hidefocus="1" tabindex="-1">' +
(icon ? '<i class="' + icon + '"></i>' : '') +
(self._text ? (icon ? ' ' : '') + self._text : '') +
'</button>' +
- '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
+ '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
//(icon ? '<i class="' + icon + '"></i>' : '') +
(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
' <i class="' + prefix + 'caret"></i>' +
'</button>' +
'</div>'
@@ -31716,11 +32296,11 @@
'</div>'
);
});
return (
- '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
+ '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
tabsHtml +
'</div>' +
'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
layout.renderHtml(self) +
@@ -31992,16 +32572,16 @@
if (settings.multiline) {
return (
'<textarea id="' + id + '" class="' + self.classes() + '" ' +
(settings.rows ? ' rows="' + settings.rows + '"' : '') +
- ' hidefocus="true"' + extraAttrs + '>' + value +
+ ' hidefocus="1"' + extraAttrs + '>' + value +
'</textarea>'
);
}
- return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="true"' + extraAttrs + '>';
+ return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
},
/**
* Called after the control has been rendered.
*
@@ -32041,22 +32621,24 @@
*
* @-x-less Throbber.less
* @class tinymce.ui.Throbber
*/
define("tinymce/ui/Throbber", [
- "tinymce/ui/DomUtils"
-], function(DomUtils) {
+ "tinymce/ui/DomUtils",
+ "tinymce/ui/Control"
+], function(DomUtils, Control) {
"use strict";
/**
* Constructs a new throbber.
*
* @constructor
* @param {Element} elm DOM Html element to display throbber in.
+ * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
*/
- return function(elm) {
- var self = this, state;
+ return function(elm, inline) {
+ var self = this, state, classPrefix = Control.classPrefix;
/**
* Shows the throbber.
*
* @method show
@@ -32068,11 +32650,13 @@
state = true;
window.setTimeout(function() {
if (state) {
- elm.appendChild(DomUtils.createFragment('<div class="mce-throbber"></div>'));
+ elm.appendChild(DomUtils.createFragment(
+ '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
+ ));
}
}, time || 0);
return self;
};
@@ -32095,7 +32679,7 @@
return self;
};
};
});
-expose(["tinymce/dom/Sizzle","tinymce/html/Styles","tinymce/dom/EventUtils","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
+expose(["tinymce/dom/Sizzle","tinymce/html/Styles","tinymce/dom/EventUtils","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/fmt/Preview","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
})(this);
\ No newline at end of file