From f55779fd831029f764c885bf1fd026a7e94f08eb Mon Sep 17 00:00:00 2001
From: friendica
Date: Tue, 20 Mar 2012 20:47:31 -0700
Subject: update tinymce to 3.5b2 to fix issues with FF 11 and pasting into
code blocks
---
library/tinymce/jscripts/tiny_mce/tiny_mce_src.js | 20360 +++++++++++---------
1 file changed, 11779 insertions(+), 8581 deletions(-)
mode change 100755 => 100644 library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
(limited to 'library/tinymce/jscripts/tiny_mce/tiny_mce_src.js')
diff --git a/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js b/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
old mode 100755
new mode 100644
index 9db8d18fe..42f01a58c
--- a/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
+++ b/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
@@ -1,13 +1,13 @@
(function(win) {
var whiteSpaceRe = /^\s*|\s*$/g,
- undefined;
+ undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
var tinymce = {
majorVersion : '3',
- minorVersion : '3.7',
+ minorVersion : '5b2',
- releaseDate : '2010-06-10',
+ releaseDate : '2012-03-15',
_init : function() {
var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
@@ -20,6 +20,12 @@
t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
+ t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
+
+ t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
+
+ t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
+
t.isGecko = !t.isWebKit && /Gecko/.test(ua);
t.isMac = ua.indexOf('Mac') != -1;
@@ -27,6 +33,8 @@
t.isAir = /adobeair/i.test(ua);
t.isIDevice = /(iPad|iPhone)/.test(ua);
+
+ t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
// TinyMCE .NET webcontrol might be setting the values for TinyMCE
if (win.tinyMCEPreInit) {
@@ -52,7 +60,7 @@
}
function getBase(n) {
- if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
+ if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
if (/_(src|dev)\.js/g.test(n.src))
t.suffix = '_src';
@@ -103,6 +111,24 @@
return typeof(o) == t;
},
+ makeMap : function(items, delim, map) {
+ var i;
+
+ items = items || [];
+ delim = delim || ',';
+
+ if (typeof(items) == "string")
+ items = items.split(delim);
+
+ map = map || {};
+
+ i = items.length;
+ while (i--)
+ map[items[i]] = {};
+
+ return map;
+ },
+
each : function(o, cb, s) {
var n, l;
@@ -185,7 +211,7 @@
return (s ? '' + s : '').replace(whiteSpaceRe, '');
},
- create : function(s, p) {
+ create : function(s, p, root) {
var t = this, sp, ns, cn, scn, c, de = 0;
// Parse : :
@@ -193,7 +219,7 @@
cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
// Create namespace for new class
- ns = t.createNS(s[3].replace(/\.\w+$/, ''));
+ ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
// Class already exists
if (ns[cn])
@@ -428,6 +454,29 @@
return u + v;
return u.replace('#', v + '#');
+ },
+
+ // Fix function for IE 9 where regexps isn't working correctly
+ // Todo: remove me once MS fixes the bug
+ _replace : function(find, replace, str) {
+ // On IE9 we have to fake $x replacement
+ if (isRegExpBroken) {
+ return str.replace(find, function() {
+ var val = replace, args = arguments, i;
+
+ for (i = 0; i < args.length - 2; i++) {
+ if (args[i] === undefined) {
+ val = val.replace(new RegExp('\\$' + i, 'g'), '');
+ } else {
+ val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
+ }
+ }
+
+ return val;
+ });
+ }
+
+ return str.replace(find, replace);
}
};
@@ -437,7 +486,11 @@
// Expose tinymce namespace to the global namespace (window)
win.tinymce = win.tinyMCE = tinymce;
-})(window);
+
+ // Describe the different namespaces
+
+ })(window);
+
tinymce.create('tinymce.util.Dispatcher', {
@@ -482,7 +535,7 @@ tinymce.create('tinymce.util.Dispatcher', {
// And this is also more efficient
for (i = 0; i 0 ? a : [c.scope]);
if (s === false)
break;
@@ -498,7 +551,7 @@ tinymce.create('tinymce.util.Dispatcher', {
tinymce.create('tinymce.util.URI', {
URI : function(u, s) {
- var t = this, o, a, b;
+ var t = this, o, a, b, base_url;
// Trim whitespace
u = tinymce.trim(u);
@@ -506,8 +559,9 @@ tinymce.create('tinymce.util.Dispatcher', {
// Default settings
s = t.settings = s || {};
- // Strange app protocol or local anchor
- if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
+ // Strange app protocol that isn't http/https or local anchor
+ // For example: mailto,skype,tel etc.
+ if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
t.source = u;
return;
}
@@ -517,12 +571,14 @@ tinymce.create('tinymce.util.Dispatcher', {
u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
// Relative path http:// or protocol relative //path
- if (!/^\w*:?\/\//.test(u))
- u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
+ if (!/^[\w-]*:?\/\//.test(u)) {
+ base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
+ u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
+ }
// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
- u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
+ u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
var s = u[i];
@@ -805,9 +861,11 @@ tinymce.create('tinymce.util.Dispatcher', {
});
})();
-tinymce.create('static tinymce.util.JSON', {
- serialize : function(o) {
- var i, v, s = tinymce.util.JSON.serialize, t;
+(function() {
+ function serialize(o, quote) {
+ var i, v, t;
+
+ quote = quote || '"';
if (o == null)
return 'null';
@@ -817,7 +875,11 @@ tinymce.create('static tinymce.util.JSON', {
if (t == 'string') {
v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
- return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
+ return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
+ // Make sure single quotes never get encoded inside double quotes for JSON compatibility
+ if (quote === '"' && a === "'")
+ return a;
+
i = v.indexOf(b);
if (i + 1)
@@ -826,37 +888,44 @@ tinymce.create('static tinymce.util.JSON', {
a = b.charCodeAt().toString(16);
return '\\u' + '0000'.substring(a.length) + a;
- }) + '"';
+ }) + quote;
}
if (t == 'object') {
if (o.hasOwnProperty && o instanceof Array) {
for (i=0, v = '['; i 0 ? ',' : '') + s(o[i]);
+ v += (i > 0 ? ',' : '') + serialize(o[i], quote);
return v + ']';
}
v = '{';
- for (i in o)
- v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
+ for (i in o) {
+ if (o.hasOwnProperty(i)) {
+ v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
+ }
+ }
return v + '}';
}
return '' + o;
- },
+ };
- parse : function(s) {
- try {
- return eval('(' + s + ')');
- } catch (ex) {
- // Ignore
+ tinymce.util.JSON = {
+ serialize: serialize,
+
+ parse: function(s) {
+ try {
+ return eval('(' + s + ')');
+ } catch (ex) {
+ // Ignore
+ }
}
- }
- });
+ };
+})();
tinymce.create('static tinymce.util.XHR', {
send : function(o) {
@@ -948,7 +1017,8 @@ tinymce.create('static tinymce.util.XHR', {
};
o.error = function(ty, x) {
- ecb.call(o.error_scope || o.scope, ty, x);
+ if (ecb)
+ ecb.call(o.error_scope || o.scope, ty, x);
};
o.data = JSON.serialize({
@@ -970,5862 +1040,8219 @@ tinymce.create('static tinymce.util.XHR', {
}
});
}());
+(function(tinymce){
+ tinymce.VK = {
+ BACKSPACE: 8,
+ DELETE: 46,
+ DOWN: 40,
+ ENTER: 13,
+ LEFT: 37,
+ RIGHT: 39,
+ SPACEBAR: 32,
+ TAB: 9,
+ UP: 38,
+
+ modifierPressed: function (e) {
+ return e.shiftKey || e.ctrlKey || e.altKey;
+ }
+ }
+})(tinymce);
+
(function(tinymce) {
- // Shorten names
- var each = tinymce.each,
- is = tinymce.is,
- isWebKit = tinymce.isWebKit,
- isIE = tinymce.isIE,
- blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,
- boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
- mceAttribs = makeMap('src,href,style,coords,shape'),
- encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
- encodeCharsRe = /[<>&\"]/g,
- simpleSelectorRe = /^([a-z0-9],?)+$/i,
- tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
- attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+ var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
- function makeMap(str) {
- var map = {}, i;
+ function setEditorCommandState(editor, cmd, state) {
+ try {
+ editor.getDoc().execCommand(cmd, false, state);
+ } catch (ex) {
+ // Ignore
+ }
+ }
- str = str.split(',');
- for (i = str.length; i >= 0; i--)
- map[str[i]] = 1;
+ function cleanupStylesWhenDeleting(ed) {
+ var dom = ed.dom, selection = ed.selection;
- return map;
- };
+ ed.onKeyDown.add(function(ed, e) {
+ var rng, blockElm, node, clonedSpan, isDelete;
- tinymce.create('tinymce.dom.DOMUtils', {
- doc : null,
- root : null,
- files : null,
- pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
- props : {
- "for" : "htmlFor",
- "class" : "className",
- className : "className",
- checked : "checked",
- disabled : "disabled",
- maxlength : "maxLength",
- readonly : "readOnly",
- selected : "selected",
- value : "value",
- id : "id",
- name : "name",
- type : "type"
- },
+ if (e.isDefaultPrevented()) {
+ return;
+ }
- DOMUtils : function(d, s) {
- var t = this, globalStyle;
+ isDelete = e.keyCode == DELETE;
+ if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
+ e.preventDefault();
+ rng = selection.getRng();
- t.doc = d;
- t.win = window;
- t.files = {};
- t.cssFlicker = false;
- t.counter = 0;
- t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
- t.stdMode = d.documentMode === 8;
+ // Find root block
+ blockElm = dom.getParent(rng.startContainer, dom.isBlock);
- t.settings = s = tinymce.extend({
- keep_values : false,
- hex_colors : 1,
- process_html : 1
- }, s);
+ // On delete clone the root span of the next block element
+ if (isDelete)
+ blockElm = dom.getNext(blockElm, dom.isBlock);
- // Fix IE6SP2 flicker and check it failed for pre SP2
- if (tinymce.isIE6) {
- try {
- d.execCommand('BackgroundImageCache', false, true);
- } catch (e) {
- t.cssFlicker = true;
+ // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
+ if (blockElm) {
+ node = blockElm.firstChild;
+
+ // Ignore empty text nodes
+ while (node && node.nodeType == 3 && node.nodeValue.length == 0)
+ node = node.nextSibling;
+
+ if (node && node.nodeName === 'SPAN') {
+ clonedSpan = node.cloneNode(false);
+ }
}
- }
- // Build styles list
- if (s.valid_styles) {
- t._styles = {};
+ // Do the backspace/delete action
+ ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
+
+ // Find all odd apple-style-spans
+ blockElm = dom.getParent(rng.startContainer, dom.isBlock);
+ tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
+ var bm = selection.getBookmark();
- // Convert styles into a rule list
- each(s.valid_styles, function(value, key) {
- t._styles[key] = tinymce.explode(value);
+ if (clonedSpan) {
+ dom.replace(clonedSpan.cloneNode(false), span, true);
+ } else {
+ dom.remove(span, true);
+ }
+
+ // Restore the selection
+ selection.moveToBookmark(bm);
});
}
+ });
+ };
- tinymce.addUnload(t.destroy, t);
- },
+ function emptyEditorWhenDeleting(ed) {
+ function serializeRng(rng) {
+ var body = ed.dom.create("body");
+ var contents = rng.cloneContents();
+ body.appendChild(contents);
+ return ed.selection.serializer.serialize(body, {format: 'html'});
+ }
- getRoot : function() {
- var t = this, s = t.settings;
+ function allContentsSelected(rng) {
+ var selection = serializeRng(rng);
- return (s && t.get(s.root_element)) || t.doc.body;
- },
+ var allRng = ed.dom.createRng();
+ allRng.selectNode(ed.getBody());
- getViewPort : function(w) {
- var d, b;
+ var allSelection = serializeRng(allRng);
+ return selection === allSelection;
+ }
- w = !w ? this.win : w;
- d = w.document;
- b = this.boxModel ? d.documentElement : d.body;
+ ed.onKeyDown.addToTop(function(ed, e) {
+ var keyCode = e.keyCode;
+ if (keyCode == DELETE || keyCode == BACKSPACE) {
+ var rng = ed.selection.getRng(true);
+ if (!rng.collapsed && allContentsSelected(rng)) {
+ ed.setContent('', {format : 'raw'});
+ ed.nodeChanged();
+ e.preventDefault();
+ }
+ }
+ });
+ };
- // Returns viewport size excluding scrollbars
- return {
- x : w.pageXOffset || b.scrollLeft,
- y : w.pageYOffset || b.scrollTop,
- w : w.innerWidth || b.clientWidth,
- h : w.innerHeight || b.clientHeight
- };
- },
+ function inputMethodFocus(ed) {
+ ed.dom.bind(ed.getDoc(), 'focusin', function() {
+ ed.selection.setRng(ed.selection.getRng());
+ });
+ };
- getRect : function(e) {
- var p, t = this, sr;
+ function removeHrOnBackspace(ed) {
+ ed.onKeyDown.add(function(ed, e) {
+ if (e.keyCode === BACKSPACE) {
+ if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
+ var node = ed.selection.getNode();
+ var previousSibling = node.previousSibling;
+ if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
+ ed.dom.remove(previousSibling);
+ tinymce.dom.Event.cancel(e);
+ }
+ }
+ }
+ })
+ }
- e = t.get(e);
- p = t.getPos(e);
- sr = t.getSize(e);
+ function focusBody(ed) {
+ // Fix for a focus bug in FF 3.x where the body element
+ // wouldn't get proper focus if the user clicked on the HTML element
+ if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
+ ed.onMouseDown.add(function(ed, e) {
+ if (e.target.nodeName === "HTML") {
+ var body = ed.getBody();
+
+ // Blur the body it's focused but not correctly focused
+ body.blur();
+
+ // Refocus the body after a little while
+ setTimeout(function() {
+ body.focus();
+ }, 0);
+ }
+ });
+ }
+ };
- return {
- x : p.x,
- y : p.y,
- w : sr.w,
- h : sr.h
- };
- },
+ function selectControlElements(ed) {
+ ed.onClick.add(function(ed, e) {
+ e = e.target;
- getSize : function(e) {
- var t = this, w, h;
+ // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
+ // WebKit can't even do simple things like selecting an image
+ // Needs tobe the setBaseAndExtend or it will fail to select floated images
+ if (/^(IMG|HR)$/.test(e.nodeName))
+ ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);
- e = t.get(e);
- w = t.getStyle(e, 'width');
- h = t.getStyle(e, 'height');
+ if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))
+ ed.selection.select(e);
- // Non pixel value, then force offset/clientWidth
- if (w.indexOf('px') === -1)
- w = 0;
+ ed.nodeChanged();
+ });
+ };
- // Non pixel value, then force offset/clientWidth
- if (h.indexOf('px') === -1)
- h = 0;
+ function removeStylesWhenDeletingAccrossBlockElements(ed) {
+ var selection = ed.selection, dom = ed.dom;
- return {
- w : parseInt(w) || e.offsetWidth || e.clientWidth,
- h : parseInt(h) || e.offsetHeight || e.clientHeight
- };
- },
+ function getAttributeApplyFunction() {
+ var template = dom.getAttribs(selection.getStart().cloneNode(false));
- getParent : function(n, f, r) {
- return this.getParents(n, f, r, false);
- },
+ return function() {
+ var target = selection.getStart();
- getParents : function(n, f, r, c) {
- var t = this, na, se = t.settings, o = [];
+ if (target !== ed.getBody()) {
+ dom.setAttrib(target, "style", null);
- n = t.get(n);
- c = c === undefined;
+ tinymce.each(template, function(attr) {
+ target.setAttributeNode(attr.cloneNode(true));
+ });
+ }
+ };
+ }
- if (se.strict_root)
- r = r || t.getRoot();
+ function isSelectionAcrossElements() {
+ return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
+ }
- // Wrap node name as func
- if (is(f, 'string')) {
- na = f;
+ function blockEvent(ed, e) {
+ e.preventDefault();
+ return false;
+ }
- if (f === '*') {
- f = function(n) {return n.nodeType == 1;};
- } else {
- f = function(n) {
- return t.is(n, na);
- };
- }
+ ed.onKeyPress.add(function(ed, e) {
+ var applyAttributes;
+
+ if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
+ applyAttributes = getAttributeApplyFunction();
+ ed.getDoc().execCommand('delete', false, null);
+ applyAttributes();
+ e.preventDefault();
+ return false;
}
+ });
- while (n) {
- if (n == r || !n.nodeType || n.nodeType === 9)
- break;
+ dom.bind(ed.getDoc(), 'cut', function(e) {
+ var applyAttributes;
- if (!f || f(n)) {
- if (c)
- o.push(n);
- else
- return n;
- }
+ if (isSelectionAcrossElements()) {
+ applyAttributes = getAttributeApplyFunction();
+ ed.onKeyUp.addToTop(blockEvent);
- n = n.parentNode;
+ setTimeout(function() {
+ applyAttributes();
+ ed.onKeyUp.remove(blockEvent);
+ }, 0);
}
+ });
+ }
+
+ /*
+ function removeStylesOnPTagsInheritedFromHeadingTag(ed) {
+ ed.onKeyDown.add(function(ed, event) {
+ function checkInHeadingTag(ed) {
+ var currentNode = ed.selection.getNode();
+ var headingTags = 'h1,h2,h3,h4,h5,h6';
+ return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;
+ }
+
+ if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {
+ setTimeout(function() {
+ var currentNode = ed.selection.getNode();
+ if (ed.dom.is(currentNode, 'p')) {
+ ed.dom.setAttrib(currentNode, 'style', null);
+ // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'
+ ed.execCommand('mceCleanup');
+ }
+ }, 0);
+ }
+ });
+ }
+ */
- return c ? o : null;
- },
-
- get : function(e) {
- var n;
-
- if (e && this.doc && typeof(e) == 'string') {
- n = e;
- e = this.doc.getElementById(e);
+ function selectionChangeNodeChanged(ed) {
+ var lastRng, selectionTimer;
- // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
- if (e && e.id !== n)
- return this.doc.getElementsByName(n)[1];
+ ed.dom.bind(ed.getDoc(), 'selectionchange', function() {
+ if (selectionTimer) {
+ clearTimeout(selectionTimer);
+ selectionTimer = 0;
}
- return e;
- },
+ selectionTimer = window.setTimeout(function() {
+ var rng = ed.selection.getRng();
- getNext : function(node, selector) {
- return this._findSib(node, selector, 'nextSibling');
- },
+ // Compare the ranges to see if it was a real change or not
+ if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
+ ed.nodeChanged();
+ lastRng = rng;
+ }
+ }, 50);
+ });
+ }
- getPrev : function(node, selector) {
- return this._findSib(node, selector, 'previousSibling');
- },
+ function ensureBodyHasRoleApplication(ed) {
+ document.body.setAttribute("role", "application");
+ }
+
+ function disableBackspaceIntoATable(ed) {
+ ed.onKeyDown.add(function(ed, e) {
+ if (e.keyCode === BACKSPACE) {
+ if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
+ var previousSibling = ed.selection.getNode().previousSibling;
+ if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
+ return tinymce.dom.Event.cancel(e);
+ }
+ }
+ }
+ })
+ }
+ function addNewLinesBeforeBrInPre(editor) {
+ var documentMode = editor.getDoc().documentMode;
- select : function(pa, s) {
- var t = this;
+ // IE8+ rendering mode does the right thing with BR in PRE
+ if (documentMode && documentMode > 7) {
+ return;
+ }
- return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
- },
-
- is : function(n, selector) {
- var i;
-
- // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
- if (n.length === undefined) {
- // Simple all selector
- if (selector === '*')
- return n.nodeType == 1;
-
- // Simple selector just elements
- if (simpleSelectorRe.test(selector)) {
- selector = selector.toLowerCase().split(/,/);
- n = n.nodeName.toLowerCase();
+ // Enable display: none in area and add a specific class that hides all BR elements in PRE to
+ // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
+ setEditorCommandState(editor, 'RespectVisibilityInDesign', true);
+ editor.dom.addClass(editor.getBody(), 'mceHideBrInPre');
+
+ // Adds a \n before all BR elements in PRE to get them visual
+ editor.parser.addNodeFilter('pre', function(nodes, name) {
+ var i = nodes.length, brNodes, j, brElm, sibling;
+
+ while (i--) {
+ brNodes = nodes[i].getAll('br');
+ j = brNodes.length;
+ while (j--) {
+ brElm = brNodes[j];
+
+ // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
+ sibling = brElm.prev;
+ if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
+ sibling.value += '\n';
+ } else {
+ brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
+ }
+ }
+ }
+ });
- for (i = selector.length - 1; i >= 0; i--) {
- if (selector[i] == n)
- return true;
+ // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
+ editor.serializer.addNodeFilter('pre', function(nodes, name) {
+ var i = nodes.length, brNodes, j, brElm, sibling;
+
+ while (i--) {
+ brNodes = nodes[i].getAll('br');
+ j = brNodes.length;
+ while (j--) {
+ brElm = brNodes[j];
+ sibling = brElm.prev;
+ if (sibling && sibling.type == 3) {
+ sibling.value = sibling.value.replace(/\r?\n$/, '');
}
+ }
+ }
+ });
+ }
- return false;
+ tinymce.create('tinymce.util.Quirks', {
+ Quirks: function(ed) {
+ // All browsers
+ disableBackspaceIntoATable(ed);
+
+ // WebKit
+ if (tinymce.isWebKit) {
+ cleanupStylesWhenDeleting(ed);
+ emptyEditorWhenDeleting(ed);
+ inputMethodFocus(ed);
+ selectControlElements(ed);
+
+ // iOS
+ if (tinymce.isIDevice) {
+ selectionChangeNodeChanged(ed);
}
}
- return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
- },
+ // IE
+ if (tinymce.isIE) {
+ removeHrOnBackspace(ed);
+ emptyEditorWhenDeleting(ed);
+ ensureBodyHasRoleApplication(ed);
+ //removeStylesOnPTagsInheritedFromHeadingTag(ed)
+ addNewLinesBeforeBrInPre(ed);
+ }
+ // Gecko
+ if (tinymce.isGecko) {
+ removeHrOnBackspace(ed);
+ focusBody(ed);
+ removeStylesWhenDeletingAccrossBlockElements(ed);
+ }
+ }
+ });
+})(tinymce);
- add : function(p, n, a, h, c) {
- var t = this;
+(function(tinymce) {
+ var namedEntities, baseEntities, reverseEntities,
+ attrsCharsRegExp = /[&<>\"\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",
+ 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
+ 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
+ 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
+ 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
+ };
- return this.run(p, function(p) {
- var e, k;
+ // Raw entities
+ baseEntities = {
+ '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
+ "'" : ''',
+ '<' : '<',
+ '>' : '>',
+ '&' : '&'
+ };
- e = is(n, 'string') ? t.doc.createElement(n) : n;
- t.setAttribs(e, a);
+ // Reverse lookup table for raw entities
+ reverseEntities = {
+ '<' : '<',
+ '>' : '>',
+ '&' : '&',
+ '"' : '"',
+ ''' : "'"
+ };
- if (h) {
- if (h.nodeType)
- e.appendChild(h);
- else
- t.setHTML(e, h);
- }
+ // Decodes text by using the browser
+ function nativeDecode(text) {
+ var elm;
- return !c ? p.appendChild(e) : e;
- });
- },
+ elm = document.createElement("div");
+ elm.innerHTML = text;
- create : function(n, a, h) {
- return this.add(this.doc.createElement(n), n, a, h, 1);
- },
+ return elm.textContent || elm.innerText || text;
+ };
- createHTML : function(n, a, h) {
- var o = '', t = this, k;
+ // Build a two way lookup table for the entities
+ function buildEntitiesLookup(items, radix) {
+ var i, chr, entity, lookup = {};
- o += '<' + n;
+ if (items) {
+ items = items.split(',');
+ radix = radix || 10;
- for (k in a) {
- if (a.hasOwnProperty(k))
- o += ' ' + k + '="' + t.encode(a[k]) + '"';
+ // Build entities lookup table
+ for (i = 0; i < items.length; i += 2) {
+ chr = String.fromCharCode(parseInt(items[i], radix));
+
+ // Only add non base entities
+ if (!baseEntities[chr]) {
+ entity = '&' + items[i + 1] + ';';
+ lookup[chr] = entity;
+ lookup[entity] = chr;
+ }
}
- if (tinymce.is(h))
- return o + '>' + h + '' + n + '>';
+ return lookup;
+ }
+ };
- return o + ' />';
+ // Unpack entities lookup where the numbers are in radix 32 to reduce the size
+ namedEntities = buildEntitiesLookup(
+ '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
+ '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
+ '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
+ '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
+ '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
+ '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
+ '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
+ '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
+ '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
+ '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
+ 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
+ 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
+ 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
+ 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
+ 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
+ '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
+ '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
+ '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
+ '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
+ '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
+ 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
+ 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
+ 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
+ '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
+ '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
+ , 32);
+
+ tinymce.html = tinymce.html || {};
+
+ tinymce.html.Entities = {
+ encodeRaw : function(text, attr) {
+ return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
+ return baseEntities[chr] || chr;
+ });
},
- remove : function(node, keep_children) {
- return this.run(node, function(node) {
- var parent, child;
+ encodeAllRaw : function(text) {
+ return ('' + text).replace(rawCharsRegExp, function(chr) {
+ return baseEntities[chr] || chr;
+ });
+ },
- parent = node.parentNode;
+ encodeNumeric : function(text, attr) {
+ return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
+ // Multi byte sequence convert it to a single entity
+ if (chr.length > 1)
+ return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
- if (!parent)
- return null;
+ return baseEntities[chr] || '' + chr.charCodeAt(0) + ';';
+ });
+ },
- if (keep_children) {
- while (child = node.firstChild) {
- // IE 8 will crash if you don't remove completely empty text nodes
- if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
- parent.insertBefore(child, node);
- else
- node.removeChild(child);
- }
- }
+ encodeNamed : function(text, attr, entities) {
+ entities = entities || namedEntities;
- return parent.removeChild(node);
+ return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
+ return baseEntities[chr] || entities[chr] || chr;
});
},
- setStyle : function(n, na, v) {
- var t = this;
-
- return t.run(n, function(e) {
- var s, i;
+ getEncodeFunc : function(name, entities) {
+ var Entities = tinymce.html.Entities;
- s = e.style;
+ entities = buildEntitiesLookup(entities) || namedEntities;
- // Camelcase it, if needed
- na = na.replace(/-(\D)/g, function(a, b){
- return b.toUpperCase();
+ function encodeNamedAndNumeric(text, attr) {
+ return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
+ return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr;
});
+ };
- // Default px suffix on these
- if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
- v += 'px';
+ function encodeCustomNamed(text, attr) {
+ return Entities.encodeNamed(text, attr, entities);
+ };
- switch (na) {
- case 'opacity':
- // IE specific opacity
- if (isIE) {
- s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
+ // Replace + with , to be compatible with previous TinyMCE versions
+ name = tinymce.makeMap(name.replace(/\+/g, ','));
- if (!n.currentStyle || !n.currentStyle.hasLayout)
- s.display = 'inline-block';
- }
+ // Named and numeric encoder
+ if (name.named && name.numeric)
+ return encodeNamedAndNumeric;
- // Fix for older browsers
- s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
- break;
+ // Named encoder
+ if (name.named) {
+ // Custom names
+ if (entities)
+ return encodeCustomNamed;
- case 'float':
- isIE ? s.styleFloat = v : s.cssFloat = v;
- break;
-
- default:
- s[na] = v || '';
- }
+ return Entities.encodeNamed;
+ }
- // Force update of the style data
- if (t.settings.update_styles)
- t.setAttrib(e, '_mce_style');
- });
- },
+ // Numeric
+ if (name.numeric)
+ return Entities.encodeNumeric;
- getStyle : function(n, na, c) {
- n = this.get(n);
+ // Raw encoder
+ return Entities.encodeRaw;
+ },
- if (!n)
- return false;
+ decode : function(text) {
+ return text.replace(entityRegExp, function(all, numeric, value) {
+ if (numeric) {
+ value = parseInt(value, numeric.length === 2 ? 16 : 10);
- // Gecko
- if (this.doc.defaultView && c) {
- // Remove camelcase
- na = na.replace(/[A-Z]/g, function(a){
- return '-' + a;
- });
+ // Support upper UTF
+ if (value > 0xFFFF) {
+ value -= 0x10000;
- try {
- return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
- } catch (ex) {
- // Old safari might fail
- return null;
+ return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
+ } else
+ return asciiMap[value] || String.fromCharCode(value);
}
- }
- // Camelcase it, if needed
- na = na.replace(/-(\D)/g, function(a, b){
- return b.toUpperCase();
+ return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
});
+ }
+ };
+})(tinymce);
- if (na == 'float')
- na = isIE ? 'styleFloat' : 'cssFloat';
+tinymce.html.Styles = function(settings, schema) {
+ var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
+ urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
+ styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
+ trimRightRegExp = /\s+$/,
+ urlColorRegExp = /rgb/,
+ undef, i, encodingLookup = {}, encodingItems;
- // IE & Opera
- if (n.currentStyle && c)
- return n.currentStyle[na];
+ settings = settings || {};
- return n.style[na];
- },
+ encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
+ for (i = 0; i < encodingItems.length; i++) {
+ encodingLookup[encodingItems[i]] = '\uFEFF' + i;
+ encodingLookup['\uFEFF' + i] = encodingItems[i];
+ }
- setStyles : function(e, o) {
- var t = this, s = t.settings, ol;
+ function toHex(match, r, g, b) {
+ function hex(val) {
+ val = parseInt(val).toString(16);
- ol = s.update_styles;
- s.update_styles = 0;
+ return val.length > 1 ? val : '0' + val; // 0 -> 00
+ };
- each(o, function(v, n) {
- t.setStyle(e, n, v);
- });
+ return '#' + hex(r) + hex(g) + hex(b);
+ };
- // Update style info
- s.update_styles = ol;
- if (s.update_styles)
- t.setAttrib(e, s.cssText);
+ return {
+ toHex : function(color) {
+ return color.replace(rgbRegExp, toHex);
},
- setAttrib : function(e, n, v) {
- var t = this;
+ parse : function(css) {
+ var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
- // Whats the point
- if (!e || !n)
- return;
+ function compress(prefix, suffix) {
+ var top, right, bottom, left;
- // Strict XML mode
- if (t.settings.strict)
- n = n.toLowerCase();
+ // Get values and check it it needs compressing
+ top = styles[prefix + '-top' + suffix];
+ if (!top)
+ return;
- return this.run(e, function(e) {
- var s = t.settings;
+ right = styles[prefix + '-right' + suffix];
+ if (top != right)
+ return;
- switch (n) {
- case "style":
- if (!is(v, 'string')) {
- each(v, function(v, n) {
- t.setStyle(e, n, v);
- });
+ bottom = styles[prefix + '-bottom' + suffix];
+ if (right != bottom)
+ return;
- return;
- }
+ left = styles[prefix + '-left' + suffix];
+ if (bottom != left)
+ return;
- // No mce_style for elements with these since they might get resized by the user
- if (s.keep_values) {
- if (v && !t._isRes(v))
- e.setAttribute('_mce_style', v, 2);
- else
- e.removeAttribute('_mce_style', 2);
- }
+ // Compress
+ styles[prefix + suffix] = left;
+ delete styles[prefix + '-top' + suffix];
+ delete styles[prefix + '-right' + suffix];
+ delete styles[prefix + '-bottom' + suffix];
+ delete styles[prefix + '-left' + suffix];
+ };
- e.style.cssText = v;
- break;
+ function canCompress(key) {
+ var value = styles[key], i;
- case "class":
- e.className = v || ''; // Fix IE null bug
- break;
+ if (!value || value.indexOf(' ') < 0)
+ return;
- case "src":
- case "href":
- if (s.keep_values) {
- if (s.url_converter)
- v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
+ value = value.split(' ');
+ i = value.length;
+ while (i--) {
+ if (value[i] !== value[0])
+ return false;
+ }
- t.setAttrib(e, '_mce_' + n, v, 2);
- }
+ styles[key] = value[0];
- break;
-
- case "shape":
- e.setAttribute('_mce_style', v);
- break;
- }
+ return true;
+ };
- if (is(v) && v !== null && v.length !== 0)
- e.setAttribute(n, '' + v, 2);
- else
- e.removeAttribute(n, 2);
- });
- },
+ function compress2(target, a, b, c) {
+ if (!canCompress(a))
+ return;
- setAttribs : function(e, o) {
- var t = this;
+ if (!canCompress(b))
+ return;
- return this.run(e, function(e) {
- each(o, function(v, n) {
- t.setAttrib(e, n, v);
- });
- });
- },
+ if (!canCompress(c))
+ return;
- getAttrib : function(e, n, dv) {
- var v, t = this;
+ // Compress
+ styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
+ delete styles[a];
+ delete styles[b];
+ delete styles[c];
+ };
- e = t.get(e);
+ // Encodes the specified string by replacing all \" \' ; : with _
+ function encode(str) {
+ isEncoded = true;
- if (!e || e.nodeType !== 1)
- return false;
+ return encodingLookup[str];
+ };
- if (!is(dv))
- dv = '';
+ // Decodes the specified string by replacing all _ with it's original value \" \' etc
+ // It will also decode the \" \' if keep_slashes is set to fale or omitted
+ function decode(str, keep_slashes) {
+ if (isEncoded) {
+ str = str.replace(/\uFEFF[0-9]/g, function(str) {
+ return encodingLookup[str];
+ });
+ }
- // Try the mce variant for these
- if (/^(src|href|style|coords|shape)$/.test(n)) {
- v = e.getAttribute("_mce_" + n);
+ if (!keep_slashes)
+ str = str.replace(/\\([\'\";:])/g, "$1");
- if (v)
- return v;
+ return str;
}
- if (isIE && t.props[n]) {
- v = e[t.props[n]];
- v = v && v.nodeValue ? v.nodeValue : v;
- }
+ if (css) {
+ // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
+ css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
+ return str.replace(/[;:]/g, encode);
+ });
- if (!v)
- v = e.getAttribute(n, 2);
+ // Parse styles
+ while (matches = styleRegExp.exec(css)) {
+ name = matches[1].replace(trimRightRegExp, '').toLowerCase();
+ value = matches[2].replace(trimRightRegExp, '');
- // Check boolean attribs
- if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
- if (e[t.props[n]] === true && v === '')
- return n;
+ if (name && value.length > 0) {
+ // Opera will produce 700 instead of bold in their style values
+ if (name === 'font-weight' && value === '700')
+ value = 'bold';
+ else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
+ value = value.toLowerCase();
- return v ? n : '';
- }
+ // Convert RGB colors to HEX
+ value = value.replace(rgbRegExp, toHex);
- // Inner input elements will override attributes on form elements
- if (e.nodeName === "FORM" && e.getAttributeNode(n))
- return e.getAttributeNode(n).nodeValue;
+ // Convert URLs and force them into url('value') format
+ value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
+ str = str || str2;
- if (n === 'style') {
- v = v || e.style.cssText;
+ if (str) {
+ str = decode(str);
- if (v) {
- v = t.serializeStyle(t.parseStyle(v), e.nodeName);
+ // Force strings into single quote format
+ return "'" + str.replace(/\'/g, "\\'") + "'";
+ }
- if (t.settings.keep_values && !t._isRes(v))
- e.setAttribute('_mce_style', v);
+ url = decode(url || url2 || url3);
+
+ // Convert the URL to relative/absolute depending on config
+ if (urlConverter)
+ url = urlConverter.call(urlConverterScope, url, 'style');
+
+ // Output new URL format
+ return "url('" + url.replace(/\'/g, "\\'") + "')";
+ });
+
+ styles[name] = isEncoded ? decode(value, true) : value;
+ }
+
+ styleRegExp.lastIndex = matches.index + matches[0].length;
}
+
+ // Compress the styles to reduce it's size for example IE will expand styles
+ compress("border", "");
+ compress("border", "-width");
+ compress("border", "-color");
+ compress("border", "-style");
+ compress("padding", "");
+ compress("margin", "");
+ compress2('border', 'border-width', 'border-style', 'border-color');
+
+ // Remove pointless border, IE produces these
+ if (styles.border === 'medium none')
+ delete styles.border;
}
- // Remove Apple and WebKit stuff
- if (isWebKit && n === "class" && v)
- v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
+ return styles;
+ },
- // Handle IE issues
- if (isIE) {
- switch (n) {
- case 'rowspan':
- case 'colspan':
- // IE returns 1 as default value
- if (v === 1)
- v = '';
+ serialize : function(styles, element_name) {
+ var css = '', name, value;
- break;
+ function serializeStyles(name) {
+ var styleList, i, l, value;
- case 'size':
- // IE returns +0 as default value for size
- if (v === '+0' || v === 20 || v === 0)
- v = '';
+ styleList = schema.styles[name];
+ if (styleList) {
+ for (i = 0, l = styleList.length; i < l; i++) {
+ name = styleList[i];
+ value = styles[name];
- break;
+ if (value !== undef && value.length > 0)
+ css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
+ }
+ }
+ };
- case 'width':
- case 'height':
- case 'vspace':
- case 'checked':
- case 'disabled':
- case 'readonly':
- if (v === 0)
- v = '';
+ // Serialize styles according to schema
+ if (element_name && schema && schema.styles) {
+ // Serialize global styles and element specific styles
+ serializeStyles('*');
+ serializeStyles(element_name);
+ } else {
+ // Output the styles in the order they are inside the object
+ for (name in styles) {
+ value = styles[name];
- break;
+ if (value !== undef && value.length > 0)
+ css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
+ }
+ }
- case 'hspace':
- // IE returns -1 as default value
- if (v === -1)
- v = '';
+ return css;
+ }
+ };
+};
- break;
+(function(tinymce) {
+ var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
- case 'maxlength':
- case 'tabindex':
- // IE returns default value
- if (v === 32768 || v === 2147483647 || v === '32768')
- v = '';
+ function split(str, delim) {
+ return str.split(delim || ',');
+ };
- break;
+ function unpack(lookup, data) {
+ var key, elements = {};
- case 'multiple':
- case 'compact':
- case 'noshade':
- case 'nowrap':
- if (v === 65535)
- return n;
+ function replace(value) {
+ return value.replace(/[A-Z]+/g, function(key) {
+ return replace(lookup[key]);
+ });
+ };
- return dv;
+ // Unpack lookup
+ for (key in lookup) {
+ if (lookup.hasOwnProperty(key))
+ lookup[key] = replace(lookup[key]);
+ }
- case 'shape':
- v = v.toLowerCase();
- break;
+ // Unpack and parse data into object map
+ replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
+ attributes = split(attributes, '|');
- default:
- // IE has odd anonymous function for event attributes
- if (n.indexOf('on') === 0 && v)
- v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
- }
+ elements[name] = {
+ attributes : makeMap(attributes),
+ attributesOrder : attributes,
+ children : makeMap(children, '|', {'#comment' : {}})
}
+ });
- return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
- },
+ return elements;
+ };
- getPos : function(n, ro) {
- var t = this, x = 0, y = 0, e, d = t.doc, r;
+ function getHTML5() {
+ var html5 = mapCache.html5;
+
+ if (!html5) {
+ html5 = mapCache.html5 = unpack({
+ A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',
+ B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',
+ C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
+ }, 'html[A|manifest][body|head]' +
+ 'head[A][base|command|link|meta|noscript|script|style|title]' +
+ 'title[A][#]' +
+ 'base[A|href|target][]' +
+ 'link[A|href|rel|media|type|sizes][]' +
+ 'meta[A|http-equiv|name|content|charset][]' +
+ 'style[A|type|media|scoped][#]' +
+ 'script[A|charset|type|src|defer|async][#]' +
+ 'noscript[A][C]' +
+ 'body[A][C]' +
+ 'section[A][C]' +
+ 'nav[A][C]' +
+ 'article[A][C]' +
+ 'aside[A][C]' +
+ 'h1[A][B]' +
+ 'h2[A][B]' +
+ 'h3[A][B]' +
+ 'h4[A][B]' +
+ 'h5[A][B]' +
+ 'h6[A][B]' +
+ 'hgroup[A][h1|h2|h3|h4|h5|h6]' +
+ 'header[A][C]' +
+ 'footer[A][C]' +
+ 'address[A][C]' +
+ 'p[A][B]' +
+ 'br[A][]' +
+ 'pre[A][B]' +
+ 'dialog[A][dd|dt]' +
+ 'blockquote[A|cite][C]' +
+ 'ol[A|start|reversed][li]' +
+ 'ul[A][li]' +
+ 'li[A|value][C]' +
+ 'dl[A][dd|dt]' +
+ 'dt[A][B]' +
+ 'dd[A][C]' +
+ 'a[A|href|target|ping|rel|media|type][C]' +
+ 'em[A][B]' +
+ 'strong[A][B]' +
+ 'small[A][B]' +
+ 'cite[A][B]' +
+ 'q[A|cite][B]' +
+ 'dfn[A][B]' +
+ 'abbr[A][B]' +
+ 'code[A][B]' +
+ 'var[A][B]' +
+ 'samp[A][B]' +
+ 'kbd[A][B]' +
+ 'sub[A][B]' +
+ 'sup[A][B]' +
+ 'i[A][B]' +
+ 'b[A][B]' +
+ 'mark[A][B]' +
+ 'progress[A|value|max][B]' +
+ 'meter[A|value|min|max|low|high|optimum][B]' +
+ 'time[A|datetime][B]' +
+ 'ruby[A][B|rt|rp]' +
+ 'rt[A][B]' +
+ 'rp[A][B]' +
+ 'bdo[A][B]' +
+ 'span[A][B]' +
+ 'ins[A|cite|datetime][B]' +
+ 'del[A|cite|datetime][B]' +
+ 'figure[A][C|legend]' +
+ 'img[A|alt|src|height|width|usemap|ismap][]' +
+ 'iframe[A|name|src|height|width|sandbox|seamless][]' +
+ 'embed[A|src|height|width|type][]' +
+ 'object[A|data|type|height|width|usemap|name|form|classid][param]' +
+ 'param[A|name|value][]' +
+ 'details[A|open][C|legend]' +
+ 'command[A|type|label|icon|disabled|checked|radiogroup][]' +
+ 'menu[A|type|label][C|li]' +
+ 'legend[A][C|B]' +
+ 'div[A][C]' +
+ 'source[A|src|type|media][]' +
+ 'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
+ 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
+ 'hr[A][]' +
+ 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
+ 'fieldset[A|disabled|form|name][C|legend]' +
+ 'label[A|form|for][B]' +
+ 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
+ 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
+ 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
+ 'datalist[A][B|option]' +
+ 'optgroup[A|disabled|label][option]' +
+ 'option[A|disabled|selected|label|value][]' +
+ 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
+ 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
+ 'output[A|for|form|name][B]' +
+ 'canvas[A|width|height][]' +
+ 'map[A|name][B|C]' +
+ 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
+ 'mathml[A][]' +
+ 'svg[A][]' +
+ 'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
+ 'caption[A][C]' +
+ 'colgroup[A|span][col]' +
+ 'col[A|span][]' +
+ 'thead[A][tr]' +
+ 'tfoot[A][tr]' +
+ 'tbody[A][tr]' +
+ 'tr[A][th|td]' +
+ 'th[A|headers|rowspan|colspan|scope][B]' +
+ 'td[A|headers|rowspan|colspan][C]'
+ );
+ }
- n = t.get(n);
- ro = ro || d.body;
+ return html5;
+ };
- if (n) {
- // Use getBoundingClientRect on IE, Opera has it but it's not perfect
- if (isIE && !t.stdMode) {
- n = n.getBoundingClientRect();
- e = t.boxModel ? d.documentElement : d.body;
- x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
- x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
- n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
+ function getHTML4() {
+ var html4 = mapCache.html4;
+
+ if (!html4) {
+ // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
+ html4 = mapCache.html4 = unpack({
+ Z : 'H|K|N|O|P',
+ Y : 'X|form|R|Q',
+ ZG : 'E|span|width|align|char|charoff|valign',
+ X : 'p|T|div|U|W|isindex|fieldset|table',
+ ZF : 'E|align|char|charoff|valign',
+ W : 'pre|hr|blockquote|address|center|noframes',
+ ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
+ ZD : '[E][S]',
+ U : 'ul|ol|dl|menu|dir',
+ ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
+ T : 'h1|h2|h3|h4|h5|h6',
+ ZB : 'X|S|Q',
+ S : 'R|P',
+ ZA : 'a|G|J|M|O|P',
+ R : 'a|H|K|N|O',
+ Q : 'noscript|P',
+ P : 'ins|del|script',
+ O : 'input|select|textarea|label|button',
+ N : 'M|L',
+ M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
+ L : 'sub|sup',
+ K : 'J|I',
+ J : 'tt|i|b|u|s|strike',
+ I : 'big|small|font|basefont',
+ H : 'G|F',
+ G : 'br|span|bdo',
+ F : 'object|applet|img|map|iframe',
+ E : 'A|B|C',
+ D : 'accesskey|tabindex|onfocus|onblur',
+ C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
+ B : 'lang|xml:lang|dir',
+ A : 'id|class|style|title'
+ }, 'script[id|charset|type|language|src|defer|xml:space][]' +
+ 'style[B|id|type|media|title|xml:space][]' +
+ 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
+ 'param[id|name|value|valuetype|type][]' +
+ 'p[E|align][#|S]' +
+ 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
+ 'br[A|clear][]' +
+ 'span[E][#|S]' +
+ 'bdo[A|C|B][#|S]' +
+ 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
+ 'h1[E|align][#|S]' +
+ 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
+ 'map[B|C|A|name][X|form|Q|area]' +
+ 'h2[E|align][#|S]' +
+ 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
+ 'h3[E|align][#|S]' +
+ 'tt[E][#|S]' +
+ 'i[E][#|S]' +
+ 'b[E][#|S]' +
+ 'u[E][#|S]' +
+ 's[E][#|S]' +
+ 'strike[E][#|S]' +
+ 'big[E][#|S]' +
+ 'small[E][#|S]' +
+ 'font[A|B|size|color|face][#|S]' +
+ 'basefont[id|size|color|face][]' +
+ 'em[E][#|S]' +
+ 'strong[E][#|S]' +
+ 'dfn[E][#|S]' +
+ 'code[E][#|S]' +
+ 'q[E|cite][#|S]' +
+ 'samp[E][#|S]' +
+ 'kbd[E][#|S]' +
+ 'var[E][#|S]' +
+ 'cite[E][#|S]' +
+ 'abbr[E][#|S]' +
+ 'acronym[E][#|S]' +
+ 'sub[E][#|S]' +
+ 'sup[E][#|S]' +
+ 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
+ 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
+ 'optgroup[E|disabled|label][option]' +
+ 'option[E|selected|disabled|label|value][]' +
+ 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
+ 'label[E|for|accesskey|onfocus|onblur][#|S]' +
+ 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
+ 'h4[E|align][#|S]' +
+ 'ins[E|cite|datetime][#|Y]' +
+ 'h5[E|align][#|S]' +
+ 'del[E|cite|datetime][#|Y]' +
+ 'h6[E|align][#|S]' +
+ 'div[E|align][#|Y]' +
+ 'ul[E|type|compact][li]' +
+ 'li[E|type|value][#|Y]' +
+ 'ol[E|type|compact|start][li]' +
+ 'dl[E|compact][dt|dd]' +
+ 'dt[E][#|S]' +
+ 'dd[E][#|Y]' +
+ 'menu[E|compact][li]' +
+ 'dir[E|compact][li]' +
+ 'pre[E|width|xml:space][#|ZA]' +
+ 'hr[E|align|noshade|size|width][]' +
+ 'blockquote[E|cite][#|Y]' +
+ 'address[E][#|S|p]' +
+ 'center[E][#|Y]' +
+ 'noframes[E][#|Y]' +
+ 'isindex[A|B|prompt][]' +
+ 'fieldset[E][#|legend|Y]' +
+ 'legend[E|accesskey|align][#|S]' +
+ 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
+ 'caption[E|align][#|S]' +
+ 'col[ZG][]' +
+ 'colgroup[ZG][col]' +
+ 'thead[ZF][tr]' +
+ 'tr[ZF|bgcolor][th|td]' +
+ 'th[E|ZE][#|Y]' +
+ 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
+ 'noscript[E][#|Y]' +
+ 'td[E|ZE][#|Y]' +
+ 'tfoot[ZF][tr]' +
+ 'tbody[ZF][tr]' +
+ 'area[E|D|shape|coords|href|nohref|alt|target][]' +
+ 'base[id|href|target][]' +
+ 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
+ );
+ }
- return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
- }
+ return html4;
+ };
- r = n;
- while (r && r != ro && r.nodeType) {
- x += r.offsetLeft || 0;
- y += r.offsetTop || 0;
- r = r.offsetParent;
- }
+ tinymce.html.Schema = function(settings) {
+ var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
+ var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
- r = n.parentNode;
- while (r && r != ro && r.nodeType) {
- x -= r.scrollLeft || 0;
- y -= r.scrollTop || 0;
- r = r.parentNode;
+ // Creates an lookup table map object for the specified option or the default value
+ function createLookupTable(option, default_value, extend) {
+ var value = settings[option];
+
+ if (!value) {
+ // Get cached default map or make it if needed
+ value = mapCache[option];
+
+ if (!value) {
+ value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
+ value = tinymce.extend(value, extend);
+
+ mapCache[option] = value;
}
+ } else {
+ // Create custom map
+ value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
}
- return {x : x, y : y};
- },
+ return value;
+ };
- parseStyle : function(st) {
- var t = this, s = t.settings, o = {};
+ settings = settings || {};
+ schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
- if (!st)
- return o;
+ // Allow all elements and attributes if verify_html is set to false
+ if (settings.verify_html === false)
+ settings.valid_elements = '*[*]';
- function compress(p, s, ot) {
- var t, r, b, l;
+ // Build styles list
+ if (settings.valid_styles) {
+ validStyles = {};
- // Get values and check it it needs compressing
- t = o[p + '-top' + s];
- if (!t)
- return;
+ // Convert styles into a rule list
+ each(settings.valid_styles, function(value, key) {
+ validStyles[key] = tinymce.explode(value);
+ });
+ }
- r = o[p + '-right' + s];
- if (t != r)
- return;
+ // Setup map objects
+ whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
+ selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options p td tfoot th thead tr');
+ shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source');
+ boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
+ nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
+ blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' +
+ 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' +
+ 'noscript menu isindex samp header footer article section hgroup aside nav');
+
+ // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
+ function patternToRegExp(str) {
+ return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
+ };
- b = o[p + '-bottom' + s];
- if (r != b)
- return;
+ // Parses the specified valid_elements string and adds to the current rules
+ // This function is a bit hard to read since it's heavily optimized for speed
+ function addValidElements(valid_elements) {
+ var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
+ prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
+ elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
+ attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
+ hasPatternsRegExp = /[*?+]/;
+
+ if (valid_elements) {
+ // Split valid elements into an array with rules
+ valid_elements = split(valid_elements);
+
+ if (elements['@']) {
+ globalAttributes = elements['@'].attributes;
+ globalAttributesOrder = elements['@'].attributesOrder;
+ }
- l = o[p + '-left' + s];
- if (b != l)
- return;
+ // Loop all rules
+ for (ei = 0, el = valid_elements.length; ei < el; ei++) {
+ // Parse element rule
+ matches = elementRuleRegExp.exec(valid_elements[ei]);
+ if (matches) {
+ // Setup local names for matches
+ prefix = matches[1];
+ elementName = matches[2];
+ outputName = matches[3];
+ attrData = matches[4];
+
+ // Create new attributes and attributesOrder
+ attributes = {};
+ attributesOrder = [];
+
+ // Create the new element
+ element = {
+ attributes : attributes,
+ attributesOrder : attributesOrder
+ };
- // Compress
- o[ot] = l;
- delete o[p + '-top' + s];
- delete o[p + '-right' + s];
- delete o[p + '-bottom' + s];
- delete o[p + '-left' + s];
- };
+ // Padd empty elements prefix
+ if (prefix === '#')
+ element.paddEmpty = true;
- function compress2(ta, a, b, c) {
- var t;
+ // Remove empty elements prefix
+ if (prefix === '-')
+ element.removeEmpty = true;
- t = o[a];
- if (!t)
- return;
+ // Copy attributes from global rule into current rule
+ if (globalAttributes) {
+ for (key in globalAttributes)
+ attributes[key] = globalAttributes[key];
- t = o[b];
- if (!t)
- return;
+ attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
+ }
- t = o[c];
- if (!t)
- return;
+ // Attributes defined
+ if (attrData) {
+ attrData = split(attrData, '|');
+ for (ai = 0, al = attrData.length; ai < al; ai++) {
+ matches = attrRuleRegExp.exec(attrData[ai]);
+ if (matches) {
+ attr = {};
+ attrType = matches[1];
+ attrName = matches[2].replace(/::/g, ':');
+ prefix = matches[3];
+ value = matches[4];
+
+ // Required
+ if (attrType === '!') {
+ element.attributesRequired = element.attributesRequired || [];
+ element.attributesRequired.push(attrName);
+ attr.required = true;
+ }
- // Compress
- o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
- delete o[a];
- delete o[b];
- delete o[c];
- };
+ // Denied from global
+ if (attrType === '-') {
+ delete attributes[attrName];
+ attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
+ continue;
+ }
- st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
+ // Default value
+ if (prefix) {
+ // Default value
+ if (prefix === '=') {
+ element.attributesDefault = element.attributesDefault || [];
+ element.attributesDefault.push({name: attrName, value: value});
+ attr.defaultValue = value;
+ }
- each(st.split(';'), function(v) {
- var sv, ur = [];
+ // Forced value
+ if (prefix === ':') {
+ element.attributesForced = element.attributesForced || [];
+ element.attributesForced.push({name: attrName, value: value});
+ attr.forcedValue = value;
+ }
- if (v) {
- v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
- v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
- v = v.split(':');
- sv = tinymce.trim(v[1]);
- sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
-
- sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
- return t.toHex(v);
- });
+ // Required values
+ if (prefix === '<')
+ attr.validValues = makeMap(value, '?');
+ }
- if (s.url_converter) {
- sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
- return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
- });
- }
+ // Check for attribute patterns
+ if (hasPatternsRegExp.test(attrName)) {
+ element.attributePatterns = element.attributePatterns || [];
+ attr.pattern = patternToRegExp(attrName);
+ element.attributePatterns.push(attr);
+ } else {
+ // Add attribute to order list if it doesn't already exist
+ if (!attributes[attrName])
+ attributesOrder.push(attrName);
- o[tinymce.trim(v[0]).toLowerCase()] = sv;
- }
- });
+ attributes[attrName] = attr;
+ }
+ }
+ }
+ }
- compress("border", "", "border");
- compress("border", "-width", "border-width");
- compress("border", "-color", "border-color");
- compress("border", "-style", "border-style");
- compress("padding", "", "padding");
- compress("margin", "", "margin");
- compress2('border', 'border-width', 'border-style', 'border-color');
+ // Global rule, store away these for later usage
+ if (!globalAttributes && elementName == '@') {
+ globalAttributes = attributes;
+ globalAttributesOrder = attributesOrder;
+ }
- if (isIE) {
- // Remove pointless border
- if (o.border == 'medium none')
- o.border = '';
+ // Handle substitute elements such as b/strong
+ if (outputName) {
+ element.outputName = elementName;
+ elements[outputName] = element;
+ }
+
+ // Add pattern or exact element
+ if (hasPatternsRegExp.test(elementName)) {
+ element.pattern = patternToRegExp(elementName);
+ patternElements.push(element);
+ } else
+ elements[elementName] = element;
+ }
+ }
}
+ };
- return o;
- },
+ function setValidElements(valid_elements) {
+ elements = {};
+ patternElements = [];
- serializeStyle : function(o, name) {
- var t = this, s = '';
+ addValidElements(valid_elements);
- function add(v, k) {
- if (k && v) {
- // Remove browser specific styles like -moz- or -webkit-
- if (k.indexOf('-') === 0)
- return;
+ each(schemaItems, function(element, name) {
+ children[name] = element.children;
+ });
+ };
- switch (k) {
- case 'font-weight':
- // Opera will output bold as 700
- if (v == 700)
- v = 'bold';
+ // Adds custom non HTML elements to the schema
+ function addCustomElements(custom_elements) {
+ var customElementRegExp = /^(~)?(.+)$/;
- break;
+ if (custom_elements) {
+ each(split(custom_elements), function(rule) {
+ var matches = customElementRegExp.exec(rule),
+ inline = matches[1] === '~',
+ cloneName = inline ? 'span' : 'div',
+ name = matches[2];
- case 'color':
- case 'background-color':
- v = v.toLowerCase();
- break;
- }
+ children[name] = children[cloneName];
+ customElementsMap[name] = cloneName;
- s += (s ? ' ' : '') + k + ': ' + v + ';';
- }
- };
+ // If it's not marked as inline then add it to valid block elements
+ if (!inline)
+ blockElementsMap[name] = {};
- // Validate style output
- if (name && t._styles) {
- each(t._styles['*'], function(name) {
- add(o[name], name);
+ // Add custom elements at span/div positions
+ each(children, function(element, child) {
+ if (element[cloneName])
+ element[name] = element[cloneName];
+ });
});
+ }
+ };
+
+ // Adds valid children to the schema object
+ function addValidChildren(valid_children) {
+ var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
+
+ if (valid_children) {
+ each(split(valid_children), function(rule) {
+ var matches = childRuleRegExp.exec(rule), parent, prefix;
+
+ if (matches) {
+ prefix = matches[1];
+
+ // Add/remove items from default
+ if (prefix)
+ parent = children[matches[2]];
+ else
+ parent = children[matches[2]] = {'#comment' : {}};
+
+ parent = children[matches[2]];
- each(t._styles[name.toLowerCase()], function(name) {
- add(o[name], name);
+ each(split(matches[3], '|'), function(child) {
+ if (prefix === '-')
+ delete parent[child];
+ else
+ parent[child] = {};
+ });
+ }
});
- } else
- each(o, add);
+ }
+ };
- return s;
- },
+ function getElementRule(name) {
+ var element = elements[name], i;
- loadCSS : function(u) {
- var t = this, d = t.doc, head;
+ // Exact match found
+ if (element)
+ return element;
- if (!u)
- u = '';
+ // No exact match then try the patterns
+ i = patternElements.length;
+ while (i--) {
+ element = patternElements[i];
- head = t.select('head')[0];
+ if (element.pattern.test(name))
+ return element;
+ }
+ };
- each(u.split(','), function(u) {
- var link;
+ if (!settings.valid_elements) {
+ // No valid elements defined then clone the elements from the schema spec
+ each(schemaItems, function(element, name) {
+ elements[name] = {
+ attributes : element.attributes,
+ attributesOrder : element.attributesOrder
+ };
- if (t.files[u])
- return;
+ children[name] = element.children;
+ });
- t.files[u] = true;
- link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
+ // Switch these on HTML4
+ if (settings.schema != "html5") {
+ each(split('strong/b,em/i'), function(item) {
+ item = split(item, '/');
+ elements[item[1]].outputName = item[0];
+ });
+ }
- // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
- // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
- // It's ugly but it seems to work fine.
- if (isIE && d.documentMode) {
- link.onload = function() {
- d.recalc();
- link.onload = null;
- };
+ // Add default alt attribute for images
+ elements.img.attributesDefault = [{name: 'alt', value: ''}];
+
+ // Remove these if they are empty by default
+ each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
+ if (elements[name]) {
+ elements[name].removeEmpty = true;
}
+ });
- head.appendChild(link);
+ // Padd these by default
+ each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
+ elements[name].paddEmpty = true;
});
- },
+ } else
+ setValidElements(settings.valid_elements);
- addClass : function(e, c) {
- return this.run(e, function(e) {
- var o;
+ addCustomElements(settings.custom_elements);
+ addValidChildren(settings.valid_children);
+ addValidElements(settings.extended_valid_elements);
- if (!c)
- return 0;
+ // Todo: Remove this when we fix list handling to be valid
+ addValidChildren('+ol[ul|ol],+ul[ul|ol]');
- if (this.hasClass(e, c))
- return e.className;
+ // Delete invalid elements
+ if (settings.invalid_elements) {
+ tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
+ if (elements[item])
+ delete elements[item];
+ });
+ }
- o = this.removeClass(e, c);
+ // If the user didn't allow span only allow internal spans
+ if (!getElementRule('span'))
+ addValidElements('span[!data-mce-type|*]');
- return e.className = (o != '' ? (o + ' ') : '') + c;
- });
- },
+ self.children = children;
- removeClass : function(e, c) {
- var t = this, re;
+ self.styles = validStyles;
- return t.run(e, function(e) {
- var v;
+ self.getBoolAttrs = function() {
+ return boolAttrMap;
+ };
- if (t.hasClass(e, c)) {
- if (!re)
- re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
+ self.getBlockElements = function() {
+ return blockElementsMap;
+ };
- v = e.className.replace(re, ' ');
- v = tinymce.trim(v != ' ' ? v : '');
+ self.getShortEndedElements = function() {
+ return shortEndedElementsMap;
+ };
- e.className = v;
+ self.getSelfClosingElements = function() {
+ return selfClosingElementsMap;
+ };
- // Empty class attr
- if (!v) {
- e.removeAttribute('class');
- e.removeAttribute('className');
- }
+ self.getNonEmptyElements = function() {
+ return nonEmptyElementsMap;
+ };
- return v;
- }
+ self.getWhiteSpaceElements = function() {
+ return whiteSpaceElementsMap;
+ };
- return e.className;
- });
- },
+ self.isValidChild = function(name, child) {
+ var parent = children[name];
- hasClass : function(n, c) {
- n = this.get(n);
+ return !!(parent && parent[child]);
+ };
- if (!n || !c)
- return false;
+ self.getElementRule = getElementRule;
- return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
- },
+ self.getCustomElements = function() {
+ return customElementsMap;
+ };
- show : function(e) {
- return this.setStyle(e, 'display', 'block');
- },
+ self.addValidElements = addValidElements;
- hide : function(e) {
- return this.setStyle(e, 'display', 'none');
- },
+ self.setValidElements = setValidElements;
- isHidden : function(e) {
- e = this.get(e);
+ self.addCustomElements = addCustomElements;
- return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
- },
+ self.addValidChildren = addValidChildren;
+ };
+})(tinymce);
- uniqueId : function(p) {
- return (!p ? 'mce_' : p) + (this.counter++);
- },
+(function(tinymce) {
+ tinymce.html.SaxParser = function(settings, schema) {
+ var self = this, noop = function() {};
- setHTML : function(e, h) {
- var t = this;
+ settings = settings || {};
+ self.schema = schema = schema || new tinymce.html.Schema();
- return this.run(e, function(e) {
- var x, i, nl, n, p, x;
+ if (settings.fix_self_closing !== false)
+ settings.fix_self_closing = true;
- h = t.processHTML(h);
+ // Add handler functions from settings and setup default handlers
+ tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
+ if (name)
+ self[name] = settings[name] || noop;
+ });
- if (isIE) {
- function set() {
- // Remove all child nodes
- while (e.firstChild)
- e.firstChild.removeNode();
+ self.parse = function(html) {
+ var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
+ shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
+ validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
+ tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
- try {
- // IE will remove comments from the beginning
- // unless you padd the contents with something
- e.innerHTML = '
' + h;
- e.removeChild(e.firstChild);
- } catch (ex) {
- // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
- // This seems to fix this problem
-
- // Create new div with HTML contents and a BR infront to keep comments
- x = t.create('div');
- x.innerHTML = '
' + h;
-
- // Add all children from div to target
- each (x.childNodes, function(n, i) {
- // Skip br element
- if (i)
- e.appendChild(n);
- });
- }
- };
+ function processEndTag(name) {
+ var pos, i;
- // IE has a serious bug when it comes to paragraphs it can produce an invalid
- // DOM tree if contents like this
is inserted
- // It seems to be that IE doesn't like a root block element placed inside another root block element
- if (t.settings.fix_ie_paragraphs)
- h = h.replace(/<\/p>|
]+)><\/p>|
/gi, '
');
+ // Find position of parent of the same type
+ pos = stack.length;
+ while (pos--) {
+ if (stack[pos].name === name)
+ break;
+ }
- set();
+ // Found parent
+ if (pos >= 0) {
+ // Close all the open elements
+ for (i = stack.length - 1; i >= pos; i--) {
+ name = stack[i];
- if (t.settings.fix_ie_paragraphs) {
- // Check for odd paragraphs this is a sign of a broken DOM
- nl = e.getElementsByTagName("p");
- for (i = nl.length - 1, x = 0; i >= 0; i--) {
- n = nl[i];
+ if (name.valid)
+ self.end(name.name);
+ }
- if (!n.hasChildNodes()) {
- if (!n._mce_keep) {
- x = 1; // Is broken
- break;
- }
+ // Remove the open elements from the stack
+ stack.length = pos;
+ }
+ };
- n.removeAttribute('_mce_keep');
- }
- }
- }
+ // Precompile RegExps and map objects
+ tokenRegExp = new RegExp('<(?:' +
+ '(?:!--([\\w\\W]*?)-->)|' + // Comment
+ '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
+ '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
+ '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
+ '(?:\\/([^>]+)>)|' + // End element
+ '(?:([A-Za-z0-9\-\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
+ ')', 'g');
+
+ attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
+ specialElements = {
+ 'script' : /<\/script[^>]*>/gi,
+ 'style' : /<\/style[^>]*>/gi,
+ 'noscript' : /<\/noscript[^>]*>/gi
+ };
- // Time to fix the madness IE left us
- if (x) {
- // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
- // after we use innerHTML we can fix the DOM tree
- h = h.replace(/]+)>|
/ig, '
');
- h = h.replace(/<\/p>/gi, '
');
+ // Setup lookup tables for empty elements and boolean attributes
+ shortEndedElements = schema.getShortEndedElements();
+ selfClosing = schema.getSelfClosingElements();
+ fillAttrsMap = schema.getBoolAttrs();
+ validate = settings.validate;
+ removeInternalElements = settings.remove_internals;
+ fixSelfClosing = settings.fix_self_closing;
+ isIE = tinymce.isIE;
+ invalidPrefixRegExp = /^:/;
+
+ while (matches = tokenRegExp.exec(html)) {
+ // Text
+ if (index < matches.index)
+ self.text(decode(html.substr(index, matches.index - index)));
+
+ if (value = matches[6]) { // End element
+ value = value.toLowerCase();
+
+ // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
+ if (isIE && invalidPrefixRegExp.test(value))
+ value = value.substr(1);
+
+ processEndTag(value);
+ } else if (value = matches[7]) { // Start element
+ value = value.toLowerCase();
+
+ // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
+ if (isIE && invalidPrefixRegExp.test(value))
+ value = value.substr(1);
+
+ isShortEnded = value in shortEndedElements;
+
+ // Is self closing tag for example an after an open
+ if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
+ processEndTag(value);
+
+ // Validate element
+ if (!validate || (elementRule = schema.getElementRule(value))) {
+ isValidElement = true;
+
+ // Grab attributes map and patters when validation is enabled
+ if (validate) {
+ validAttributesMap = elementRule.attributes;
+ validAttributePatterns = elementRule.attributePatterns;
+ }
- // Set the new HTML with DIVs
- set();
+ // Parse attributes
+ if (attribsValue = matches[8]) {
+ isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
- // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
- // This is needed since IE has a annoying bug see above for details
- // This is a slow process but it has to be done. :(
- if (t.settings.fix_ie_paragraphs) {
- nl = e.getElementsByTagName("DIV");
- for (i = nl.length - 1; i >= 0; i--) {
- n = nl[i];
+ // If the element has internal attributes then remove it if we are told to do so
+ if (isInternalElement && removeInternalElements)
+ isValidElement = false;
- // Is it a temp div
- if (n._mce_tmp) {
- // Create new paragraph
- p = t.doc.createElement('p');
+ attrList = [];
+ attrList.map = {};
- // Copy all attributes
- n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
- var v;
+ attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
+ var attrRule, i;
- if (b !== '_mce_tmp') {
- v = n.getAttribute(b);
+ name = name.toLowerCase();
+ value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
- if (!v && b === 'class')
- v = n.className;
+ // Validate name and value
+ if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
+ attrRule = validAttributesMap[name];
- p.setAttribute(b, v);
+ // Find rule by pattern matching
+ if (!attrRule && validAttributePatterns) {
+ i = validAttributePatterns.length;
+ while (i--) {
+ attrRule = validAttributePatterns[i];
+ if (attrRule.pattern.test(name))
+ break;
}
- });
- // Append all children to new paragraph
- for (x = 0; x]+)\/>|/gi, ''); // Force open
+ // Handle required attributes
+ if (attributesRequired) {
+ i = attributesRequired.length;
+ while (i--) {
+ if (attributesRequired[i] in attrList.map)
+ break;
+ }
- // Store away src and href in _mce_src and mce_href since browsers mess them up
- if (s.keep_values) {
- // Wrap scripts and styles in comments for serialization purposes
- if (/';
+ t.plugins = {};
- bi = s.body_id || 'tinymce';
- if (bi.indexOf('=') != -1) {
- bi = t.getParam('body_id', '', 'hash');
- bi = bi[t.id] || bi;
- }
+ // Add events to the editor
+ each([
+ 'onPreInit',
- bc = s.body_class || '';
- if (bc.indexOf('=') != -1) {
- bc = t.getParam('body_class', '', 'hash');
- bc = bc[t.id] || '';
- }
+ 'onBeforeRenderUI',
- t.iframeHTML += '