summaryrefslogtreecommitdiff
path: root/program/js/tiny_mce/tiny_mce_src.js
diff options
context:
space:
mode:
authoralecpl <alec@alec.pl>2010-03-19 14:53:30 +0000
committeralecpl <alec@alec.pl>2010-03-19 14:53:30 +0000
commit69d05c371029b7915cf2a87d6e552d9853244644 (patch)
tree689e3a2cf571e8fc0912bd87284b7acc8e47b91d /program/js/tiny_mce/tiny_mce_src.js
parent258a043a31ff002d83097e45b4504e59e6559d1d (diff)
- TinyMCE 3.3.1
Diffstat (limited to 'program/js/tiny_mce/tiny_mce_src.js')
-rw-r--r--program/js/tiny_mce/tiny_mce_src.js7259
1 files changed, 3918 insertions, 3341 deletions
diff --git a/program/js/tiny_mce/tiny_mce_src.js b/program/js/tiny_mce/tiny_mce_src.js
index 6f86af440..5cbcced76 100644
--- a/program/js/tiny_mce/tiny_mce_src.js
+++ b/program/js/tiny_mce/tiny_mce_src.js
@@ -1,436 +1,443 @@
-var tinymce = {
- majorVersion : '3',
- minorVersion : '2.7',
- releaseDate : '2009-09-22',
+(function(win) {
+ var whiteSpaceRe = /^\s*|\s*$/g,
+ undefined;
- _init : function() {
- var t = this, d = document, w = window, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
+ var tinymce = {
+ majorVersion : '3',
- t.isOpera = w.opera && opera.buildNumber;
+ minorVersion : '3.1',
- t.isWebKit = /WebKit/.test(ua);
+ releaseDate : '2010-03-18',
- t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
+ _init : function() {
+ var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
- t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
+ t.isOpera = win.opera && opera.buildNumber;
- t.isGecko = !t.isWebKit && /Gecko/.test(ua);
+ t.isWebKit = /WebKit/.test(ua);
- t.isMac = ua.indexOf('Mac') != -1;
+ t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
- t.isAir = /adobeair/i.test(ua);
+ t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
- // TinyMCE .NET webcontrol might be setting the values for TinyMCE
- if (w.tinyMCEPreInit) {
- t.suffix = tinyMCEPreInit.suffix;
- t.baseURL = tinyMCEPreInit.base;
- t.query = tinyMCEPreInit.query;
- return;
- }
+ t.isGecko = !t.isWebKit && /Gecko/.test(ua);
- // Get suffix and base
- t.suffix = '';
+ t.isMac = ua.indexOf('Mac') != -1;
- // If base element found, add that infront of baseURL
- nl = d.getElementsByTagName('base');
- for (i=0; i<nl.length; i++) {
- if (v = nl[i].href) {
- // Host only value like http://site.com or http://site.com:8008
- if (/^https?:\/\/[^\/]+$/.test(v))
- v += '/';
+ t.isAir = /adobeair/i.test(ua);
- base = v ? v.match(/.*\//)[0] : ''; // Get only directory
+ // TinyMCE .NET webcontrol might be setting the values for TinyMCE
+ if (win.tinyMCEPreInit) {
+ t.suffix = tinyMCEPreInit.suffix;
+ t.baseURL = tinyMCEPreInit.base;
+ t.query = tinyMCEPreInit.query;
+ return;
}
- }
- function getBase(n) {
- if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
- if (/_(src|dev)\.js/g.test(n.src))
- t.suffix = '_src';
+ // Get suffix and base
+ t.suffix = '';
- if ((p = n.src.indexOf('?')) != -1)
- t.query = n.src.substring(p + 1);
+ // If base element found, add that infront of baseURL
+ nl = d.getElementsByTagName('base');
+ for (i=0; i<nl.length; i++) {
+ if (v = nl[i].href) {
+ // Host only value like http://site.com or http://site.com:8008
+ if (/^https?:\/\/[^\/]+$/.test(v))
+ v += '/';
- t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
+ base = v ? v.match(/.*\//)[0] : ''; // Get only directory
+ }
+ }
- // If path to script is relative and a base href was found add that one infront
- // the src property will always be an absolute one on non IE browsers and IE 8
- // so this logic will basically only be executed on older IE versions
- if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
- t.baseURL = base + t.baseURL;
+ function getBase(n) {
+ if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
+ if (/_(src|dev)\.js/g.test(n.src))
+ t.suffix = '_src';
- return t.baseURL;
- }
+ if ((p = n.src.indexOf('?')) != -1)
+ t.query = n.src.substring(p + 1);
- return null;
- };
+ t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
- // Check document
- nl = d.getElementsByTagName('script');
- for (i=0; i<nl.length; i++) {
- if (getBase(nl[i]))
- return;
- }
+ // If path to script is relative and a base href was found add that one infront
+ // the src property will always be an absolute one on non IE browsers and IE 8
+ // so this logic will basically only be executed on older IE versions
+ if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
+ t.baseURL = base + t.baseURL;
+
+ return t.baseURL;
+ }
+
+ return null;
+ };
- // Check head
- n = d.getElementsByTagName('head')[0];
- if (n) {
- nl = n.getElementsByTagName('script');
+ // Check document
+ nl = d.getElementsByTagName('script');
for (i=0; i<nl.length; i++) {
if (getBase(nl[i]))
return;
}
- }
- return;
- },
+ // Check head
+ n = d.getElementsByTagName('head')[0];
+ if (n) {
+ nl = n.getElementsByTagName('script');
+ for (i=0; i<nl.length; i++) {
+ if (getBase(nl[i]))
+ return;
+ }
+ }
- is : function(o, t) {
- var n = typeof(o);
+ return;
+ },
- if (!t)
- return n != 'undefined';
+ is : function(o, t) {
+ if (!t)
+ return o !== undefined;
- if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
- return true;
+ if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
+ return true;
- return n == t;
- },
+ return typeof(o) == t;
+ },
- each : function(o, cb, s) {
- var n, l;
+ each : function(o, cb, s) {
+ var n, l;
- if (!o)
- return 0;
+ if (!o)
+ return 0;
- s = s || o;
+ s = s || o;
- if (typeof(o.length) != 'undefined') {
- // Indexed arrays, needed for Safari
- for (n=0, l = o.length; n<l; n++) {
- if (cb.call(s, o[n], n, o) === false)
- return 0;
- }
- } else {
- // Hashtables
- for (n in o) {
- if (o.hasOwnProperty(n)) {
+ if (o.length !== undefined) {
+ // Indexed arrays, needed for Safari
+ for (n=0, l = o.length; n < l; n++) {
if (cb.call(s, o[n], n, o) === false)
return 0;
}
+ } else {
+ // Hashtables
+ for (n in o) {
+ if (o.hasOwnProperty(n)) {
+ if (cb.call(s, o[n], n, o) === false)
+ return 0;
+ }
+ }
}
- }
- return 1;
- },
+ return 1;
+ },
- map : function(a, f) {
- var o = [];
+ map : function(a, f) {
+ var o = [];
- tinymce.each(a, function(v) {
- o.push(f(v));
- });
+ tinymce.each(a, function(v) {
+ o.push(f(v));
+ });
- return o;
- },
+ return o;
+ },
- grep : function(a, f) {
- var o = [];
+ grep : function(a, f) {
+ var o = [];
- tinymce.each(a, function(v) {
- if (!f || f(v))
- o.push(v);
- });
+ tinymce.each(a, function(v) {
+ if (!f || f(v))
+ o.push(v);
+ });
- return o;
- },
+ return o;
+ },
- inArray : function(a, v) {
- var i, l;
+ inArray : function(a, v) {
+ var i, l;
- if (a) {
- for (i = 0, l = a.length; i < l; i++) {
- if (a[i] === v)
- return i;
+ if (a) {
+ for (i = 0, l = a.length; i < l; i++) {
+ if (a[i] === v)
+ return i;
+ }
}
- }
-
- return -1;
- },
- extend : function(o, e) {
- var i, a = arguments;
+ return -1;
+ },
- for (i=1; i<a.length; i++) {
- e = a[i];
+ extend : function(o, e) {
+ var i, l, a = arguments;
- tinymce.each(e, function(v, n) {
- if (typeof(v) !== 'undefined')
- o[n] = v;
- });
- }
+ for (i = 1, l = a.length; i < l; i++) {
+ e = a[i];
- return o;
- },
+ tinymce.each(e, function(v, n) {
+ if (v !== undefined)
+ o[n] = v;
+ });
+ }
+ return o;
+ },
- trim : function(s) {
- return (s ? '' + s : '').replace(/^\s*|\s*$/g, '');
- },
- create : function(s, p) {
- var t = this, sp, ns, cn, scn, c, de = 0;
+ trim : function(s) {
+ return (s ? '' + s : '').replace(whiteSpaceRe, '');
+ },
- // Parse : <prefix> <class>:<super class>
- s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
- cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
+ create : function(s, p) {
+ var t = this, sp, ns, cn, scn, c, de = 0;
- // Create namespace for new class
- ns = t.createNS(s[3].replace(/\.\w+$/, ''));
+ // Parse : <prefix> <class>:<super class>
+ s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
+ cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
- // Class already exists
- if (ns[cn])
- return;
+ // Create namespace for new class
+ ns = t.createNS(s[3].replace(/\.\w+$/, ''));
- // Make pure static class
- if (s[2] == 'static') {
- ns[cn] = p;
+ // Class already exists
+ if (ns[cn])
+ return;
- if (this.onCreate)
- this.onCreate(s[2], s[3], ns[cn]);
+ // Make pure static class
+ if (s[2] == 'static') {
+ ns[cn] = p;
- return;
- }
+ if (this.onCreate)
+ this.onCreate(s[2], s[3], ns[cn]);
- // Create default constructor
- if (!p[cn]) {
- p[cn] = function() {};
- de = 1;
- }
+ return;
+ }
- // Add constructor and methods
- ns[cn] = p[cn];
- t.extend(ns[cn].prototype, p);
-
- // Extend
- if (s[5]) {
- sp = t.resolve(s[5]).prototype;
- scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
-
- // Extend constructor
- c = ns[cn];
- if (de) {
- // Add passthrough constructor
- ns[cn] = function() {
- return sp[scn].apply(this, arguments);
- };
- } else {
- // Add inherit constructor
- ns[cn] = function() {
- this.parent = sp[scn];
- return c.apply(this, arguments);
- };
+ // Create default constructor
+ if (!p[cn]) {
+ p[cn] = function() {};
+ de = 1;
}
- ns[cn].prototype[cn] = ns[cn];
- // Add super methods
- t.each(sp, function(f, n) {
- ns[cn].prototype[n] = sp[n];
- });
+ // Add constructor and methods
+ ns[cn] = p[cn];
+ t.extend(ns[cn].prototype, p);
+
+ // Extend
+ if (s[5]) {
+ sp = t.resolve(s[5]).prototype;
+ scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
- // Add overridden methods
- t.each(p, function(f, n) {
- // Extend methods if needed
- if (sp[n]) {
- ns[cn].prototype[n] = function() {
- this.parent = sp[n];
- return f.apply(this, arguments);
+ // Extend constructor
+ c = ns[cn];
+ if (de) {
+ // Add passthrough constructor
+ ns[cn] = function() {
+ return sp[scn].apply(this, arguments);
};
} else {
- if (n != cn)
- ns[cn].prototype[n] = f;
+ // Add inherit constructor
+ ns[cn] = function() {
+ this.parent = sp[scn];
+ return c.apply(this, arguments);
+ };
}
- });
- }
+ ns[cn].prototype[cn] = ns[cn];
- // Add static methods
- t.each(p['static'], function(f, n) {
- ns[cn][n] = f;
- });
+ // Add super methods
+ t.each(sp, function(f, n) {
+ ns[cn].prototype[n] = sp[n];
+ });
- if (this.onCreate)
- this.onCreate(s[2], s[3], ns[cn].prototype);
- },
+ // Add overridden methods
+ t.each(p, function(f, n) {
+ // Extend methods if needed
+ if (sp[n]) {
+ ns[cn].prototype[n] = function() {
+ this.parent = sp[n];
+ return f.apply(this, arguments);
+ };
+ } else {
+ if (n != cn)
+ ns[cn].prototype[n] = f;
+ }
+ });
+ }
- walk : function(o, f, n, s) {
- s = s || this;
+ // Add static methods
+ t.each(p['static'], function(f, n) {
+ ns[cn][n] = f;
+ });
- if (o) {
- if (n)
- o = o[n];
+ if (this.onCreate)
+ this.onCreate(s[2], s[3], ns[cn].prototype);
+ },
- tinymce.each(o, function(o, i) {
- if (f.call(s, o, i, n) === false)
- return false;
+ walk : function(o, f, n, s) {
+ s = s || this;
- tinymce.walk(o, f, n, s);
- });
- }
- },
+ if (o) {
+ if (n)
+ o = o[n];
+
+ tinymce.each(o, function(o, i) {
+ if (f.call(s, o, i, n) === false)
+ return false;
- createNS : function(n, o) {
- var i, v;
+ tinymce.walk(o, f, n, s);
+ });
+ }
+ },
- o = o || window;
+ createNS : function(n, o) {
+ var i, v;
- n = n.split('.');
- for (i=0; i<n.length; i++) {
- v = n[i];
+ o = o || win;
- if (!o[v])
- o[v] = {};
+ n = n.split('.');
+ for (i=0; i<n.length; i++) {
+ v = n[i];
- o = o[v];
- }
+ if (!o[v])
+ o[v] = {};
- return o;
- },
+ o = o[v];
+ }
- resolve : function(n, o) {
- var i, l;
+ return o;
+ },
- o = o || window;
+ resolve : function(n, o) {
+ var i, l;
- n = n.split('.');
- for (i = 0, l = n.length; i < l; i++) {
- o = o[n[i]];
+ o = o || win;
- if (!o)
- break;
- }
+ n = n.split('.');
+ for (i = 0, l = n.length; i < l; i++) {
+ o = o[n[i]];
- return o;
- },
+ if (!o)
+ break;
+ }
- addUnload : function(f, s) {
- var t = this, w = window;
+ return o;
+ },
- f = {func : f, scope : s || this};
+ addUnload : function(f, s) {
+ var t = this;
- if (!t.unloads) {
- function unload() {
- var li = t.unloads, o, n;
+ f = {func : f, scope : s || this};
- if (li) {
- // Call unload handlers
- for (n in li) {
- o = li[n];
+ if (!t.unloads) {
+ function unload() {
+ var li = t.unloads, o, n;
- if (o && o.func)
- o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
- }
+ if (li) {
+ // Call unload handlers
+ for (n in li) {
+ o = li[n];
- // Detach unload function
- if (w.detachEvent) {
- w.detachEvent('onbeforeunload', fakeUnload);
- w.detachEvent('onunload', unload);
- } else if (w.removeEventListener)
- w.removeEventListener('unload', unload, false);
+ if (o && o.func)
+ o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
+ }
- // Destroy references
- t.unloads = o = li = w = unload = 0;
+ // Detach unload function
+ if (win.detachEvent) {
+ win.detachEvent('onbeforeunload', fakeUnload);
+ win.detachEvent('onunload', unload);
+ } else if (win.removeEventListener)
+ win.removeEventListener('unload', unload, false);
- // Run garbarge collector on IE
- if (window.CollectGarbage)
- window.CollectGarbage();
- }
- };
+ // Destroy references
+ t.unloads = o = li = w = unload = 0;
- function fakeUnload() {
- var d = document;
+ // Run garbarge collector on IE
+ if (win.CollectGarbage)
+ CollectGarbage();
+ }
+ };
- // Is there things still loading, then do some magic
- if (d.readyState == 'interactive') {
- function stop() {
- // Prevent memory leak
- d.detachEvent('onstop', stop);
+ function fakeUnload() {
+ var d = document;
- // Call unload handler
- if (unload)
- unload();
+ // Is there things still loading, then do some magic
+ if (d.readyState == 'interactive') {
+ function stop() {
+ // Prevent memory leak
+ d.detachEvent('onstop', stop);
- d = 0;
- };
+ // Call unload handler
+ if (unload)
+ unload();
- // Fire unload when the currently loading page is stopped
- if (d)
- d.attachEvent('onstop', stop);
+ d = 0;
+ };
- // Remove onstop listener after a while to prevent the unload function
- // to execute if the user presses cancel in an onbeforeunload
- // confirm dialog and then presses the browser stop button
- window.setTimeout(function() {
+ // Fire unload when the currently loading page is stopped
if (d)
- d.detachEvent('onstop', stop);
- }, 0);
- }
- };
+ d.attachEvent('onstop', stop);
+
+ // Remove onstop listener after a while to prevent the unload function
+ // to execute if the user presses cancel in an onbeforeunload
+ // confirm dialog and then presses the browser stop button
+ win.setTimeout(function() {
+ if (d)
+ d.detachEvent('onstop', stop);
+ }, 0);
+ }
+ };
- // Attach unload handler
- if (w.attachEvent) {
- w.attachEvent('onunload', unload);
- w.attachEvent('onbeforeunload', fakeUnload);
- } else if (w.addEventListener)
- w.addEventListener('unload', unload, false);
+ // Attach unload handler
+ if (win.attachEvent) {
+ win.attachEvent('onunload', unload);
+ win.attachEvent('onbeforeunload', fakeUnload);
+ } else if (win.addEventListener)
+ win.addEventListener('unload', unload, false);
- // Setup initial unload handler array
- t.unloads = [f];
- } else
- t.unloads.push(f);
+ // Setup initial unload handler array
+ t.unloads = [f];
+ } else
+ t.unloads.push(f);
- return f;
- },
+ return f;
+ },
- removeUnload : function(f) {
- var u = this.unloads, r = null;
+ removeUnload : function(f) {
+ var u = this.unloads, r = null;
- tinymce.each(u, function(o, i) {
- if (o && o.func == f) {
- u.splice(i, 1);
- r = f;
- return false;
- }
- });
+ tinymce.each(u, function(o, i) {
+ if (o && o.func == f) {
+ u.splice(i, 1);
+ r = f;
+ return false;
+ }
+ });
- return r;
- },
+ return r;
+ },
- explode : function(s, d) {
- return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
- },
+ explode : function(s, d) {
+ return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
+ },
- _addVer : function(u) {
- var v;
+ _addVer : function(u) {
+ var v;
- if (!this.query)
- return u;
+ if (!this.query)
+ return u;
- v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
+ v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
- if (u.indexOf('#') == -1)
- return u + v;
+ if (u.indexOf('#') == -1)
+ return u + v;
- return u.replace('#', v + '#');
- }
+ return u.replace('#', v + '#');
+ }
- };
+ };
+
+ // Initialize the API
+ tinymce._init();
+
+ // Expose tinymce namespace to the global namespace (window)
+ win.tinymce = win.tinyMCE = tinymce;
+})(window);
-// Required for GZip AJAX loading
-window.tinymce = tinymce;
-// Initialize the API
-tinymce._init();
tinymce.create('tinymce.util.Dispatcher', {
scope : null,
listeners : null,
@@ -483,6 +490,7 @@ tinymce.create('tinymce.util.Dispatcher', {
}
});
+
(function() {
var each = tinymce.each;
@@ -723,6 +731,7 @@ tinymce.create('tinymce.util.Dispatcher', {
}
});
})();
+
(function() {
var each = tinymce.each;
@@ -793,6 +802,7 @@ tinymce.create('tinymce.util.Dispatcher', {
}
});
})();
+
tinymce.create('static tinymce.util.JSON', {
serialize : function(o) {
var i, v, s = tinymce.util.JSON.serialize, t;
@@ -845,6 +855,7 @@ tinymce.create('static tinymce.util.JSON', {
}
});
+
tinymce.create('static tinymce.util.XHR', {
send : function(o) {
var x, t, w = window, c = 0;
@@ -903,6 +914,7 @@ tinymce.create('static tinymce.util.XHR', {
}
}
});
+
(function() {
var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
@@ -955,10 +967,31 @@ tinymce.create('static tinymce.util.XHR', {
}
}
});
-}());(function(tinymce) {
+}());
+(function(tinymce) {
// Shorten names
- var each = tinymce.each, is = tinymce.is;
- var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE;
+ 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 = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'},
+ encodeCharsRe = /[<>&\"]/g,
+ simpleSelectorRe = /^([a-z0-9],?)+$/i,
+ tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
+ attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+ function makeMap(str) {
+ var map = {}, i;
+
+ str = str.split(',');
+ for (i = str.length; i >= 0; i--)
+ map[str[i]] = 1;
+
+ return map;
+ };
tinymce.create('tinymce.dom.DOMUtils', {
doc : null,
@@ -981,7 +1014,7 @@ tinymce.create('static tinymce.util.XHR', {
},
DOMUtils : function(d, s) {
- var t = this;
+ var t = this, globalStyle;
t.doc = d;
t.win = window;
@@ -1006,6 +1039,16 @@ tinymce.create('static tinymce.util.XHR', {
}
}
+ // Build styles list
+ if (s.valid_styles) {
+ t._styles = {};
+
+ // Convert styles into a rule list
+ each(s.valid_styles, function(value, key) {
+ t._styles[key] = tinymce.explode(value);
+ });
+ }
+
tinymce.addUnload(t.destroy, t);
},
@@ -1140,8 +1183,30 @@ tinymce.create('static tinymce.util.XHR', {
return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
},
- is : function(n, patt) {
- return tinymce.dom.Sizzle.matches(patt, n.nodeType ? [n] : n).length > 0;
+ 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();
+
+ for (i = selector.length - 1; i >= 0; i--) {
+ if (selector[i] == n)
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
},
@@ -1185,38 +1250,26 @@ tinymce.create('static tinymce.util.XHR', {
return o + ' />';
},
- remove : function(n, k) {
- var t = this;
-
- return this.run(n, function(n) {
- var p, g, i;
+ remove : function(node, keep_children) {
+ return this.run(node, function(node) {
+ var parent, child;
- p = n.parentNode;
+ parent = node.parentNode;
- if (!p)
+ if (!parent)
return null;
- if (k) {
- for (i = n.childNodes.length - 1; i >= 0; i--)
- t.insertAfter(n.childNodes[i], n);
-
- //each(n.childNodes, function(c) {
- // p.insertBefore(c.cloneNode(true), n);
- //});
- }
-
- // Fix IE psuedo leak
- if (t.fixPsuedoLeaks) {
- p = n.cloneNode(true);
- k = 'IELeakGarbageBin';
- g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'});
- g.appendChild(n);
- g.innerHTML = '';
-
- return p;
+ if (keep_children) {
+ while (child = node.firstChild) {
+ // IE 8 will crash if you don't remove completely empty text nodes
+ if (child.nodeType !== 3 || child.nodeValue)
+ parent.insertBefore(child, node);
+ else
+ node.removeChild(child);
+ }
}
- return p.removeChild(n);
+ return parent.removeChild(node);
});
},
@@ -1261,7 +1314,7 @@ tinymce.create('static tinymce.util.XHR', {
// Force update of the style data
if (t.settings.update_styles)
- t.setAttrib(e, 'mce_style');
+ t.setAttrib(e, '_mce_style');
});
},
@@ -1344,9 +1397,9 @@ tinymce.create('static tinymce.util.XHR', {
// 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);
+ e.setAttribute('_mce_style', v, 2);
else
- e.removeAttribute('mce_style', 2);
+ e.removeAttribute('_mce_style', 2);
}
e.style.cssText = v;
@@ -1362,13 +1415,13 @@ tinymce.create('static tinymce.util.XHR', {
if (s.url_converter)
v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
- t.setAttrib(e, 'mce_' + n, v, 2);
+ t.setAttrib(e, '_mce_' + n, v, 2);
}
break;
case "shape":
- e.setAttribute('mce_style', v);
+ e.setAttribute('_mce_style', v);
break;
}
@@ -1402,7 +1455,7 @@ tinymce.create('static tinymce.util.XHR', {
// Try the mce variant for these
if (/^(src|href|style|coords|shape)$/.test(n)) {
- v = e.getAttribute("mce_" + n);
+ v = e.getAttribute("_mce_" + n);
if (v)
return v;
@@ -1432,10 +1485,10 @@ tinymce.create('static tinymce.util.XHR', {
v = v || e.style.cssText;
if (v) {
- v = t.serializeStyle(t.parseStyle(v));
+ v = t.serializeStyle(t.parseStyle(v), e.nodeName);
if (t.settings.keep_values && !t._isRes(v))
- e.setAttribute('mce_style', v);
+ e.setAttribute('_mce_style', v);
}
}
@@ -1645,15 +1698,23 @@ tinymce.create('static tinymce.util.XHR', {
return o;
},
- serializeStyle : function(o) {
- var s = '';
+ serializeStyle : function(o, name) {
+ var t = this, s = '';
- each(o, function(v, k) {
+ function add(v, k) {
if (k && v) {
- if (tinymce.isGecko && k.indexOf('-moz-') === 0)
+ // Remove browser specific styles like -moz- or -webkit-
+ if (k.indexOf('-') === 0)
return;
switch (k) {
+ case 'font-weight':
+ // Opera will output bold as 700
+ if (v == 700)
+ v = 'bold';
+
+ break;
+
case 'color':
case 'background-color':
v = v.toLowerCase();
@@ -1662,7 +1723,19 @@ tinymce.create('static tinymce.util.XHR', {
s += (s ? ' ' : '') + k + ': ' + v + ';';
}
- });
+ };
+
+ // Validate style output
+ if (name && t._styles) {
+ each(t._styles['*'], function(name) {
+ add(o[name], name);
+ });
+
+ each(t._styles[name.toLowerCase()], function(name) {
+ add(o[name], name);
+ });
+ } else
+ each(o, add);
return s;
},
@@ -1725,8 +1798,17 @@ tinymce.create('static tinymce.util.XHR', {
re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
v = e.className.replace(re, ' ');
+ v = tinymce.trim(v != ' ' ? v : '');
- return e.className = tinymce.trim(v != ' ' ? v : '');
+ e.className = v;
+
+ // Empty class attr
+ if (!v) {
+ e.removeAttribute('class');
+ e.removeAttribute('className');
+ }
+
+ return v;
}
return e.className;
@@ -1770,6 +1852,10 @@ tinymce.create('static tinymce.util.XHR', {
if (isIE) {
function set() {
+ // Remove all child nodes
+ while (e.firstChild)
+ e.firstChild.removeNode();
+
try {
// IE will remove comments from the beginning
// unless you padd the contents with something
@@ -1779,10 +1865,6 @@ tinymce.create('static tinymce.util.XHR', {
// 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
- // Remove all child nodes
- while (e.firstChild)
- e.firstChild.removeNode();
-
// Create new div with HTML contents and a BR infront to keep comments
x = t.create('div');
x.innerHTML = '<br />' + h;
@@ -1800,7 +1882,7 @@ tinymce.create('static tinymce.util.XHR', {
// DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> 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>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 mce_keep="true">&nbsp;</p>');
+ h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true">&nbsp;</p>');
set();
@@ -1811,12 +1893,12 @@ tinymce.create('static tinymce.util.XHR', {
n = nl[i];
if (!n.hasChildNodes()) {
- if (!n.mce_keep) {
+ if (!n._mce_keep) {
x = 1; // Is broken
break;
}
- n.removeAttribute('mce_keep');
+ n.removeAttribute('_mce_keep');
}
}
}
@@ -1825,13 +1907,13 @@ tinymce.create('static tinymce.util.XHR', {
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(/<p ([^>]+)>|<p>/ig, '<div $1 mce_tmp="1">');
+ h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
h = h.replace(/<\/p>/g, '</div>');
// Set the new HTML with DIVs
set();
- // Replace all DIV elements with he mce_tmp attibute back to paragraphs
+ // 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) {
@@ -1840,7 +1922,7 @@ tinymce.create('static tinymce.util.XHR', {
n = nl[i];
// Is it a temp div
- if (n.mce_tmp) {
+ if (n._mce_tmp) {
// Create new paragraph
p = t.doc.createElement('p');
@@ -1848,7 +1930,7 @@ tinymce.create('static tinymce.util.XHR', {
n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
var v;
- if (b !== 'mce_tmp') {
+ if (b !== '_mce_tmp') {
v = n.getAttribute(b);
if (!v && b === 'class')
@@ -1881,11 +1963,7 @@ tinymce.create('static tinymce.util.XHR', {
if (!s.process_html)
return h;
- // Convert strong and em to b and i in FF since it can't handle them
- if (tinymce.isGecko) {
- h = h.replace(/<(\/?)strong>|<strong( [^>]+)>/gi, '<$1b$2>');
- h = h.replace(/<(\/?)em>|<em( [^>]+)>/gi, '<$1i$2>');
- } else if (isIE) {
+ if (isIE) {
h = h.replace(/&apos;/g, '&#39;'); // IE can't handle apos
h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
}
@@ -1893,7 +1971,7 @@ tinymce.create('static tinymce.util.XHR', {
// Fix some issues
h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
- // Store away src and href in mce_src and mce_href since browsers mess them up
+ // 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 (/<script|noscript|style/i.test(h)) {
@@ -1918,7 +1996,7 @@ tinymce.create('static tinymce.util.XHR', {
if (s.url_converter)
url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
- return 'mce_src="' + url + '"';
+ return '_mce_src="' + url + '"';
});
// Wrap text contents
@@ -1938,7 +2016,7 @@ tinymce.create('static tinymce.util.XHR', {
text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
}
- return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' mce_bogus="1">' + text + '</style>';
+ return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
});
// Wrap noscript elements
@@ -1949,53 +2027,46 @@ tinymce.create('static tinymce.util.XHR', {
h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
- // Remove false bool attributes and force attributes into xhtml style attr="attr"
- h = h.replace(/<([\w:]+) [^>]*(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)[^>]*>/gi, function(val) {
- function handle(val, name, value) {
- // Remove false/0 attribs
- if (value === 'false' || value === '0')
- return '';
-
- return ' ' + name + '="' + name + '"';
- };
+ // This function processes the attributes in the HTML string to force boolean
+ // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
+ function processTags(html) {
+ return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
+ return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
+ var mceValue;
- val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\"]([^\"]+)[\"]/gi, handle); // W3C
- val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\']([^\']+)[\']/gi, handle); // W3C
- val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=([^\s\"\'>]+)/gi, handle); // IE
- val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)([\s>])/gi, ' $1="$1"$2'); // Force attr="attr"
+ name = name.toLowerCase();
+ value = value || val2 || val3 || "";
- return val;
- });
+ // Treat boolean attributes
+ if (boolAttrs[name]) {
+ // false or 0 is treated as a missing attribute
+ if (value === 'false' || value === '0')
+ return;
- // Process all tags with src, href or style
- h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) {
- function handle(m, b, c) {
- var u = c;
+ return name + '="' + name + '"';
+ }
- // Tag already got a mce_ version
- if (a.indexOf('mce_' + b) != -1)
- return m;
+ // Is attribute one that needs special treatment
+ if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
+ mceValue = t.decode(value);
- if (b == 'style') {
- // No mce_style for elements with these since they might get resized by the user
- if (t._isRes(c))
- return m;
+ // Convert URLs to relative/absolute ones
+ if (s.url_converter && (name == "src" || name == "href"))
+ mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
- // Parse and serialize the style to convert for example uppercase styles like "BORDER: 1px"
- u = t.encode(t.serializeStyle(t.parseStyle(u)));
- } else if (b != 'coords' && b != 'shape') {
- if (s.url_converter)
- u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n));
- }
+ // Process styles lowercases them and compresses them
+ if (name == 'style')
+ mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
- return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"';
- };
+ return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
+ }
- a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C
- a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C
+ return match;
+ }) + end + '>';
+ });
+ };
- return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE
- });
+ h = processTags(h);
// Restore script blocks
h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
@@ -2028,7 +2099,7 @@ tinymce.create('static tinymce.util.XHR', {
function setHTML(e, h, d) {
var n, tp;
-
+
tp = d.createElement("body");
tp.innerHTML = h;
@@ -2069,7 +2140,7 @@ tinymce.create('static tinymce.util.XHR', {
var e, n, v;
// Look for entities to decode
- if (/&[^;]+;/.test(s)) {
+ if (/&[\w#]+;/.test(s)) {
// Decode the entities using a div element not super efficient but less code
e = this.doc.createElement("div");
e.innerHTML = s;
@@ -2079,7 +2150,7 @@ tinymce.create('static tinymce.util.XHR', {
if (n) {
do {
v += n.nodeValue;
- } while (n.nextSibling);
+ } while (n = n.nextSibling);
}
return v || s;
@@ -2088,43 +2159,27 @@ tinymce.create('static tinymce.util.XHR', {
return s;
},
- encode : function(s) {
- return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) {
- switch (c) {
- case '&':
- return '&amp;';
-
- case '"':
- return '&quot;';
-
- case '<':
- return '&lt;';
-
- case '>':
- return '&gt;';
- }
-
- return c;
- }) : s;
+ encode : function(str) {
+ return ('' + str).replace(encodeCharsRe, function(chr) {
+ return encodedChars[chr];
+ });
},
- insertAfter : function(n, r) {
- var t = this;
-
- r = t.get(r);
+ insertAfter : function(node, reference_node) {
+ reference_node = this.get(reference_node);
- return this.run(n, function(n) {
- var p, ns;
+ return this.run(node, function(node) {
+ var parent, nextSibling;
- p = r.parentNode;
- ns = r.nextSibling;
+ parent = reference_node.parentNode;
+ nextSibling = reference_node.nextSibling;
- if (ns)
- p.insertBefore(n, ns);
+ if (nextSibling)
+ parent.insertBefore(node, nextSibling);
else
- p.appendChild(n);
+ parent.appendChild(node);
- return n;
+ return node;
});
},
@@ -2134,7 +2189,7 @@ tinymce.create('static tinymce.util.XHR', {
n = n.nodeName || n;
- return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TH|TBODY|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(n);
+ return blockRe.test(n);
},
replace : function(n, o, k) {
@@ -2145,23 +2200,34 @@ tinymce.create('static tinymce.util.XHR', {
return t.run(o, function(o) {
if (k) {
- each(o.childNodes, function(c) {
- n.appendChild(c.cloneNode(true));
+ each(tinymce.grep(o.childNodes), function(c) {
+ n.appendChild(c);
});
}
- // Fix IE psuedo leak for elements since replacing elements if fairly common
- // Will break parentNode for some unknown reason
- if (t.fixPsuedoLeaks && o.nodeType === 1) {
- o.parentNode.insertBefore(n, o);
- t.remove(o);
- return n;
- }
-
return o.parentNode.replaceChild(n, o);
});
},
+ rename : function(elm, name) {
+ var t = this, newElm;
+
+ if (elm.nodeName != name.toUpperCase()) {
+ // Rename block element
+ newElm = t.create(name);
+
+ // Copy attribs to new block
+ each(t.getAttribs(elm), function(attr_node) {
+ t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
+ });
+
+ // Replace block
+ t.replace(newElm, elm, 1);
+ }
+
+ return newElm || elm;
+ },
+
findCommonAncestor : function(a, b) {
var ps = a, pe;
@@ -2310,7 +2376,7 @@ tinymce.create('static tinymce.util.XHR', {
o.push({specified : 1, nodeName : 'selected'});
// It's crazy that this is faster in IE but it's because it returns all attributes all the time
- n.cloneNode(false).outerHTML.replace(/<\/?[\w:]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=\w+|>/gi, '').replace(/[\w:]+/gi, function(a) {
+ n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
o.push({specified : 1, nodeName : a});
});
@@ -2339,77 +2405,90 @@ tinymce.create('static tinymce.util.XHR', {
return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
},
+ nodeIndex : function(node, normalized) {
+ var idx = 0, lastNodeType, nodeType;
+
+ if (node) {
+ for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
+ nodeType = node.nodeType;
+
+ // Handle normalization of text nodes
+ if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length))
+ idx++;
+
+ lastNodeType = nodeType;
+ }
+ }
+
+ return idx;
+ },
+
split : function(pe, e, re) {
var t = this, r = t.createRng(), bef, aft, pa;
- // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sence
- // but we don't want that in our code since it serves no purpose
+ // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
+ // but we don't want that in our code since it serves no purpose for the end user
// For example if this is chopped:
// <p>text 1<span><b>CHOP</b></span>text 2</p>
// would produce:
// <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
// this function will then trim of empty edges and produce:
// <p>text 1</p><b>CHOP</b><p>text 2</p>
- function trimEdge(n, na) {
- n = n[na];
+ function trim(node) {
+ var i, children = node.childNodes;
- if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na]))
- t.remove(n[na]);
- };
+ if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
+ return;
- function isEmpty(n) {
- n = t.getOuterHTML(n);
- n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars
- n = n.replace(/<[^>]+>/g, ''); // Remove all tags
+ for (i = children.length - 1; i >= 0; i--)
+ trim(children[i]);
- return n.replace(/[ \t\r\n]+|&nbsp;|&#160;/g, '') == '';
- };
+ if (node.nodeType != 9) {
+ // Keep non whitespace text nodes
+ if (node.nodeType == 3 && node.nodeValue.length > 0)
+ return;
- // Added until Gecko can create real HTML documents using implementation.createHTMLDocument
- // this is to future proof it if Gecko decides to implement the error checking for range methods.
- function nodeIndex(n) {
- var i = 0;
+ if (node.nodeType == 1) {
+ // If the only child is a bookmark then move it up
+ children = node.childNodes;
+ if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
+ node.parentNode.insertBefore(children[0], node);
- while (n.previousSibling) {
- i++;
- n = n.previousSibling;
+ // Keep non empty elements or img, hr etc
+ if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
+ return;
+ }
+
+ t.remove(node);
}
- return i;
+ return node;
};
if (pe && e) {
// Get before chunk
- r.setStart(pe.parentNode, nodeIndex(pe));
- r.setEnd(e.parentNode, nodeIndex(e));
+ r.setStart(pe.parentNode, t.nodeIndex(pe));
+ r.setEnd(e.parentNode, t.nodeIndex(e));
bef = r.extractContents();
// Get after chunk
r = t.createRng();
- r.setStart(e.parentNode, nodeIndex(e) + 1);
- r.setEnd(pe.parentNode, nodeIndex(pe) + 1);
+ r.setStart(e.parentNode, t.nodeIndex(e) + 1);
+ r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
aft = r.extractContents();
- // Insert chunks and remove parent
+ // Insert before chunk
pa = pe.parentNode;
+ pa.insertBefore(trim(bef), pe);
- // Remove right side edge of the before contents
- trimEdge(bef, 'lastChild');
-
- if (!isEmpty(bef))
- pa.insertBefore(bef, pe);
-
+ // Insert middle chunk
if (re)
pa.replaceChild(re, e);
else
pa.insertBefore(e, pe);
- // Remove left site edge of the after contents
- trimEdge(aft, 'firstChild');
-
- if (!isEmpty(aft))
- pa.insertBefore(aft, pe);
-
+ // Insert after chunk
+ pa.insertBefore(trim(aft), pe);
t.remove(pe);
return re || e;
@@ -2494,231 +2573,235 @@ tinymce.create('static tinymce.util.XHR', {
tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
})(tinymce);
-(function(ns) {
- // Traverse constants
- var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend;
-
- function indexOf(child, parent) {
- var i, node;
-
- if (child.parentNode != parent)
- return -1;
-
- for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling)
- i++;
-
- return i;
- };
-
- function nodeIndex(n) {
- var i = 0;
-
- while (n.previousSibling) {
- i++;
- n = n.previousSibling;
- }
-
- return i;
- };
-
- function getSelectedNode(container, offset) {
- var child;
-
- if (container.nodeType == 3 /* TEXT_NODE */)
- return container;
-
- if (offset < 0)
- return container;
-
- child = container.firstChild;
- while (child != null && offset > 0) {
- --offset;
- child = child.nextSibling;
- }
-
- if (child != null)
- return child;
-
- return container;
- };
+(function(ns) {
// Range constructor
function Range(dom) {
- var d = dom.doc;
-
- extend(this, {
- dom : dom,
-
+ var t = this,
+ doc = dom.doc,
+ EXTRACT = 0,
+ CLONE = 1,
+ DELETE = 2,
+ TRUE = true,
+ FALSE = false,
+ START_OFFSET = 'startOffset',
+ START_CONTAINER = 'startContainer',
+ END_CONTAINER = 'endContainer',
+ END_OFFSET = 'endOffset',
+ extend = tinymce.extend,
+ nodeIndex = dom.nodeIndex;
+
+ extend(t, {
// Inital states
- startContainer : d,
+ startContainer : doc,
startOffset : 0,
- endContainer : d,
+ endContainer : doc,
endOffset : 0,
- collapsed : true,
- commonAncestorContainer : d,
+ collapsed : TRUE,
+ commonAncestorContainer : doc,
// Range constants
START_TO_START : 0,
START_TO_END : 1,
END_TO_END : 2,
- END_TO_START : 3
+ END_TO_START : 3,
+
+ // Public methods
+ setStart : setStart,
+ setEnd : setEnd,
+ setStartBefore : setStartBefore,
+ setStartAfter : setStartAfter,
+ setEndBefore : setEndBefore,
+ setEndAfter : setEndAfter,
+ collapse : collapse,
+ selectNode : selectNode,
+ selectNodeContents : selectNodeContents,
+ compareBoundaryPoints : compareBoundaryPoints,
+ deleteContents : deleteContents,
+ extractContents : extractContents,
+ cloneContents : cloneContents,
+ insertNode : insertNode,
+ surroundContents : surroundContents,
+ cloneRange : cloneRange
});
- };
- // Add range methods
- extend(Range.prototype, {
- setStart : function(n, o) {
- this._setEndPoint(true, n, o);
- },
-
- setEnd : function(n, o) {
- this._setEndPoint(false, n, o);
- },
+ function setStart(n, o) {
+ _setEndPoint(TRUE, n, o);
+ };
- setStartBefore : function(n) {
- this.setStart(n.parentNode, nodeIndex(n));
- },
+ function setEnd(n, o) {
+ _setEndPoint(FALSE, n, o);
+ };
- setStartAfter : function(n) {
- this.setStart(n.parentNode, nodeIndex(n) + 1);
- },
+ function setStartBefore(n) {
+ setStart(n.parentNode, nodeIndex(n));
+ };
- setEndBefore : function(n) {
- this.setEnd(n.parentNode, nodeIndex(n));
- },
+ function setStartAfter(n) {
+ setStart(n.parentNode, nodeIndex(n) + 1);
+ };
- setEndAfter : function(n) {
- this.setEnd(n.parentNode, nodeIndex(n) + 1);
- },
+ function setEndBefore(n) {
+ setEnd(n.parentNode, nodeIndex(n));
+ };
- collapse : function(ts) {
- var t = this;
+ function setEndAfter(n) {
+ setEnd(n.parentNode, nodeIndex(n) + 1);
+ };
+ function collapse(ts) {
if (ts) {
- t.endContainer = t.startContainer;
- t.endOffset = t.startOffset;
+ t[END_CONTAINER] = t[START_CONTAINER];
+ t[END_OFFSET] = t[START_OFFSET];
} else {
- t.startContainer = t.endContainer;
- t.startOffset = t.endOffset;
+ t[START_CONTAINER] = t[END_CONTAINER];
+ t[START_OFFSET] = t[END_OFFSET];
}
- t.collapsed = true;
- },
+ t.collapsed = TRUE;
+ };
- selectNode : function(n) {
- this.setStartBefore(n);
- this.setEndAfter(n);
- },
+ function selectNode(n) {
+ setStartBefore(n);
+ setEndAfter(n);
+ };
- selectNodeContents : function(n) {
- this.setStart(n, 0);
- this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
- },
+ function selectNodeContents(n) {
+ setStart(n, 0);
+ setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
+ };
- compareBoundaryPoints : function(h, r) {
- var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset;
+ function compareBoundaryPoints(h, r) {
+ var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
// Check START_TO_START
if (h === 0)
- return t._compareBoundaryPoints(sc, so, sc, so);
+ return _compareBoundaryPoints(sc, so, sc, so);
// Check START_TO_END
if (h === 1)
- return t._compareBoundaryPoints(sc, so, ec, eo);
+ return _compareBoundaryPoints(sc, so, ec, eo);
// Check END_TO_END
if (h === 2)
- return t._compareBoundaryPoints(ec, eo, ec, eo);
+ return _compareBoundaryPoints(ec, eo, ec, eo);
// Check END_TO_START
if (h === 3)
- return t._compareBoundaryPoints(ec, eo, sc, so);
- },
+ return _compareBoundaryPoints(ec, eo, sc, so);
+ };
- deleteContents : function() {
- this._traverse(DELETE);
- },
+ function deleteContents() {
+ _traverse(DELETE);
+ };
- extractContents : function() {
- return this._traverse(EXTRACT);
- },
+ function extractContents() {
+ return _traverse(EXTRACT);
+ };
- cloneContents : function() {
- return this._traverse(CLONE);
- },
+ function cloneContents() {
+ return _traverse(CLONE);
+ };
- insertNode : function(n) {
- var t = this, nn, o;
+ function insertNode(n) {
+ var startContainer = this[START_CONTAINER],
+ startOffset = this[START_OFFSET], nn, o;
// Node is TEXT_NODE or CDATA
- if (n.nodeType === 3 || n.nodeType === 4) {
- nn = t.startContainer.splitText(t.startOffset);
- t.startContainer.parentNode.insertBefore(n, nn);
+ if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
+ if (!startOffset) {
+ // At the start of text
+ startContainer.parentNode.insertBefore(n, startContainer);
+ } else if (startOffset >= startContainer.nodeValue.length) {
+ // At the end of text
+ dom.insertAfter(n, startContainer);
+ } else {
+ // Middle, need to split
+ nn = startContainer.splitText(startOffset);
+ startContainer.parentNode.insertBefore(n, nn);
+ }
} else {
// Insert element node
- if (t.startContainer.childNodes.length > 0)
- o = t.startContainer.childNodes[t.startOffset];
+ if (startContainer.childNodes.length > 0)
+ o = startContainer.childNodes[startOffset];
- t.startContainer.insertBefore(n, o);
+ if (o)
+ startContainer.insertBefore(n, o);
+ else
+ startContainer.appendChild(n);
}
- },
+ };
- surroundContents : function(n) {
- var t = this, f = t.extractContents();
+ function surroundContents(n) {
+ var f = t.extractContents();
t.insertNode(n);
n.appendChild(f);
t.selectNode(n);
- },
-
- cloneRange : function() {
- var t = this;
+ };
- return extend(new Range(t.dom), {
- startContainer : t.startContainer,
- startOffset : t.startOffset,
- endContainer : t.endContainer,
- endOffset : t.endOffset,
+ function cloneRange() {
+ return extend(new Range(dom), {
+ startContainer : t[START_CONTAINER],
+ startOffset : t[START_OFFSET],
+ endContainer : t[END_CONTAINER],
+ endOffset : t[END_OFFSET],
collapsed : t.collapsed,
commonAncestorContainer : t.commonAncestorContainer
});
- },
+ };
-/*
- detach : function() {
- // Not implemented
- },
-*/
- // Internal methods
+ // Private methods
- _isCollapsed : function() {
- return (this.startContainer == this.endContainer && this.startOffset == this.endOffset);
- },
+ function _getSelectedNode(container, offset) {
+ var child;
- _compareBoundaryPoints : function (containerA, offsetA, containerB, offsetB) {
+ if (container.nodeType == 3 /* TEXT_NODE */)
+ return container;
+
+ if (offset < 0)
+ return container;
+
+ child = container.firstChild;
+ while (child && offset > 0) {
+ --offset;
+ child = child.nextSibling;
+ }
+
+ if (child)
+ return child;
+
+ return container;
+ };
+
+ function _isCollapsed() {
+ return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
+ };
+
+ function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
var c, offsetC, n, cmnRoot, childA, childB;
- // In the first case the boundary-points have the same container. A is before B
- // if its offset is less than the offset of B, A is equal to B if its offset is
- // equal to the offset of B, and A is after B if its offset is greater than the
+ // In the first case the boundary-points have the same container. A is before B
+ // if its offset is less than the offset of B, A is equal to B if its offset is
+ // equal to the offset of B, and A is after B if its offset is greater than the
// offset of B.
if (containerA == containerB) {
- if (offsetA == offsetB) {
+ if (offsetA == offsetB)
return 0; // equal
- } else if (offsetA < offsetB) {
+
+ if (offsetA < offsetB)
return -1; // before
- } else {
- return 1; // after
- }
+
+ return 1; // after
}
- // In the second case a child node C of the container of A is an ancestor
- // container of B. In this case, A is before B if the offset of A is less than or
+ // In the second case a child node C of the container of A is an ancestor
+ // container of B. In this case, A is before B if the offset of A is less than or
// equal to the index of the child node C and A is after B otherwise.
c = containerB;
- while (c && c.parentNode != containerA) {
+ while (c && c.parentNode != containerA)
c = c.parentNode;
- }
+
if (c) {
offsetC = 0;
n = containerA.firstChild;
@@ -2728,15 +2811,14 @@ tinymce.create('static tinymce.util.XHR', {
n = n.nextSibling;
}
- if (offsetA <= offsetC) {
+ if (offsetA <= offsetC)
return -1; // before
- } else {
- return 1; // after
- }
+
+ return 1; // after
}
- // In the third case a child node C of the container of B is an ancestor container
- // of A. In this case, A is before B if the index of the child node C is less than
+ // In the third case a child node C of the container of B is an ancestor container
+ // of A. In this case, A is before B if the index of the child node C is less than
// the offset of B and A is after B otherwise.
c = containerA;
while (c && c.parentNode != containerB) {
@@ -2752,124 +2834,113 @@ tinymce.create('static tinymce.util.XHR', {
n = n.nextSibling;
}
- if (offsetC < offsetB) {
+ if (offsetC < offsetB)
return -1; // before
- } else {
- return 1; // after
- }
+
+ return 1; // after
}
- // In the fourth case, none of three other cases hold: the containers of A and B
- // are siblings or descendants of sibling nodes. In this case, A is before B if
+ // In the fourth case, none of three other cases hold: the containers of A and B
+ // are siblings or descendants of sibling nodes. In this case, A is before B if
// the container of A is before the container of B in a pre-order traversal of the
// Ranges' context tree and A is after B otherwise.
- cmnRoot = this.dom.findCommonAncestor(containerA, containerB);
+ cmnRoot = dom.findCommonAncestor(containerA, containerB);
childA = containerA;
- while (childA && childA.parentNode != cmnRoot) {
- childA = childA.parentNode;
- }
+ while (childA && childA.parentNode != cmnRoot)
+ childA = childA.parentNode;
- if (!childA) {
+ if (!childA)
childA = cmnRoot;
- }
childB = containerB;
- while (childB && childB.parentNode != cmnRoot) {
+ while (childB && childB.parentNode != cmnRoot)
childB = childB.parentNode;
- }
- if (!childB) {
+ if (!childB)
childB = cmnRoot;
- }
- if (childA == childB) {
+ if (childA == childB)
return 0; // equal
- }
n = cmnRoot.firstChild;
while (n) {
- if (n == childA) {
+ if (n == childA)
return -1; // before
- }
- if (n == childB) {
+ if (n == childB)
return 1; // after
- }
n = n.nextSibling;
}
- },
+ };
- _setEndPoint : function(st, n, o) {
- var t = this, ec, sc;
+ function _setEndPoint(st, n, o) {
+ var ec, sc;
if (st) {
- t.startContainer = n;
- t.startOffset = o;
+ t[START_CONTAINER] = n;
+ t[START_OFFSET] = o;
} else {
- t.endContainer = n;
- t.endOffset = o;
+ t[END_CONTAINER] = n;
+ t[END_OFFSET] = o;
}
- // If one boundary-point of a Range is set to have a root container
- // other than the current one for the Range, the Range is collapsed to
+ // If one boundary-point of a Range is set to have a root container
+ // other than the current one for the Range, the Range is collapsed to
// the new position. This enforces the restriction that both boundary-
// points of a Range must have the same root container.
- ec = t.endContainer;
+ ec = t[END_CONTAINER];
while (ec.parentNode)
ec = ec.parentNode;
- sc = t.startContainer;
+ sc = t[START_CONTAINER];
while (sc.parentNode)
sc = sc.parentNode;
- if (sc != ec) {
- t.collapse(st);
- } else {
- // The start position of a Range is guaranteed to never be after the
- // end position. To enforce this restriction, if the start is set to
- // be at a position after the end, the Range is collapsed to that
+ if (sc == ec) {
+ // The start position of a Range is guaranteed to never be after the
+ // end position. To enforce this restriction, if the start is set to
+ // be at a position after the end, the Range is collapsed to that
// position.
- if (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0)
+ if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
t.collapse(st);
- }
-
- t.collapsed = t._isCollapsed();
- t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer);
- },
+ } else
+ t.collapse(st);
- // This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :)
+ t.collapsed = _isCollapsed();
+ t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
+ };
- _traverse : function(how) {
- var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
+ function _traverse(how) {
+ var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
- if (t.startContainer == t.endContainer)
- return t._traverseSameContainer(how);
+ if (t[START_CONTAINER] == t[END_CONTAINER])
+ return _traverseSameContainer(how);
- for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
- if (p == t.startContainer)
- return t._traverseCommonStartContainer(c, how);
+ for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
+ if (p == t[START_CONTAINER])
+ return _traverseCommonStartContainer(c, how);
++endContainerDepth;
}
- for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
- if (p == t.endContainer)
- return t._traverseCommonEndContainer(c, how);
+ for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
+ if (p == t[END_CONTAINER])
+ return _traverseCommonEndContainer(c, how);
++startContainerDepth;
}
depthDiff = startContainerDepth - endContainerDepth;
- startNode = t.startContainer;
+ startNode = t[START_CONTAINER];
while (depthDiff > 0) {
startNode = startNode.parentNode;
depthDiff--;
}
- endNode = t.endContainer;
+ endNode = t[END_CONTAINER];
while (depthDiff < 0) {
endNode = endNode.parentNode;
depthDiff++;
@@ -2881,47 +2952,47 @@ tinymce.create('static tinymce.util.XHR', {
endNode = ep;
}
- return t._traverseCommonAncestors(startNode, endNode, how);
- },
+ return _traverseCommonAncestors(startNode, endNode, how);
+ };
- _traverseSameContainer : function(how) {
- var t = this, frag, s, sub, n, cnt, sibling, xferNode;
+ function _traverseSameContainer(how) {
+ var frag, s, sub, n, cnt, sibling, xferNode;
if (how != DELETE)
- frag = t.dom.doc.createDocumentFragment();
+ frag = doc.createDocumentFragment();
// If selection is empty, just return the fragment
- if (t.startOffset == t.endOffset)
+ if (t[START_OFFSET] == t[END_OFFSET])
return frag;
// Text node needs special case handling
- if (t.startContainer.nodeType == 3 /* TEXT_NODE */) {
+ if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
// get the substring
- s = t.startContainer.nodeValue;
- sub = s.substring(t.startOffset, t.endOffset);
+ s = t[START_CONTAINER].nodeValue;
+ sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
// set the original text node to its new value
if (how != CLONE) {
- t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset);
+ t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
// Nothing is partially selected, so collapse to start point
- t.collapse(true);
+ t.collapse(TRUE);
}
if (how == DELETE)
- return null;
+ return;
- frag.appendChild(t.dom.doc.createTextNode(sub));
+ frag.appendChild(doc.createTextNode(sub));
return frag;
}
// Copy nodes between the start/end offsets.
- n = getSelectedNode(t.startContainer, t.startOffset);
- cnt = t.endOffset - t.startOffset;
+ n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
+ cnt = t[END_OFFSET] - t[START_OFFSET];
while (cnt > 0) {
sibling = n.nextSibling;
- xferNode = t._traverseFullySelected(n, how);
+ xferNode = _traverseFullySelected(n, how);
if (frag)
frag.appendChild( xferNode );
@@ -2932,31 +3003,31 @@ tinymce.create('static tinymce.util.XHR', {
// Nothing is partially selected, so collapse to start point
if (how != CLONE)
- t.collapse(true);
+ t.collapse(TRUE);
return frag;
- },
+ };
- _traverseCommonStartContainer : function(endAncestor, how) {
- var t = this, frag, n, endIdx, cnt, sibling, xferNode;
+ function _traverseCommonStartContainer(endAncestor, how) {
+ var frag, n, endIdx, cnt, sibling, xferNode;
if (how != DELETE)
- frag = t.dom.doc.createDocumentFragment();
+ frag = doc.createDocumentFragment();
- n = t._traverseRightBoundary(endAncestor, how);
+ n = _traverseRightBoundary(endAncestor, how);
if (frag)
frag.appendChild(n);
- endIdx = indexOf(endAncestor, t.startContainer);
- cnt = endIdx - t.startOffset;
+ endIdx = nodeIndex(endAncestor);
+ cnt = endIdx - t[START_OFFSET];
if (cnt <= 0) {
- // Collapse to just before the endAncestor, which
+ // Collapse to just before the endAncestor, which
// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
- t.collapse(false);
+ t.collapse(FALSE);
}
return frag;
@@ -2965,7 +3036,7 @@ tinymce.create('static tinymce.util.XHR', {
n = endAncestor.previousSibling;
while (cnt > 0) {
sibling = n.previousSibling;
- xferNode = t._traverseFullySelected(n, how);
+ xferNode = _traverseFullySelected(n, how);
if (frag)
frag.insertBefore(xferNode, frag.firstChild);
@@ -2974,34 +3045,34 @@ tinymce.create('static tinymce.util.XHR', {
n = sibling;
}
- // Collapse to just before the endAncestor, which
+ // Collapse to just before the endAncestor, which
// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
- t.collapse(false);
+ t.collapse(FALSE);
}
return frag;
- },
+ };
- _traverseCommonEndContainer : function(startAncestor, how) {
- var t = this, frag, startIdx, n, cnt, sibling, xferNode;
+ function _traverseCommonEndContainer(startAncestor, how) {
+ var frag, startIdx, n, cnt, sibling, xferNode;
if (how != DELETE)
- frag = t.dom.doc.createDocumentFragment();
+ frag = doc.createDocumentFragment();
- n = t._traverseLeftBoundary(startAncestor, how);
+ n = _traverseLeftBoundary(startAncestor, how);
if (frag)
frag.appendChild(n);
- startIdx = indexOf(startAncestor, t.endContainer);
+ startIdx = nodeIndex(startAncestor);
++startIdx; // Because we already traversed it....
- cnt = t.endOffset - startIdx;
+ cnt = t[END_OFFSET] - startIdx;
n = startAncestor.nextSibling;
while (cnt > 0) {
sibling = n.nextSibling;
- xferNode = t._traverseFullySelected(n, how);
+ xferNode = _traverseFullySelected(n, how);
if (frag)
frag.appendChild(xferNode);
@@ -3012,25 +3083,25 @@ tinymce.create('static tinymce.util.XHR', {
if (how != CLONE) {
t.setStartAfter(startAncestor);
- t.collapse(true);
+ t.collapse(TRUE);
}
return frag;
- },
+ };
- _traverseCommonAncestors : function(startAncestor, endAncestor, how) {
- var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
+ function _traverseCommonAncestors(startAncestor, endAncestor, how) {
+ var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
if (how != DELETE)
- frag = t.dom.doc.createDocumentFragment();
+ frag = doc.createDocumentFragment();
- n = t._traverseLeftBoundary(startAncestor, how);
+ n = _traverseLeftBoundary(startAncestor, how);
if (frag)
frag.appendChild(n);
commonParent = startAncestor.parentNode;
- startOffset = indexOf(startAncestor, commonParent);
- endOffset = indexOf(endAncestor, commonParent);
+ startOffset = nodeIndex(startAncestor);
+ endOffset = nodeIndex(endAncestor);
++startOffset;
cnt = endOffset - startOffset;
@@ -3038,7 +3109,7 @@ tinymce.create('static tinymce.util.XHR', {
while (cnt > 0) {
nextSibling = sibling.nextSibling;
- n = t._traverseFullySelected(sibling, how);
+ n = _traverseFullySelected(sibling, how);
if (frag)
frag.appendChild(n);
@@ -3047,38 +3118,37 @@ tinymce.create('static tinymce.util.XHR', {
--cnt;
}
- n = t._traverseRightBoundary(endAncestor, how);
+ n = _traverseRightBoundary(endAncestor, how);
if (frag)
frag.appendChild(n);
if (how != CLONE) {
t.setStartAfter(startAncestor);
- t.collapse(true);
+ t.collapse(TRUE);
}
return frag;
- },
+ };
- _traverseRightBoundary : function(root, how) {
- var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent;
- var isFullySelected = next != t.endContainer;
+ function _traverseRightBoundary(root, how) {
+ var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
if (next == root)
- return t._traverseNode(next, isFullySelected, false, how);
+ return _traverseNode(next, isFullySelected, FALSE, how);
parent = next.parentNode;
- clonedParent = t._traverseNode(parent, false, false, how);
+ clonedParent = _traverseNode(parent, FALSE, FALSE, how);
- while (parent != null) {
- while (next != null) {
+ while (parent) {
+ while (next) {
prevSibling = next.previousSibling;
- clonedChild = t._traverseNode(next, isFullySelected, false, how);
+ clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
if (how != DELETE)
clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
- isFullySelected = true;
+ isFullySelected = TRUE;
next = prevSibling;
}
@@ -3088,37 +3158,33 @@ tinymce.create('static tinymce.util.XHR', {
next = parent.previousSibling;
parent = parent.parentNode;
- clonedGrandParent = t._traverseNode(parent, false, false, how);
+ clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);
clonedParent = clonedGrandParent;
}
+ };
- // should never occur
- return null;
- },
-
- _traverseLeftBoundary : function(root, how) {
- var t = this, next = getSelectedNode(t.startContainer, t.startOffset);
- var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
+ function _traverseLeftBoundary(root, how) {
+ var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
if (next == root)
- return t._traverseNode(next, isFullySelected, true, how);
+ return _traverseNode(next, isFullySelected, TRUE, how);
parent = next.parentNode;
- clonedParent = t._traverseNode(parent, false, true, how);
+ clonedParent = _traverseNode(parent, FALSE, TRUE, how);
- while (parent != null) {
- while (next != null) {
+ while (parent) {
+ while (next) {
nextSibling = next.nextSibling;
- clonedChild = t._traverseNode(next, isFullySelected, true, how);
+ clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
if (how != DELETE)
clonedParent.appendChild(clonedChild);
- isFullySelected = true;
+ isFullySelected = TRUE;
next = nextSibling;
}
@@ -3128,33 +3194,30 @@ tinymce.create('static tinymce.util.XHR', {
next = parent.nextSibling;
parent = parent.parentNode;
- clonedGrandParent = t._traverseNode(parent, false, true, how);
+ clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);
clonedParent = clonedGrandParent;
}
+ };
- // should never occur
- return null;
- },
-
- _traverseNode : function(n, isFullySelected, isLeft, how) {
- var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode;
+ function _traverseNode(n, isFullySelected, isLeft, how) {
+ var txtValue, newNodeValue, oldNodeValue, offset, newNode;
if (isFullySelected)
- return t._traverseFullySelected(n, how);
+ return _traverseFullySelected(n, how);
if (n.nodeType == 3 /* TEXT_NODE */) {
txtValue = n.nodeValue;
if (isLeft) {
- offset = t.startOffset;
+ offset = t[START_OFFSET];
newNodeValue = txtValue.substring(offset);
oldNodeValue = txtValue.substring(0, offset);
} else {
- offset = t.endOffset;
+ offset = t[END_OFFSET];
newNodeValue = txtValue.substring(0, offset);
oldNodeValue = txtValue.substring(offset);
}
@@ -3163,102 +3226,64 @@ tinymce.create('static tinymce.util.XHR', {
n.nodeValue = oldNodeValue;
if (how == DELETE)
- return null;
+ return;
- newNode = n.cloneNode(false);
+ newNode = n.cloneNode(FALSE);
newNode.nodeValue = newNodeValue;
return newNode;
}
if (how == DELETE)
- return null;
-
- return n.cloneNode(false);
- },
+ return;
- _traverseFullySelected : function(n, how) {
- var t = this;
+ return n.cloneNode(FALSE);
+ };
+ function _traverseFullySelected(n, how) {
if (how != DELETE)
- return how == CLONE ? n.cloneNode(true) : n;
+ return how == CLONE ? n.cloneNode(TRUE) : n;
n.parentNode.removeChild(n);
- return null;
- }
- });
+ };
+ };
ns.Range = Range;
})(tinymce.dom);
+
(function() {
function Selection(selection) {
- var t = this, invisibleChar = '\uFEFF', range, lastIERng;
+ var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
+ // Compares two IE specific ranges to see if they are different
+ // this method is useful when invalidating the cached selection range
function compareRanges(rng1, rng2) {
if (rng1 && rng2) {
// Both are control ranges and the selected element matches
if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
- return 1;
+ return TRUE;
// Both are text ranges and the range matches
- if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
- return 1;
+ if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
+ // IE will say that the range is equal then produce an invalid argument exception
+ // if you perform specific operations in a keyup event. For example Ctrl+Del.
+ // This hack will invalidate the range cache if the exception occurs
+ try {
+ // Try accessing nextSibling will producer an invalid argument some times
+ range.startContainer.nextSibling;
+ return TRUE;
+ } catch (ex) {
+ // Ignore
+ }
+ }
}
- return 0;
+ return FALSE;
};
+ // Returns a W3C DOM compatible range object by using the IE Range API
function getRange() {
- var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed;
-
- function findIndex(element) {
- var nl = element.parentNode.childNodes, i;
-
- for (i = nl.length - 1; i >= 0; i--) {
- if (nl[i] == element)
- return i;
- }
-
- return -1;
- };
-
- function findEndPoint(start) {
- var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng;
-
- // Insert marker character
- rng.collapse(start);
- parent = rng.parentElement();
- rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue
-
- // Find marker character
- nl = parent.childNodes;
- for (i = 0; i < nl.length; i++) {
- n = nl[i];
-
- // Calculate node index excluding text node fragmentation
- if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3))
- index++;
-
- // If text node then calculate offset
- if (n.nodeType === 3) {
- // Look for marker
- pos = n.nodeValue.indexOf(invisibleChar);
- if (pos !== -1) {
- offset += pos;
- break;
- }
-
- offset += n.nodeValue.length;
- } else
- offset = 0;
- }
-
- // Remove marker character
- rng.moveStart('character', -1);
- rng.text = '';
-
- return {index : index, offset : offset, parent : parent};
- };
+ var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged;
// If selection is outside the current document just return an empty range
element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
@@ -3267,61 +3292,98 @@ tinymce.create('static tinymce.util.XHR', {
// Handle control selection or text selection of a image
if (ieRange.item || !element.hasChildNodes()) {
- domRange.setStart(element.parentNode, findIndex(element));
+ domRange.setStart(element.parentNode, dom.nodeIndex(element));
domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
return domRange;
}
- // Check collapsed state
+ // Duplicare IE selection range and check if the range is collapsed
+ ieRange2 = ieRange.duplicate();
collapsed = selection.isCollapsed();
- // Find start and end pos index and offset
- startPos = findEndPoint(true);
- endPos = findEndPoint(false);
+ // Insert invisible start marker
+ ieRange.collapse();
+ ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>');
- // Normalize the elements to avoid fragmented dom
- startPos.parent.normalize();
- endPos.parent.normalize();
+ // Insert invisible end marker
+ if (!collapsed) {
+ ieRange2.collapse(FALSE);
+ ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>');
+ }
+
+ // Sets the end point of the range by looking for the marker
+ // This method also merges the text nodes it splits so that
+ // the DOM doesn't get fragmented.
+ function setEndPoint(start) {
+ var container, offset, marker, sibling;
+
+ // Look for endpoint marker
+ marker = dom.get('_mce_' + (start ? 'start' : 'end'));
+ sibling = marker.previousSibling;
+
+ // Is marker after a text node
+ if (sibling && sibling.nodeType == 3) {
+ // Get container node and calc offset
+ container = sibling;
+ offset = container.nodeValue.length;
+ dom.remove(marker);
+
+ // Merge text nodes to reduce DOM fragmentation
+ sibling = container.nextSibling;
+ if (sibling && sibling.nodeType == 3) {
+ isMerged = TRUE;
+ container.appendData(sibling.nodeValue);
+ dom.remove(sibling);
+ }
+ } else {
+ sibling = marker.nextSibling;
- // Set start container and offset
- sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)];
+ // Is marker before a text node
+ if (sibling && sibling.nodeType == 3) {
+ container = sibling;
+ offset = 0;
+ } else {
+ // Is marker before an element
+ if (sibling)
+ offset = dom.nodeIndex(sibling) - 1;
+ else
+ offset = dom.nodeIndex(marker);
- if (sc.nodeType != 3)
- domRange.setStart(startPos.parent, startPos.index);
- else
- domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset);
+ container = marker.parentNode;
+ }
- // Set end container and offset
- ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)];
+ dom.remove(marker);
+ }
- if (ec.nodeType != 3) {
- if (!collapsed)
- endPos.index++;
+ // Set start of range
+ if (start)
+ domRange.setStart(container, offset);
- domRange.setEnd(endPos.parent, endPos.index);
- } else
- domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset);
+ // Set end of range or automatically if it's collapsed to increase performance
+ if (!start || collapsed)
+ domRange.setEnd(container, offset);
+ };
- // If not collapsed then make sure offsets are valid
- if (!collapsed) {
- sc = domRange.startContainer;
- if (sc.nodeType == 1)
- domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length));
+ // Set start of range
+ setEndPoint(TRUE);
- ec = domRange.endContainer;
- if (ec.nodeType == 1)
- domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length));
- }
+ // Set end of range if needed
+ if (!collapsed)
+ setEndPoint(FALSE);
- // Restore selection to new range
- t.addRange(domRange);
+ // Restore selection if the range contents was merged
+ // since the selection was then moved since the text nodes got changed
+ if (isMerged)
+ t.addRange(domRange);
return domRange;
};
this.addRange = function(rng) {
- var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo;
+ var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd;
+
+ this.destroy();
// Setup some shorter versions
sc = rng.startContainer;
@@ -3330,9 +3392,45 @@ tinymce.create('static tinymce.util.XHR', {
eo = rng.endOffset;
ieRng = body.createTextRange();
- // Find element
- sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
- ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
+ // If document selection move caret to first node in document
+ if (sc == doc || ec == doc) {
+ ieRng = body.createTextRange();
+ ieRng.collapse();
+ ieRng.select();
+ return;
+ }
+
+ // If child index resolve it
+ if (sc.nodeType == 1 && sc.hasChildNodes()) {
+ lastIndex = sc.childNodes.length - 1;
+
+ // Index is higher that the child count then we need to jump over the start container
+ if (so > lastIndex) {
+ skipStart = 1;
+ sc = sc.childNodes[lastIndex];
+ } else
+ sc = sc.childNodes[so];
+
+ // Child was text node then move offset to start of it
+ if (sc.nodeType == 3)
+ so = 0;
+ }
+
+ // If child index resolve it
+ if (ec.nodeType == 1 && ec.hasChildNodes()) {
+ lastIndex = ec.childNodes.length - 1;
+
+ if (eo == 0) {
+ skipEnd = 1;
+ ec = ec.childNodes[0];
+ } else {
+ ec = ec.childNodes[Math.min(lastIndex, eo - 1)];
+
+ // Child was text node then move offset to end of text node
+ if (ec.nodeType == 3)
+ eo = ec.nodeValue.length;
+ }
+ }
// Single element selection
if (sc == ec && sc.nodeType == 1) {
@@ -3352,7 +3450,7 @@ tinymce.create('static tinymce.util.XHR', {
// If it's only containing a padding remove it so the caret remains
if (sc.innerHTML == invisibleChar) {
- ieRng.collapse(true);
+ ieRng.collapse(TRUE);
sc.removeChild(sc.firstChild);
}
}
@@ -3361,78 +3459,61 @@ tinymce.create('static tinymce.util.XHR', {
ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
ieRng.select();
-
+ ieRng.scrollIntoView();
return;
}
- function getCharPos(container, offset) {
- var nodeVal, rng, pos;
-
- if (container.nodeType != 3)
- return -1;
-
- nodeVal = container.nodeValue;
- rng = body.createTextRange();
-
- // Insert marker at offset position
- container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset);
+ // Create range and marker
+ ieRng = body.createTextRange();
+ marker = doc.createElement('span');
+ marker.innerHTML = ' ';
- // Find char pos of marker and remove it
- rng.moveToElementText(container.parentNode);
- rng.findText(invisibleChar);
- pos = Math.abs(rng.moveStart('character', -0xFFFFF));
- container.nodeValue = nodeVal;
+ // Set start of range to startContainer/startOffset
+ if (sc.nodeType == 3) {
+ // Insert marker after/before startContainer
+ if (skipStart)
+ dom.insertAfter(marker, sc);
+ else
+ sc.parentNode.insertBefore(marker, sc);
- return pos;
- };
+ // Select marker the caret to offset position
+ ieRng.moveToElementText(marker);
+ marker.parentNode.removeChild(marker);
+ ieRng.move('character', so);
+ } else {
+ ieRng.moveToElementText(sc);
- // Collapsed range
- if (rng.collapsed) {
- pos = getCharPos(sc, so);
+ if (skipStart)
+ ieRng.collapse(FALSE);
+ }
- ieRng = body.createTextRange();
- ieRng.move('character', pos);
+ // If same text container then we can do a more simple move
+ if (sc == ec && sc.nodeType == 3) {
+ ieRng.moveEnd('character', eo - so);
ieRng.select();
-
+ ieRng.scrollIntoView();
return;
- } else {
- // If same text container
- if (sc == ec && sc.nodeType == 3) {
- startPos = getCharPos(sc, so);
-
- ieRng = body.createTextRange();
- ieRng.move('character', startPos);
- ieRng.moveEnd('character', eo - so);
- ieRng.select();
-
- return;
- }
-
- // Get caret positions
- startPos = getCharPos(sc, so);
- endPos = getCharPos(ec, eo);
- ieRng = body.createTextRange();
-
- // Move start of range to start character position or start element
- if (startPos == -1) {
- ieRng.moveToElementText(sc);
- startPos = 0;
- } else
- ieRng.move('character', startPos);
-
- // Move end of range to end character position or end element
- tmpRng = body.createTextRange();
-
- if (endPos == -1)
- tmpRng.moveToElementText(ec);
- else
- tmpRng.move('character', endPos);
+ }
- ieRng.setEndPoint('EndToEnd', tmpRng);
- ieRng.select();
+ // Set end of range to endContainer/endOffset
+ ieRng2 = body.createTextRange();
+ if (ec.nodeType == 3) {
+ // Insert marker after/before startContainer
+ ec.parentNode.insertBefore(marker, ec);
- return;
+ // Move selection to end marker and move caret to end offset
+ ieRng2.moveToElementText(marker);
+ marker.parentNode.removeChild(marker);
+ ieRng2.move('character', eo);
+ ieRng.setEndPoint('EndToStart', ieRng2);
+ } else {
+ ieRng2.moveToElementText(ec);
+ ieRng2.collapse(!!skipEnd);
+ ieRng.setEndPoint('EndToEnd', ieRng2);
}
+
+ ieRng.select();
+ ieRng.scrollIntoView();
};
this.getRangeAt = function() {
@@ -3452,12 +3533,86 @@ tinymce.create('static tinymce.util.XHR', {
// Destroy cached range and last IE range to avoid memory leaks
lastIERng = range = null;
};
+
+ // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
+ if (selection.dom.boxModel) {
+ (function() {
+ var doc = dom.doc, body = doc.body, started, startRng;
+
+ // Make HTML element unselectable since we are going to handle selection by hand
+ doc.documentElement.unselectable = TRUE;
+
+ // Return range from point or null if it failed
+ function rngFromPoint(x, y) {
+ var rng = body.createTextRange();
+
+ try {
+ rng.moveToPoint(x, y);
+ } catch (ex) {
+ // IE sometimes throws and exception, so lets just ignore it
+ rng = null;
+ }
+
+ return rng;
+ };
+
+ // Fires while the selection is changing
+ function selectionChange(e) {
+ var pointRng;
+
+ // Check if the button is down or not
+ if (e.button) {
+ // Create range from mouse position
+ pointRng = rngFromPoint(e.x, e.y);
+
+ if (pointRng) {
+ // Check if pointRange is before/after selection then change the endPoint
+ if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
+ pointRng.setEndPoint('StartToStart', startRng);
+ else
+ pointRng.setEndPoint('EndToEnd', startRng);
+
+ pointRng.select();
+ }
+ } else
+ endSelection();
+ }
+
+ // Removes listeners
+ function endSelection() {
+ dom.unbind(doc, 'mouseup', endSelection);
+ dom.unbind(doc, 'mousemove', selectionChange);
+ started = 0;
+ };
+
+ // Detect when user selects outside BODY
+ dom.bind(doc, 'mousedown', function(e) {
+ if (e.target.nodeName === 'HTML') {
+ if (started)
+ endSelection();
+
+ started = 1;
+
+ // Setup start position
+ startRng = rngFromPoint(e.x, e.y);
+ if (startRng) {
+ // Listen for selection change events
+ dom.bind(doc, 'mouseup', endSelection);
+ dom.bind(doc, 'mousemove', selectionChange);
+
+ startRng.select();
+ }
+ }
+ });
+ })();
+ }
};
// Expose the selection object
tinymce.dom.TridentSelection = Selection;
})();
+
/*
* Sizzle CSS Selector Engine - v1.0
* Copyright 2009, The Dojo Foundation
@@ -4430,6 +4585,7 @@ window.tinymce.dom.Sizzle = Sizzle;
})();
+
(function(tinymce) {
// Shorten names
var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
@@ -4713,45 +4869,25 @@ window.tinymce.dom.Sizzle = Sizzle;
Event.destroy();
});
})(tinymce);
-(function(tinymce) {
- var each = tinymce.each;
-
- tinymce.create('tinymce.dom.Element', {
- Element : function(id, s) {
- var t = this, dom, el;
-
- s = s || {};
- t.id = id;
- t.dom = dom = s.dom || tinymce.DOM;
- t.settings = s;
-
- // Only IE leaks DOM references, this is a lot faster
- if (!tinymce.isIE)
- el = t.dom.get(t.id);
- each([
- 'getPos',
- 'getRect',
- 'getParent',
- 'add',
- 'setStyle',
- 'getStyle',
- 'setStyles',
- 'setAttrib',
- 'setAttribs',
- 'getAttrib',
- 'addClass',
- 'removeClass',
- 'hasClass',
- 'getOuterHTML',
- 'setOuterHTML',
- 'remove',
- 'show',
- 'hide',
- 'isHidden',
- 'setHTML',
- 'get'
- ], function(k) {
+(function(tinymce) {
+ tinymce.dom.Element = function(id, settings) {
+ var t = this, dom, el;
+
+ t.settings = settings = settings || {};
+ t.id = id;
+ t.dom = dom = settings.dom || tinymce.DOM;
+
+ // Only IE leaks DOM references, this is a lot faster
+ if (!tinymce.isIE)
+ el = dom.get(t.id);
+
+ tinymce.each(
+ ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
+ 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
+ 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
+ 'isHidden,setHTML,get').split(/,/)
+ , function(k) {
t[k] = function() {
var a = [id], i;
@@ -4763,82 +4899,86 @@ window.tinymce.dom.Sizzle = Sizzle;
return a;
};
- });
- },
-
- on : function(n, f, s) {
- return tinymce.dom.Event.add(this.id, n, f, s);
- },
+ });
- getXY : function() {
- return {
- x : parseInt(this.getStyle('left')),
- y : parseInt(this.getStyle('top'))
- };
- },
+ tinymce.extend(t, {
+ on : function(n, f, s) {
+ return tinymce.dom.Event.add(t.id, n, f, s);
+ },
- getSize : function() {
- var n = this.dom.get(this.id);
+ getXY : function() {
+ return {
+ x : parseInt(t.getStyle('left')),
+ y : parseInt(t.getStyle('top'))
+ };
+ },
- return {
- w : parseInt(this.getStyle('width') || n.clientWidth),
- h : parseInt(this.getStyle('height') || n.clientHeight)
- };
- },
+ getSize : function() {
+ var n = dom.get(t.id);
- moveTo : function(x, y) {
- this.setStyles({left : x, top : y});
- },
+ return {
+ w : parseInt(t.getStyle('width') || n.clientWidth),
+ h : parseInt(t.getStyle('height') || n.clientHeight)
+ };
+ },
- moveBy : function(x, y) {
- var p = this.getXY();
+ moveTo : function(x, y) {
+ t.setStyles({left : x, top : y});
+ },
- this.moveTo(p.x + x, p.y + y);
- },
+ moveBy : function(x, y) {
+ var p = t.getXY();
- resizeTo : function(w, h) {
- this.setStyles({width : w, height : h});
- },
+ t.moveTo(p.x + x, p.y + y);
+ },
- resizeBy : function(w, h) {
- var s = this.getSize();
+ resizeTo : function(w, h) {
+ t.setStyles({width : w, height : h});
+ },
- this.resizeTo(s.w + w, s.h + h);
- },
+ resizeBy : function(w, h) {
+ var s = t.getSize();
- update : function(k) {
- var t = this, b, dom = t.dom;
+ t.resizeTo(s.w + w, s.h + h);
+ },
- if (tinymce.isIE6 && t.settings.blocker) {
- k = k || '';
+ update : function(k) {
+ var b;
- // Ignore getters
- if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
- return;
+ if (tinymce.isIE6 && settings.blocker) {
+ k = k || '';
- // Remove blocker on remove
- if (k == 'remove') {
- dom.remove(t.blocker);
- return;
- }
+ // Ignore getters
+ if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
+ return;
- if (!t.blocker) {
- t.blocker = dom.uniqueId();
- b = dom.add(t.settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
- dom.setStyle(b, 'opacity', 0);
- } else
- b = dom.get(t.blocker);
+ // Remove blocker on remove
+ if (k == 'remove') {
+ dom.remove(t.blocker);
+ return;
+ }
- dom.setStyle(b, 'left', t.getStyle('left', 1));
- dom.setStyle(b, 'top', t.getStyle('top', 1));
- dom.setStyle(b, 'width', t.getStyle('width', 1));
- dom.setStyle(b, 'height', t.getStyle('height', 1));
- dom.setStyle(b, 'display', t.getStyle('display', 1));
- dom.setStyle(b, 'zIndex', parseInt(t.getStyle('zIndex', 1) || 0) - 1);
+ if (!t.blocker) {
+ t.blocker = dom.uniqueId();
+ b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
+ dom.setStyle(b, 'opacity', 0);
+ } else
+ b = dom.get(t.blocker);
+
+ dom.setStyles(b, {
+ left : t.getStyle('left', 1),
+ top : t.getStyle('top', 1),
+ width : t.getStyle('width', 1),
+ height : t.getStyle('height', 1),
+ display : t.getStyle('display', 1),
+ zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
+ });
+ }
}
- }
- });
+ });
+ };
})(tinymce);
+
(function(tinymce) {
function trimNl(s) {
return s.replace(/[\n\r]+/g, '');
@@ -4926,8 +5066,13 @@ window.tinymce.dom.Sizzle = Sizzle;
h += '<span id="__caret">_</span>';
// Delete and insert new node
- r.deleteContents();
- r.insertNode(t.getRng().createContextualFragment(h));
+ if (r.startContainer == d && r.endContainer == d) {
+ // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
+ d.body.innerHTML = h;
+ } else {
+ r.deleteContents();
+ r.insertNode(t.getRng().createContextualFragment(h));
+ }
// Move to caret marker
c = t.dom.get('__caret');
@@ -4935,14 +5080,9 @@ window.tinymce.dom.Sizzle = Sizzle;
// Make sure we wrap it compleatly, Opera fails with a simple select call
r = d.createRange();
r.setStartBefore(c);
- r.setEndAfter(c);
+ r.setEndBefore(c);
t.setRng(r);
- // Delete the marker, and hopefully the caret gets placed in the right location
- // Removed this since it seems to remove &nbsp; in FF and simply deleting it
- // doesn't seem to affect the caret position in any browser
- //d.execCommand('Delete', false, null);
-
// Remove the caret position
t.dom.remove('__caret');
} else {
@@ -4971,21 +5111,24 @@ window.tinymce.dom.Sizzle = Sizzle;
e = r.parentElement();
if (e && e.nodeName == 'BODY')
- return e.firstChild;
+ return e.firstChild || e;
return e;
} else {
e = r.startContainer;
- if (e.nodeName == 'BODY')
- return e.firstChild;
+ if (e.nodeType == 1 && e.hasChildNodes())
+ e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)];
+
+ if (e && e.nodeType == 3)
+ return e.parentNode;
- return t.dom.getParent(e, '*');
+ return e;
}
},
getEnd : function() {
- var t = this, r = t.getRng(), e;
+ var t = this, r = t.getRng(), e, eo;
if (isIE) {
if (r.item)
@@ -4996,333 +5139,282 @@ window.tinymce.dom.Sizzle = Sizzle;
e = r.parentElement();
if (e && e.nodeName == 'BODY')
- return e.lastChild;
+ return e.lastChild || e;
return e;
} else {
e = r.endContainer;
+ eo = r.endOffset;
+
+ if (e.nodeType == 1 && e.hasChildNodes())
+ e = e.childNodes[eo > 0 ? eo - 1 : eo];
- if (e.nodeName == 'BODY')
- return e.lastChild;
+ if (e && e.nodeType == 3)
+ return e.parentNode;
- return t.dom.getParent(e, '*');
+ return e;
}
},
- getBookmark : function(si) {
- var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv;
- sx = vp.x;
- sy = vp.y;
+ getBookmark : function(type, normalized) {
+ var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
- // Simple bookmark fast but not as persistent
- if (si)
- return {rng : r, scrollX : sx, scrollY : sy};
+ function findIndex(name, element) {
+ var index = 0;
- // Handle IE
- if (isIE) {
- // Control selection
- if (r.item) {
- e = r.item(0);
-
- each(t.dom.select(e.nodeName), function(n, i) {
- if (e == n) {
- sp = i;
- return false;
- }
- });
+ each(dom.select(name), function(node, i) {
+ if (node == element)
+ index = i;
+ });
- return {
- tag : e.nodeName,
- index : sp,
- scrollX : sx,
- scrollY : sy
- };
- }
+ return index;
+ };
- // Text selection
- tr = t.dom.doc.body.createTextRange();
- tr.moveToElementText(ro);
- tr.collapse(true);
- bp = Math.abs(tr.move('character', c));
+ if (type == 2) {
+ function getLocation() {
+ var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
- tr = r.duplicate();
- tr.collapse(true);
- sp = Math.abs(tr.move('character', c));
+ function getPoint(rng, start) {
+ var container = rng[start ? 'startContainer' : 'endContainer'],
+ offset = rng[start ? 'startOffset' : 'endOffset'], point = [], childNodes, after = 0;
- tr = r.duplicate();
- tr.collapse(false);
- le = Math.abs(tr.move('character', c)) - sp;
+ if (container.nodeType == 3) {
+ if (normalized) {
+ for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
+ offset += node.nodeValue.length;
+ }
- return {
- start : sp - bp,
- length : le,
- scrollX : sx,
- scrollY : sy
- };
- }
+ point.push(offset);
+ } else {
+ childNodes = container.childNodes;
+
+ if (offset >= childNodes.length) {
+ after = 1;
+ offset = childNodes.length - 1;
+ }
- // Handle W3C
- e = t.getNode();
- s = t.getSel();
+ point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
+ }
- if (!s)
- return null;
+ for (; container && container != root; container = container.parentNode)
+ point.push(t.dom.nodeIndex(container, normalized));
- // Image selection
- if (e && e.nodeName == 'IMG') {
- return {
- scrollX : sx,
- scrollY : sy
- };
- }
+ return point;
+ };
- // Text selection
+ bookmark.start = getPoint(rng, true);
- function getPos(r, sn, en) {
- var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {};
+ if (!t.isCollapsed())
+ bookmark.end = getPoint(rng);
- while ((n = w.nextNode()) != null) {
- if (n == sn)
- d.start = p;
+ return bookmark;
+ };
- if (n == en) {
- d.end = p;
- return d;
- }
+ return getLocation();
+ }
- p += trimNl(n.nodeValue || '').length;
- }
+ // Handle simple range
+ if (type)
+ return {rng : t.getRng()};
- return null;
- };
+ rng = t.getRng();
+ id = dom.uniqueId();
+ collapsed = tinyMCE.activeEditor.selection.isCollapsed();
+ styles = 'overflow:hidden;line-height:0px';
- // Caret or selection
- if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) {
- e = getPos(ro, s.anchorNode, s.focusNode);
+ // Explorer method
+ if (rng.duplicate || rng.item) {
+ // Text selection
+ if (!rng.item) {
+ rng2 = rng.duplicate();
- if (!e)
- return {scrollX : sx, scrollY : sy};
+ // Insert start marker
+ rng.collapse();
+ rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
- // Count whitespace before
- trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
+ // Insert end marker
+ if (!collapsed) {
+ rng2.collapse(false);
+ rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
+ }
+ } else {
+ // Control selection
+ element = rng.item(0);
+ name = element.nodeName;
- return {
- start : Math.max(e.start + s.anchorOffset - wb, 0),
- end : Math.max(e.end + s.focusOffset - wb, 0),
- scrollX : sx,
- scrollY : sy,
- beg : s.anchorOffset - wb == 0
- };
+ return {name : name, index : findIndex(name, element)};
+ }
} else {
- e = getPos(ro, r.startContainer, r.endContainer);
+ element = t.getNode();
+ name = element.nodeName;
+ if (name == 'IMG')
+ return {name : name, index : findIndex(name, element)};
- // Count whitespace before start and end container
- //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
- //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;});
+ // W3C method
+ rng2 = rng.cloneRange();
- if (!e)
- return {scrollX : sx, scrollY : sy};
+ // Insert end marker
+ if (!collapsed) {
+ rng2.collapse(false);
+ rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
+ }
- return {
- start : Math.max(e.start + r.startOffset - wb, 0),
- end : Math.max(e.end + r.endOffset - wa, 0),
- scrollX : sx,
- scrollY : sy,
- beg : r.startOffset - wb == 0
- };
+ rng.collapse(true);
+ rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
}
- },
-
- moveToBookmark : function(b) {
- var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv;
-
- function getPos(r, sp, ep) {
- var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb;
- while ((n = w.nextNode()) != null) {
- wa = wb = 0;
+ t.moveToBookmark({id : id, keep : 1});
- nv = n.nodeValue || '';
- //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;});
- //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;});
+ return {id : id};
+ },
- nvl = trimNl(nv).length;
- p += nvl;
+ moveToBookmark : function(bookmark) {
+ var t = this, dom = t.dom, marker1, marker2, rng, root;
- if (p >= sp && !d.startNode) {
- o = sp - (p - nvl);
+ // Clear selection cache
+ if (t.tridentSel)
+ t.tridentSel.destroy();
- // Fix for odd quirk in FF
- if (b.beg && o >= nvl)
- continue;
+ if (bookmark) {
+ if (bookmark.start) {
+ rng = dom.createRng();
+ root = dom.getRoot();
- d.startNode = n;
- d.startOffset = o + wb;
- }
+ function setEndPoint(start) {
+ var point = bookmark[start ? 'start' : 'end'], i, node, offset;
- if (p >= ep) {
- d.endNode = n;
- d.endOffset = ep - (p - nvl) + wb;
- return d;
- }
- }
+ if (point) {
+ // Find container node
+ for (node = root, i = point.length - 1; i >= 1; i--)
+ node = node.childNodes[point[i]];
- return null;
- };
+ // Set offset within container node
+ if (start)
+ rng.setStart(node, point[0]);
+ else
+ rng.setEnd(node, point[0]);
+ }
+ };
- if (!b)
- return false;
+ setEndPoint(true);
+ setEndPoint();
- t.win.scrollTo(b.scrollX, b.scrollY);
+ t.setRng(rng);
+ } else if (bookmark.id) {
+ rng = dom.createRng();
- // Handle explorer
- if (isIE) {
- t.tridentSel.destroy();
+ function restoreEndPoint(suffix) {
+ var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
- // Handle simple
- if (r = b.rng) {
- try {
- r.select();
- } catch (ex) {
- // Ignore
- }
+ if (marker) {
+ node = marker.parentNode;
- return true;
- }
-
- t.win.focus();
-
- // Handle control bookmark
- if (b.tag) {
- r = ro.createControlRange();
-
- each(t.dom.select(b.tag), function(n, i) {
- if (i == b.index)
- r.addElement(n);
- });
- } else {
- // Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs
- try {
- // Incorrect bookmark
- if (b.start < 0)
- return true;
+ if (suffix == 'start') {
+ if (!keep) {
+ idx = dom.nodeIndex(marker);
+ } else {
+ node = marker;
+ idx = 1;
+ }
- r = s.createRange();
- r.moveToElementText(ro);
- r.collapse(true);
- r.moveStart('character', b.start);
- r.moveEnd('character', b.length);
- } catch (ex2) {
- return true;
- }
- }
+ rng.setStart(node, idx);
+ rng.setEnd(node, idx);
+ } else {
+ if (!keep) {
+ idx = dom.nodeIndex(marker);
+ } else {
+ node = marker;
+ idx = 1;
+ }
- try {
- r.select();
- } catch (ex) {
- // Needed for some odd IE bug #1843306
- }
+ rng.setEnd(node, idx);
+ }
- return true;
- }
+ if (!keep) {
+ prev = marker.previousSibling;
+ next = marker.nextSibling;
- // Handle W3C
- if (!s)
- return false;
+ // Remove all marker text nodes
+ each(tinymce.grep(marker.childNodes), function(node) {
+ if (node.nodeType == 3)
+ node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
+ });
- // Handle simple
- if (b.rng) {
- s.removeAllRanges();
- s.addRange(b.rng);
- } else {
- if (is(b.start) && is(b.end)) {
- try {
- sd = getPos(ro, b.start, b.end);
-
- if (sd) {
- r = t.dom.doc.createRange();
- r.setStart(sd.startNode, sd.startOffset);
- r.setEnd(sd.endNode, sd.endOffset);
- s.removeAllRanges();
- s.addRange(r);
+ // Remove marker but keep children if for example contents where inserted into the marker
+ // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
+ while (marker = dom.get(bookmark.id + '_' + suffix))
+ dom.remove(marker, 1);
+
+ // If siblings are text nodes then merge them
+ if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
+ idx = prev.nodeValue.length;
+ prev.appendData(next.nodeValue);
+ dom.remove(next);
+
+ if (suffix == 'start') {
+ rng.setStart(prev, idx);
+ rng.setEnd(prev, idx);
+ } else
+ rng.setEnd(prev, idx);
+ }
+ }
}
+ };
- if (!tinymce.isOpera)
- t.win.focus();
- } catch (ex) {
- // Ignore
- }
- }
+ // Restore start/end points
+ restoreEndPoint('start');
+ restoreEndPoint('end');
+
+ t.setRng(rng);
+ } else if (bookmark.name) {
+ t.select(dom.select(bookmark.name)[bookmark.index]);
+ } else if (bookmark.rng)
+ t.setRng(bookmark.rng);
}
},
- select : function(n, c) {
- var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document;
-
- function find(n, start) {
- var walker, o;
+ select : function(node, content) {
+ var t = this, dom = t.dom, rng = dom.createRng(), idx;
- if (n) {
- walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+ idx = dom.nodeIndex(node);
+ rng.setStart(node.parentNode, idx);
+ rng.setEnd(node.parentNode, idx + 1);
- // Find first/last non empty text node
- while (n = walker.nextNode()) {
- o = n;
+ // Find first/last text node or BR element
+ if (content) {
+ function setPoint(node, start) {
+ var walker = new tinymce.dom.TreeWalker(node, node);
- if (tinymce.trim(n.nodeValue).length != 0) {
+ do {
+ // Text node
+ if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
if (start)
- return n;
+ rng.setStart(node, 0);
else
- o = n;
- }
- }
- }
-
- return o;
- };
-
- if (isIE) {
- try {
- b = d.body;
-
- if (/^(IMG|TABLE)$/.test(n.nodeName)) {
- r = b.createControlRange();
- r.addElement(n);
- } else {
- r = b.createTextRange();
- r.moveToElementText(n);
- }
-
- r.select();
- } catch (ex) {
- // Throws illigal agrument in IE some times
- }
- } else {
- if (c) {
- fn = find(n, 1) || t.dom.select('br:first', n)[0];
- ln = find(n, 0) || t.dom.select('br:last', n)[0];
+ rng.setEnd(node, node.nodeValue.length);
- if (fn && ln) {
- r = d.createRange();
+ return;
+ }
- if (fn.nodeName == 'BR')
- r.setStartBefore(fn);
- else
- r.setStart(fn, 0);
+ // BR element
+ if (node.nodeName == 'BR') {
+ if (start)
+ rng.setStartBefore(node);
+ else
+ rng.setEndBefore(node);
- if (ln.nodeName == 'BR')
- r.setEndBefore(ln);
- else
- r.setEnd(ln, ln.nodeValue.length);
- } else
- r.selectNode(n);
- } else
- r.selectNode(n);
+ return;
+ }
+ } while (node = (start ? walker.next() : walker.prev()));
+ };
- t.setRng(r);
+ setPoint(node, 1);
+ setPoint(node);
}
- return n;
+ t.setRng(rng);
+
+ return node;
},
isCollapsed : function() {
@@ -5331,7 +5423,10 @@ window.tinymce.dom.Sizzle = Sizzle;
if (!r || r.item)
return false;
- return !s || r.boundingWidth == 0 || r.collapsed;
+ if (r.compareEndPoints)
+ return r.compareEndPoints('StartToEnd', r) === 0;
+
+ return !s || r.collapsed;
},
collapse : function(b) {
@@ -5412,33 +5507,36 @@ window.tinymce.dom.Sizzle = Sizzle;
},
getNode : function() {
- var t = this, r = t.getRng(), s = t.getSel(), e;
+ var t = this, rng = t.getRng(), sel = t.getSel(), elm;
if (!isIE) {
// Range maybe lost after the editor is made visible again
- if (!r)
+ if (!rng)
return t.dom.getRoot();
- e = r.commonAncestorContainer;
+ elm = rng.commonAncestorContainer;
// Handle selection a image or other control like element such as anchors
- if (!r.collapsed) {
- // If the anchor node is a element instead of a text node then return this element
- if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1)
- return s.anchorNode.childNodes[s.anchorOffset];
-
- if (r.startContainer == r.endContainer) {
- if (r.startOffset - r.endOffset < 2) {
- if (r.startContainer.hasChildNodes())
- e = r.startContainer.childNodes[r.startOffset];
+ if (!rng.collapsed) {
+ if (rng.startContainer == rng.endContainer) {
+ if (rng.startOffset - rng.endOffset < 2) {
+ if (rng.startContainer.hasChildNodes())
+ elm = rng.startContainer.childNodes[rng.startOffset];
}
}
+
+ // If the anchor node is a element instead of a text node then return this element
+ if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
+ return sel.anchorNode.childNodes[sel.anchorOffset];
}
- return t.dom.getParent(e, '*');
+ if (elm && elm.nodeType == 3)
+ return elm.parentNode;
+
+ return elm;
}
- return r.item ? r.item(0) : r.parentElement();
+ return rng.item ? rng.item(0) : rng.parentElement();
},
getSelectedBlocks : function(st, en) {
@@ -5479,6 +5577,7 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
})(tinymce);
+
(function(tinymce) {
tinymce.create('tinymce.dom.XMLWriter', {
node : null,
@@ -5570,6 +5669,7 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
})(tinymce);
+
(function(tinymce) {
tinymce.create('tinymce.dom.StringWriter', {
str : null,
@@ -5696,6 +5796,7 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
})(tinymce);
+
(function(tinymce) {
// Shorten names
var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
@@ -5725,20 +5826,18 @@ window.tinymce.dom.Sizzle = Sizzle;
valid_nodes : 0,
node_filter : 0,
attr_filter : 0,
- invalid_attrs : /^(mce_|_moz_|sizset|sizcache)/,
+ invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
closed : /^(br|hr|input|meta|img|link|param|area)$/,
entity_encoding : 'named',
entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
valid_elements : '*[*]',
extended_valid_elements : 0,
- valid_child_elements : 0,
invalid_elements : 0,
fix_table_elements : 1,
fix_list_elements : true,
fix_content_duplication : true,
convert_fonts_to_spans : false,
font_size_classes : 0,
- font_size_style_values : 0,
apply_source_formatting : 0,
indent_mode : 'simple',
indent_char : '\t',
@@ -5749,6 +5848,11 @@ window.tinymce.dom.Sizzle = Sizzle;
}, s);
t.dom = s.dom;
+ t.schema = s.schema;
+
+ // Use raw entities if no entities are defined
+ if (s.entity_encoding == 'named' && !s.entities)
+ s.entity_encoding = 'raw';
if (s.remove_redundant_brs) {
t.onPostProcess.add(function(se, o) {
@@ -5832,7 +5936,7 @@ window.tinymce.dom.Sizzle = Sizzle;
},
setEntities : function(s) {
- var t = this, a, i, l = {}, re = '', v;
+ var t = this, a, i, l = {}, v;
// No need to setup more than once
if (t.entityLookup)
@@ -5850,94 +5954,11 @@ window.tinymce.dom.Sizzle = Sizzle;
l[String.fromCharCode(a[i])] = a[i + 1];
v = parseInt(a[i]).toString(16);
- re += '\\u' + '0000'.substring(v.length) + v;
- }
-
- if (!re) {
- t.settings.entity_encoding = 'raw';
- return;
}
- t.entitiesRE = new RegExp('[' + re + ']', 'g');
t.entityLookup = l;
},
- setValidChildRules : function(s) {
- this.childRules = null;
- this.addValidChildRules(s);
- },
-
- addValidChildRules : function(s) {
- var t = this, inst, intr, bloc;
-
- if (!s)
- return;
-
- inst = 'A|BR|SPAN|BDO|MAP|OBJECT|IMG|TT|I|B|BIG|SMALL|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|#text|#comment';
- intr = 'A|BR|SPAN|BDO|OBJECT|APPLET|IMG|MAP|IFRAME|TT|I|B|U|S|STRIKE|BIG|SMALL|FONT|BASEFONT|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|INPUT|SELECT|TEXTAREA|LABEL|BUTTON|#text|#comment';
- bloc = 'H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|FORM|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP';
-
- each(s.split(','), function(s) {
- var p = s.split(/\[|\]/), re;
-
- s = '';
- each(p[1].split('|'), function(v) {
- if (s)
- s += '|';
-
- switch (v) {
- case '%itrans':
- v = intr;
- break;
-
- case '%itrans_na':
- v = intr.substring(2);
- break;
-
- case '%istrict':
- v = inst;
- break;
-
- case '%istrict_na':
- v = inst.substring(2);
- break;
-
- case '%btrans':
- v = bloc;
- break;
-
- case '%bstrict':
- v = bloc;
- break;
- }
-
- s += v;
- });
- re = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
-
- each(p[0].split('/'), function(s) {
- t.childRules = t.childRules || {};
- t.childRules[s] = re;
- });
- });
-
- // Build regex
- s = '';
- each(t.childRules, function(v, k) {
- if (s)
- s += '|';
-
- s += k;
- });
-
- t.parentElementsRE = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
-
- /*console.debug(t.parentElementsRE.toString());
- each(t.childRules, function(v) {
- console.debug(v.toString());
- });*/
- },
-
setRules : function(s) {
var t = this;
@@ -6204,6 +6225,7 @@ window.tinymce.dom.Sizzle = Sizzle;
// Serialize HTML DOM into a string
t.writer.reset();
+ t._info = o;
t._serializeNode(n, o.getInner);
// Post process
@@ -6296,18 +6318,28 @@ window.tinymce.dom.Sizzle = Sizzle;
o.content = h;
},
- _serializeNode : function(n, inn) {
- var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed;
+ _serializeNode : function(n, inner) {
+ var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type;
if (!s.node_filter || s.node_filter(n)) {
switch (n.nodeType) {
case 1: // Element
- if (n.hasAttribute ? n.hasAttribute('mce_bogus') : n.getAttribute('mce_bogus'))
+ if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
return;
- iv = false;
+ iv = keep = false;
hc = n.hasChildNodes();
- nn = n.getAttribute('mce_name') || n.nodeName.toLowerCase();
+ nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
+
+ // Get internal type
+ type = n.getAttribute('_mce_type');
+ if (type) {
+ if (!t._info.cleanup) {
+ iv = true;
+ return;
+ } else
+ keep = 1;
+ }
// Add correct prefix on IE
if (isIE) {
@@ -6320,18 +6352,20 @@ window.tinymce.dom.Sizzle = Sizzle;
nn = nn.substring(4);
// Check if valid
- if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inn) {
- iv = true;
- break;
+ if (!keep) {
+ if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
+ iv = true;
+ break;
+ }
}
if (isIE) {
// Fix IE content duplication (DOM can have multiple copies of the same node)
if (s.fix_content_duplication) {
- if (n.mce_serialized == t.key)
+ if (n._mce_serialized == t.key)
return;
- n.mce_serialized = t.key;
+ n._mce_serialized = t.key;
}
// IE sometimes adds a / infront of the node name
@@ -6344,18 +6378,23 @@ window.tinymce.dom.Sizzle = Sizzle;
}
// Check if valid child
- if (t.childRules) {
- if (t.parentElementsRE.test(t.elementName)) {
- if (!t.childRules[t.elementName].test(nn)) {
- iv = true;
- break;
- }
+ if (s.validate_children) {
+ if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
+ iv = true;
+ break;
}
t.elementName = nn;
}
ru = t.findRule(nn);
+
+ // No valid rule for this element could be found then skip it
+ if (!ru) {
+ iv = true;
+ break;
+ }
+
nn = ru.name || nn;
closed = s.closed.test(nn);
@@ -6415,6 +6454,10 @@ window.tinymce.dom.Sizzle = Sizzle;
}
}
+ // Keep type attribute
+ if (type && keep)
+ w.writeAttribute('_mce_type', type);
+
// Write text from script
if (nn === 'script' && tinymce.trim(n.innerHTML)) {
w.writeText('// '); // Padd it with a comment so it will parse on older browsers
@@ -6427,7 +6470,7 @@ window.tinymce.dom.Sizzle = Sizzle;
if (ru.padd) {
// If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
- if (cn.hasAttribute ? cn.hasAttribute('mce_bogus') : cn.getAttribute('mce_bogus'))
+ if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
w.writeText('\u00a0');
} else if (!hc)
w.writeText('\u00a0'); // No children then padd it
@@ -6437,10 +6480,8 @@ window.tinymce.dom.Sizzle = Sizzle;
case 3: // Text
// Check if valid child
- if (t.childRules && t.parentElementsRE.test(t.elementName)) {
- if (!t.childRules[t.elementName].test(n.nodeName))
- return;
- }
+ if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
+ return;
return w.writeText(n.nodeValue);
@@ -6533,7 +6574,7 @@ window.tinymce.dom.Sizzle = Sizzle;
t.setEntities(s.entities);
l = t.entityLookup;
- h = h.replace(t.entitiesRE, function(a) {
+ h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
var v;
if (v = l[a])
@@ -6563,7 +6604,6 @@ window.tinymce.dom.Sizzle = Sizzle;
t.setRules(s.valid_elements);
t.addRules(s.extended_valid_elements);
- t.addValidChildRules(s.valid_child_elements);
if (s.invalid_elements)
t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
@@ -6630,281 +6670,548 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
})(tinymce);
+
(function(tinymce) {
- var each = tinymce.each, Event = tinymce.dom.Event;
+ tinymce.dom.ScriptLoader = function(settings) {
+ var QUEUED = 0,
+ LOADING = 1,
+ LOADED = 2,
+ states = {},
+ queue = [],
+ scriptLoadedCallbacks = {},
+ queueLoadedCallbacks = [],
+ loading = 0,
+ undefined;
+
+ function loadScript(url, callback) {
+ var t = this, dom = tinymce.DOM, elm, uri, loc, id;
+
+ // Execute callback when script is loaded
+ function done() {
+ dom.remove(id);
+
+ if (elm)
+ elm.onreadystatechange = elm.onload = elm = null;
+
+ callback();
+ };
- tinymce.create('tinymce.dom.ScriptLoader', {
- ScriptLoader : function(s) {
- this.settings = s || {};
- this.queue = [];
- this.lookup = {};
- },
+ id = dom.uniqueId();
- isDone : function(u) {
- return this.lookup[u] ? this.lookup[u].state == 2 : 0;
- },
+ if (tinymce.isIE6) {
+ uri = new tinymce.util.URI(url);
+ loc = location;
- markDone : function(u) {
- this.lookup[u] = {state : 2, url : u};
- },
+ // If script is from same domain and we
+ // use IE 6 then use XHR since it's more reliable
+ if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
+ tinymce.util.XHR.send({
+ url : tinymce._addVer(uri.getURI()),
+ success : function(content) {
+ // Create new temp script element
+ var script = dom.create('script', {
+ type : 'text/javascript'
+ });
- add : function(u, cb, s, pr) {
- var t = this, lo = t.lookup, o;
+ // Evaluate script in global scope
+ script.text = content;
+ document.getElementsByTagName('head')[0].appendChild(script);
+ dom.remove(script);
- if (o = lo[u]) {
- // Is loaded fire callback
- if (cb && o.state == 2)
- cb.call(s || this);
+ done();
+ }
+ });
- return o;
+ return;
+ }
}
- o = {state : 0, url : u, func : cb, scope : s || this};
+ // Create new script element
+ elm = dom.create('script', {
+ id : id,
+ type : 'text/javascript',
+ src : tinymce._addVer(url)
+ });
+
+ // Add onload and readystate listeners
+ elm.onload = done;
+ elm.onreadystatechange = function() {
+ var state = elm.readyState;
- if (pr)
- t.queue.unshift(o);
- else
- t.queue.push(o);
+ // Loaded state is passed on IE 6 however there
+ // are known issues with this method but we can't use
+ // XHR in a cross domain loading
+ if (state == 'complete' || state == 'loaded')
+ done();
+ };
- lo[u] = o;
+ // Most browsers support this feature so we report errors
+ // for those at least to help users track their missing plugins etc
+ // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
+ /*elm.onerror = function() {
+ alert('Failed to load: ' + url);
+ };*/
- return o;
- },
+ // Add script to document
+ (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
+ };
- load : function(u, cb, s) {
- var t = this, o;
+ this.isDone = function(url) {
+ return states[url] == LOADED;
+ };
- if (o = t.lookup[u]) {
- // Is loaded fire callback
- if (cb && o.state == 2)
- cb.call(s || t);
+ this.markDone = function(url) {
+ states[url] = LOADED;
+ };
- return o;
+ this.add = this.load = function(url, callback, scope) {
+ var item, state = states[url];
+
+ // Add url to load queue
+ if (state == undefined) {
+ queue.push(url);
+ states[url] = QUEUED;
}
- function loadScript(u) {
- if (Event.domLoaded || t.settings.strict_mode) {
- tinymce.util.XHR.send({
- url : tinymce._addVer(u),
- error : t.settings.error,
- async : false,
- success : function(co) {
- t.eval(co);
- }
- });
- } else
- document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"></script>');
- };
+ if (callback) {
+ // Store away callback for later execution
+ if (!scriptLoadedCallbacks[url])
+ scriptLoadedCallbacks[url] = [];
- if (!tinymce.is(u, 'string')) {
- each(u, function(u) {
- loadScript(u);
+ scriptLoadedCallbacks[url].push({
+ func : callback,
+ scope : scope || this
});
+ }
+ };
- if (cb)
- cb.call(s || t);
- } else {
- loadScript(u);
+ this.loadQueue = function(callback, scope) {
+ this.loadScripts(queue, callback, scope);
+ };
- if (cb)
- cb.call(s || t);
- }
- },
+ this.loadScripts = function(scripts, callback, scope) {
+ var loadScripts;
- loadQueue : function(cb, s) {
- var t = this;
+ function execScriptLoadedCallbacks(url) {
+ // Execute URL callback functions
+ tinymce.each(scriptLoadedCallbacks[url], function(callback) {
+ callback.func.call(callback.scope);
+ });
+
+ scriptLoadedCallbacks[url] = undefined;
+ };
- if (!t.queueLoading) {
- t.queueLoading = 1;
- t.queueCallbacks = [];
+ queueLoadedCallbacks.push({
+ func : callback,
+ scope : scope || this
+ });
- t.loadScripts(t.queue, function() {
- t.queueLoading = 0;
+ loadScripts = function() {
+ var loadingScripts = tinymce.grep(scripts);
- if (cb)
- cb.call(s || t);
+ // Current scripts has been handled
+ scripts.length = 0;
- each(t.queueCallbacks, function(o) {
- o.func.call(o.scope);
- });
+ // Load scripts that needs to be loaded
+ tinymce.each(loadingScripts, function(url) {
+ // Script is already loaded then execute script callbacks directly
+ if (states[url] == LOADED) {
+ execScriptLoadedCallbacks(url);
+ return;
+ }
+
+ // Is script not loading then start loading it
+ if (states[url] != LOADING) {
+ states[url] = LOADING;
+ loading++;
+
+ loadScript(url, function() {
+ states[url] = LOADED;
+ loading--;
+
+ execScriptLoadedCallbacks(url);
+
+ // Load more scripts if they where added by the recently loaded script
+ loadScripts();
+ });
+ }
});
- } else if (cb)
- t.queueCallbacks.push({func : cb, scope : s || t});
- },
- eval : function(co) {
- var w = window;
+ // No scripts are currently loading then execute all pending queue loaded callbacks
+ if (!loading) {
+ tinymce.each(queueLoadedCallbacks, function(callback) {
+ callback.func.call(callback.scope);
+ });
- // Evaluate script
- if (!w.execScript) {
- try {
- eval.call(w, co);
- } catch (ex) {
- eval(co, w); // Firefox 3.0a8
+ queueLoadedCallbacks.length = 0;
}
- } else
- w.execScript(co); // IE
- },
+ };
- loadScripts : function(sc, cb, s) {
- var t = this, lo = t.lookup;
+ loadScripts();
+ };
+ };
- function done(o) {
- o.state = 2; // Has been loaded
+ // Global script loader
+ tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
+})(tinymce);
- // Run callback
- if (o.func)
- o.func.call(o.scope || t);
- };
+tinymce.dom.TreeWalker = function(start_node, root_node) {
+ var node = start_node;
- function allDone() {
- var l;
+ function findSibling(node, start_name, sibling_name, shallow) {
+ var sibling, parent;
- // Check if all files are loaded
- l = sc.length;
- each(sc, function(o) {
- o = lo[o.url];
+ if (node) {
+ // Walk into nodes if it has a start
+ if (!shallow && node[start_name])
+ return node[start_name];
- if (o.state === 2) {// It has finished loading
- done(o);
- l--;
- } else
- load(o);
- });
+ // Return the sibling if it has one
+ if (node != root_node) {
+ sibling = node[sibling_name];
+ if (sibling)
+ return sibling;
- // They are all loaded
- if (l === 0 && cb) {
- cb.call(s || t);
- cb = 0;
+ // Walk up the parents to look for siblings
+ for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
+ sibling = parent[sibling_name];
+ if (sibling)
+ return sibling;
}
- };
+ }
+ }
+ };
- function load(o) {
- if (o.state > 0)
- return;
+ this.current = function() {
+ return node;
+ };
+
+ this.next = function(shallow) {
+ return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
+ };
+
+ this.prev = function(shallow) {
+ return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
+ };
+};
+
+(function() {
+ var transitional = {};
- o.state = 1; // Is loading
+ function unpack(lookup, data) {
+ var key;
- tinymce.dom.ScriptLoader.loadScript(o.url, function() {
- done(o);
- allDone();
+ function replace(value) {
+ return value.replace(/[A-Z]+/g, function(key) {
+ return replace(lookup[key]);
+ });
+ };
+
+ // Unpack lookup
+ for (key in lookup) {
+ if (lookup.hasOwnProperty(key))
+ lookup[key] = replace(lookup[key]);
+ }
+
+ // Unpack and parse data into object map
+ replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
+ var i, map = {};
+
+ children = children.split(/\|/);
+
+ for (i = children.length - 1; i >= 0; i--)
+ map[children[i]] = 1;
+
+ transitional[name] = map;
+ });
+ };
+
+ // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
+ // we will later include the attributes here and use it as a default for valid elements but it
+ // requires us to rewrite the serializer engine
+ unpack({
+ Z : '#|H|K|N|O|P',
+ Y : '#|X|form|R|Q',
+ X : 'p|T|div|U|W|isindex|fieldset|table',
+ W : 'pre|hr|blockquote|address|center|noframes',
+ 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'
+ }, 'script[]' +
+ 'style[]' +
+ 'object[#|param|X|form|a|H|K|N|O|Q]' +
+ 'param[]' +
+ 'p[S]' +
+ 'a[Z]' +
+ 'br[]' +
+ 'span[S]' +
+ 'bdo[S]' +
+ 'applet[#|param|X|form|a|H|K|N|O|Q]' +
+ 'h1[S]' +
+ 'img[]' +
+ 'map[X|form|Q|area]' +
+ 'h2[S]' +
+ 'iframe[#|X|form|a|H|K|N|O|Q]' +
+ 'h3[S]' +
+ 'tt[S]' +
+ 'i[S]' +
+ 'b[S]' +
+ 'u[S]' +
+ 's[S]' +
+ 'strike[S]' +
+ 'big[S]' +
+ 'small[S]' +
+ 'font[S]' +
+ 'basefont[]' +
+ 'em[S]' +
+ 'strong[S]' +
+ 'dfn[S]' +
+ 'code[S]' +
+ 'q[S]' +
+ 'samp[S]' +
+ 'kbd[S]' +
+ 'var[S]' +
+ 'cite[S]' +
+ 'abbr[S]' +
+ 'acronym[S]' +
+ 'sub[S]' +
+ 'sup[S]' +
+ 'input[]' +
+ 'select[optgroup|option]' +
+ 'optgroup[option]' +
+ 'option[]' +
+ 'textarea[]' +
+ 'label[S]' +
+ 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
+ 'h4[S]' +
+ 'ins[#|X|form|a|H|K|N|O|Q]' +
+ 'h5[S]' +
+ 'del[#|X|form|a|H|K|N|O|Q]' +
+ 'h6[S]' +
+ 'div[#|X|form|a|H|K|N|O|Q]' +
+ 'ul[li]' +
+ 'li[#|X|form|a|H|K|N|O|Q]' +
+ 'ol[li]' +
+ 'dl[dt|dd]' +
+ 'dt[S]' +
+ 'dd[#|X|form|a|H|K|N|O|Q]' +
+ 'menu[li]' +
+ 'dir[li]' +
+ 'pre[ZA]' +
+ 'hr[]' +
+ 'blockquote[#|X|form|a|H|K|N|O|Q]' +
+ 'address[S|p]' +
+ 'center[#|X|form|a|H|K|N|O|Q]' +
+ 'noframes[#|X|form|a|H|K|N|O|Q]' +
+ 'isindex[]' +
+ 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
+ 'legend[S]' +
+ 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
+ 'caption[S]' +
+ 'col[]' +
+ 'colgroup[col]' +
+ 'thead[tr]' +
+ 'tr[th|td]' +
+ 'th[#|X|form|a|H|K|N|O|Q]' +
+ 'form[#|X|a|H|K|N|O|Q]' +
+ 'noscript[#|X|form|a|H|K|N|O|Q]' +
+ 'td[#|X|form|a|H|K|N|O|Q]' +
+ 'tfoot[tr]' +
+ 'tbody[tr]' +
+ 'area[]' +
+ 'base[]' +
+ 'body[#|X|form|a|H|K|N|O|Q]'
+ );
+
+ tinymce.dom.Schema = function() {
+ var t = this, elements = transitional;
+
+ t.isValid = function(name, child_name) {
+ var element = elements[name];
+
+ return !!(element && (!child_name || element[child_name]));
+ };
+ };
+})();
+(function(tinymce) {
+ tinymce.dom.RangeUtils = function(dom) {
+ var INVISIBLE_CHAR = '\uFEFF';
+
+ this.walk = function(rng, callback) {
+ var startContainer = rng.startContainer,
+ startOffset = rng.startOffset,
+ endContainer = rng.endContainer,
+ endOffset = rng.endOffset,
+ ancestor, startPoint,
+ endPoint, node, parent, siblings, nodes;
+
+ // Handle table cell selection the table plugin enables
+ // you to fake select table cells and perform formatting actions on them
+ nodes = dom.select('td.mceSelected,th.mceSelected');
+ if (nodes.length > 0) {
+ tinymce.each(nodes, function(node) {
+ callback([node]);
});
- /*
- tinymce.util.XHR.send({
- url : o.url,
- error : t.settings.error,
- success : function(co) {
- t.eval(co);
- done(o);
- allDone();
+ return;
+ }
+
+ function collectSiblings(node, name, end_node) {
+ var siblings = [];
+
+ for (; node && node != end_node; node = node[name])
+ siblings.push(node);
+
+ return siblings;
+ };
+
+ function findEndPoint(node, root) {
+ do {
+ if (node.parentNode == root)
+ return node;
+
+ node = node.parentNode;
+ } while(node);
+ };
+
+ function walkBoundary(start_node, end_node, next) {
+ var siblingName = next ? 'nextSibling' : 'previousSibling';
+
+ for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
+ parent = node.parentNode;
+ siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
+
+ if (siblings.length) {
+ if (!next)
+ siblings.reverse();
+
+ callback(siblings);
}
- });
- */
+ }
};
- each(sc, function(o) {
- var u = o.url;
+ // If index based start position then resolve it
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
+ startContainer = startContainer.childNodes[startOffset];
- // Add to queue if needed
- if (!lo[u]) {
- lo[u] = o;
- t.queue.push(o);
- } else
- o = lo[u];
+ // If index based end position then resolve it
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
+ endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
- // Is already loading or has been loaded
- if (o.state > 0)
- return;
+ // Find common ancestor and end points
+ ancestor = dom.findCommonAncestor(startContainer, endContainer);
- if (!Event.domLoaded && !t.settings.strict_mode) {
- var ix, ol = '';
+ // Same container
+ if (startContainer == endContainer)
+ return callback([startContainer]);
- // Add onload events
- if (cb || o.func) {
- o.state = 1; // Is loading
+ // Process left side
+ for (node = startContainer; node; node = node.parentNode) {
+ if (node == endContainer)
+ return walkBoundary(startContainer, ancestor, true);
- ix = tinymce.dom.ScriptLoader._addOnLoad(function() {
- done(o);
- allDone();
- });
+ if (node == ancestor)
+ break;
+ }
- if (tinymce.isIE)
- ol = ' onreadystatechange="';
- else
- ol = ' onload="';
+ // Process right side
+ for (node = endContainer; node; node = node.parentNode) {
+ if (node == startContainer)
+ return walkBoundary(endContainer, ancestor);
- ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"';
- }
+ if (node == ancestor)
+ break;
+ }
- document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"' + ol + '></script>');
+ // Find start/end point
+ startPoint = findEndPoint(startContainer, ancestor) || startContainer;
+ endPoint = findEndPoint(endContainer, ancestor) || endContainer;
- if (!o.func)
- done(o);
- } else
- load(o);
- });
+ // Walk left leaf
+ walkBoundary(startContainer, startPoint, true);
- allDone();
- },
+ // Walk the middle from start to end point
+ siblings = collectSiblings(
+ startPoint == startContainer ? startPoint : startPoint.nextSibling,
+ 'nextSibling',
+ endPoint == endContainer ? endPoint.nextSibling : endPoint
+ );
- // Static methods
- 'static' : {
- _addOnLoad : function(f) {
- var t = this;
+ if (siblings.length)
+ callback(siblings);
- t._funcs = t._funcs || [];
- t._funcs.push(f);
+ // Walk right leaf
+ walkBoundary(endContainer, endPoint);
+ };
- return t._funcs.length - 1;
- },
+ /* this.split = function(rng) {
+ var startContainer = rng.startContainer,
+ startOffset = rng.startOffset,
+ endContainer = rng.endContainer,
+ endOffset = rng.endOffset;
- _onLoad : function(e, u, ix) {
- if (!tinymce.isIE || e.readyState == 'complete')
- this._funcs[ix].call(this);
- },
+ function splitText(node, offset) {
+ if (offset == node.nodeValue.length)
+ node.appendData(INVISIBLE_CHAR);
- loadScript : function(u, cb) {
- var id = tinymce.DOM.uniqueId(), e;
+ node = node.splitText(offset);
- function done() {
- Event.clear(id);
- tinymce.DOM.remove(id);
+ if (node.nodeValue === INVISIBLE_CHAR)
+ node.nodeValue = '';
- if (cb) {
- cb.call(document, u);
- cb = 0;
- }
- };
+ return node;
+ };
- if (tinymce.isIE) {
-/* Event.add(e, 'readystatechange', function(e) {
- if (e.target && e.target.readyState == 'complete')
- done();
- });*/
+ // Handle single text node
+ if (startContainer == endContainer) {
+ if (startContainer.nodeType == 3) {
+ if (startOffset != 0)
+ startContainer = endContainer = splitText(startContainer, startOffset);
- tinymce.util.XHR.send({
- url : tinymce._addVer(u),
- async : false,
- success : function(co) {
- window.execScript(co);
- done();
- }
- });
- } else {
- e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)});
- Event.add(e, 'load', done);
+ if (endOffset - startOffset != startContainer.nodeValue.length)
+ splitText(startContainer, endOffset - startOffset);
+ }
+ } else {
+ // Split startContainer text node if needed
+ if (startContainer.nodeType == 3 && startOffset != 0) {
+ startContainer = splitText(startContainer, startOffset);
+ startOffset = 0;
+ }
- // Check for head or body
- (document.getElementsByTagName('head')[0] || document.body).appendChild(e);
+ // Split endContainer text node if needed
+ if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
+ endContainer = splitText(endContainer, endOffset).previousSibling;
+ endOffset = endContainer.nodeValue.length;
}
}
- }
- });
- // Global script loader
- tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
+ return {
+ startContainer : startContainer,
+ startOffset : startOffset,
+ endContainer : endContainer,
+ endOffset : endOffset
+ };
+ };
+*/
+ };
})(tinymce);
+
(function(tinymce) {
// Shorten class names
var DOM = tinymce.DOM, is = tinymce.is;
@@ -7005,7 +7312,8 @@ window.tinymce.dom.Sizzle = Sizzle;
tinymce.dom.Event.clear(this.id);
}
});
-})(tinymce);tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
+})(tinymce);
+tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
Container : function(id, s) {
this.parent(id, s);
@@ -7026,6 +7334,7 @@ window.tinymce.dom.Sizzle = Sizzle;
}
});
+
tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
Separator : function(id, s) {
this.parent(id, s);
@@ -7036,6 +7345,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
}
});
+
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
@@ -7065,6 +7375,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
});
})(tinymce);
+
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
@@ -7162,7 +7473,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
return m;
}
});
-})(tinymce);(function(tinymce) {
+})(tinymce);
+(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
@@ -7489,7 +7801,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
DOM.addClass(ro, 'mceLast');
}
});
-})(tinymce);(function(tinymce) {
+})(tinymce);
+(function(tinymce) {
var DOM = tinymce.DOM;
tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
@@ -7522,6 +7835,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
});
})(tinymce);
+
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
@@ -7669,16 +7983,16 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
hideMenu : function(e) {
var t = this;
- // Prevent double toogles by canceling the mouse click event to the button
- if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
- return;
-
- if (!e || !DOM.getParent(e.target, '.mceMenu')) {
- DOM.removeClass(t.id, t.classPrefix + 'Selected');
- Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
+ if (t.menu && t.menu.isMenuVisible) {
+ // Prevent double toogles by canceling the mouse click event to the button
+ if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
+ return;
- if (t.menu)
+ if (!e || !DOM.getParent(e.target, '.mceMenu')) {
+ DOM.removeClass(t.id, t.classPrefix + 'Selected');
+ Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
t.menu.hideMenu();
+ }
}
},
@@ -7704,13 +8018,25 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
});
each(t.items, function(o) {
- o.id = DOM.uniqueId();
- o.onclick = function() {
- if (t.settings.onselect(o.value) !== false)
- t.select(o.value); // Must be runned after
- };
+ // No value then treat it as a title
+ if (o.value === undefined) {
+ m.add({
+ title : o.title,
+ 'class' : 'mceMenuItemTitle',
+ onclick : function() {
+ if (t.settings.onselect('') !== false)
+ t.select(''); // Must be runned after
+ }
+ });
+ } else {
+ o.id = DOM.uniqueId();
+ o.onclick = function() {
+ if (t.settings.onselect(o.value) !== false)
+ t.select(o.value); // Must be runned after
+ };
- m.add(o);
+ m.add(o);
+ }
});
t.onRenderMenu.dispatch(t, m);
@@ -7779,7 +8105,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
Event.clear(this.id + '_open');
}
});
-})(tinymce);(function(tinymce) {
+})(tinymce);
+(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
@@ -7907,7 +8234,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
t.onPostRender.dispatch(t, DOM.get(t.id));
}
});
-})(tinymce);(function(tinymce) {
+})(tinymce);
+(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
@@ -7996,6 +8324,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
});
})(tinymce);
+
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
@@ -8061,6 +8390,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
});
})(tinymce);
+
(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
@@ -8169,7 +8499,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
style : {
backgroundColor : '#' + c
},
- mce_color : '#' + c
+ _mce_color : '#' + c
});
});
@@ -8191,7 +8521,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
e = e.target;
- if (e.nodeName == 'A' && (c = e.getAttribute('mce_color')))
+ if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
t.setColor(c);
return Event.cancel(e); // Prevent IE auto save warning
@@ -8227,6 +8557,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
}
});
})(tinymce);
+
tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
renderHTML : function() {
var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
@@ -8289,6 +8620,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
}
});
+
(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
@@ -8304,16 +8636,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
requireLangPack : function(n) {
- var u, s = tinymce.EditorManager.settings;
-
- if (s && s.language) {
- u = this.urls[n] + '/langs/' + s.language + '.js';
+ var s = tinymce.settings;
- if (!tinymce.dom.Event.domLoaded && !s.strict_mode)
- tinymce.ScriptLoader.load(u);
- else
- tinymce.ScriptLoader.add(u);
- }
+ if (s && s.language)
+ tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
},
add : function(id, o) {
@@ -8345,39 +8671,44 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
(function(tinymce) {
// Shorten names
- var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode;
+ var each = tinymce.each, extend = tinymce.extend,
+ DOM = tinymce.DOM, Event = tinymce.dom.Event,
+ ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
+ explode = tinymce.explode,
+ Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
- tinymce.create('static tinymce.EditorManager', {
- editors : {},
+ // Setup some URLs where the editor API is located and where the document is
+ tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
+ if (!/[\/\\]$/.test(tinymce.documentBaseURL))
+ tinymce.documentBaseURL += '/';
- i18n : {},
-
- activeEditor : null,
+ tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
- preInit : function() {
- var t = this, lo = window.location;
+ tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
- // Setup some URLs where the editor API is located and where the document is
- tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
- if (!/[\/\\]$/.test(tinymce.documentBaseURL))
- tinymce.documentBaseURL += '/';
+ // Add before unload listener
+ // This was required since IE was leaking memory if you added and removed beforeunload listeners
+ // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
+ tinymce.onBeforeUnload = new Dispatcher(tinymce);
- tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
- tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL);
+ // Must be on window or IE will leak if the editor is placed in frame or iframe
+ Event.add(window, 'beforeunload', function(e) {
+ tinymce.onBeforeUnload.dispatch(tinymce, e);
+ });
- // Add before unload listener
- // This was required since IE was leaking memory if you added and removed beforeunload listeners
- // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
- t.onBeforeUnload = new tinymce.util.Dispatcher(t);
+ tinymce.onAddEditor = new Dispatcher(tinymce);
- // Must be on window or IE will leak if the editor is placed in frame or iframe
- Event.add(window, 'beforeunload', function(e) {
- t.onBeforeUnload.dispatch(t, e);
- });
- },
+ tinymce.onRemoveEditor = new Dispatcher(tinymce);
+
+ tinymce.EditorManager = extend(tinymce, {
+ editors : [],
+
+ i18n : {},
+
+ activeEditor : null,
init : function(s) {
- var t = this, pl, sl = tinymce.ScriptLoader, c, e, el = [], ed;
+ var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
function execCallback(se, n, s) {
var f = se[n];
@@ -8396,83 +8727,17 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
s = extend({
theme : "simple",
- language : "en",
- strict_loading_mode : document.contentType == 'application/xhtml+xml'
+ language : "en"
}, s);
t.settings = s;
- // If page not loaded and strict mode isn't enabled then load them
- if (!Event.domLoaded && !s.strict_loading_mode) {
- // Load language
- if (s.language)
- sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
-
- // Load theme
- if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
- ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
-
- // Load plugins
- if (s.plugins) {
- pl = explode(s.plugins);
-
- // Load rest if plugins
- each(pl, function(v) {
- if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) {
- // Skip safari plugin for other browsers
- if (!tinymce.isWebKit && v == 'safari')
- return;
-
- PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js');
- }
- });
- }
-
- sl.loadQueue();
- }
-
// Legacy call
Event.add(document, 'init', function() {
var l, co;
execCallback(s, 'onpageload');
- // Verify that it's a valid browser
- if (s.browsers) {
- l = false;
-
- each(explode(s.browsers), function(v) {
- switch (v) {
- case 'ie':
- case 'msie':
- if (tinymce.isIE)
- l = true;
- break;
-
- case 'gecko':
- if (tinymce.isGecko)
- l = true;
- break;
-
- case 'safari':
- case 'webkit':
- if (tinymce.isWebKit)
- l = true;
- break;
-
- case 'opera':
- if (tinymce.isOpera)
- l = true;
-
- break;
- }
- });
-
- // Not a valid one
- if (!l)
- return;
- }
-
switch (s.mode) {
case "exact":
l = s.elements || '';
@@ -8484,12 +8749,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
el.push(ed);
ed.render(1);
} else {
- c = 0;
-
each(document.forms, function(f) {
each(f.elements, function(e) {
if (e.name === v) {
- v = 'mce_editor_' + c;
+ v = 'mce_editor_' + instanceCounter++;
DOM.setAttrib(e, 'id', v);
ed = new tinymce.Editor(v, s);
@@ -8535,7 +8798,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
if (s.oninit) {
l = co = 0;
- each (el, function(ed) {
+ each(el, function(ed) {
co++;
if (!ed.initialized) {
@@ -8559,6 +8822,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
},
get : function(id) {
+ if (id === undefined)
+ return this.editors;
+
return this.editors[id];
},
@@ -8566,35 +8832,44 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
return this.get(id);
},
- add : function(e) {
- this.editors[e.id] = e;
- this._setActive(e);
+ add : function(editor) {
+ var self = this, editors = self.editors;
- return e;
+ // Add named and index editor instance
+ editors[editor.id] = editor;
+ editors.push(editor);
+
+ self._setActive(editor);
+ self.onAddEditor.dispatch(self, editor);
+
+
+ return editor;
},
- remove : function(e) {
- var t = this;
+ remove : function(editor) {
+ var t = this, i, editors = t.editors;
// Not in the collection
- if (!t.editors[e.id])
+ if (!editors[editor.id])
return null;
- delete t.editors[e.id];
-
- // Select another editor since the active one was removed
- if (t.activeEditor == e) {
- t._setActive(null);
+ delete editors[editor.id];
- each(t.editors, function(e) {
- t._setActive(e);
- return false; // Break
- });
+ for (i = 0; i < editors.length; i++) {
+ if (editors[i] == editor) {
+ editors.splice(i, 1);
+ break;
+ }
}
- e.destroy();
+ // Select another editor since the active one was removed
+ if (t.activeEditor == editor)
+ t._setActive(editors[0]);
+
+ editor.destroy();
+ t.onRemoveEditor.dispatch(t, editor);
- return e;
+ return editor;
},
execCommand : function(c, u, v) {
@@ -8707,20 +8982,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
// Private methods
- _setActive : function(e) {
- this.selectedInstance = this.activeEditor = e;
+ _setActive : function(editor) {
+ this.selectedInstance = this.activeEditor = editor;
}
});
-
- tinymce.EditorManager.preInit();
})(tinymce);
-var tinyMCE = window.tinyMCE = tinymce.EditorManager;
(function(tinymce) {
- var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher;
- var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit;
- var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager;
- var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
+ // Shorten these names
+ var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
+ Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
+ isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
+ ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
+ inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
tinymce.create('tinymce.Editor', {
Editor : function(id, s) {
@@ -8836,11 +9110,9 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
custom_undo_redo_keyboard_shortcuts : 1,
custom_undo_redo_restore_selection : 1,
custom_undo_redo : 1,
- doctype : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
+ doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
visual_table_class : 'mceItemTable',
visual : 1,
- inline_styles : true,
- convert_fonts_to_spans : true,
font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
apply_source_formatting : 1,
directionality : 'ltr',
@@ -8854,14 +9126,15 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
indentation : '30px',
keep_styles : 1,
fix_table_elements : 1,
- removeformat_selector : 'span,b,strong,em,i,font,u,strike'
+ inline_styles : 1,
+ convert_fonts_to_spans : true
}, s);
t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
base_uri : tinyMCE.baseURI
});
- t.baseURI = EditorManager.baseURI;
+ t.baseURI = tinymce.baseURI;
// Call setup
t.execCallback('setup', t);
@@ -8878,21 +9151,12 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
return;
}
- // Force strict loading mode if render us called by user and not internally
- if (!nst) {
- s.strict_loading_mode = 1;
- tinyMCE.settings = s;
- }
+ tinyMCE.settings = s;
// Element not found, then skip initialization
if (!t.getElement())
return;
- if (s.strict_loading_mode) {
- sl.settings.strict_mode = s.strict_loading_mode;
- tinymce.DOM.settings.strict = 1;
- }
-
// Add hidden input for non input elements inside form elements
if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
@@ -8942,7 +9206,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
n._mceOldSubmit = n.submit;
n.submit = function() {
// Save all instances
- EditorManager.triggerSave();
+ tinymce.triggerSave();
t.isNotDirty = 1;
return t.formElement._mceOldSubmit(t.formElement);
@@ -8963,8 +9227,8 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
each(explode(s.plugins), function(p) {
if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
- // Skip safari plugin for other browsers
- if (!isWebKit && p == 'safari')
+ // Skip safari plugin, since it is removed as of 3.3b1
+ if (p == 'safari')
return;
PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
@@ -8984,7 +9248,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
init : function() {
var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
- EditorManager.add(t);
+ tinymce.add(t);
if (s.theme) {
s.theme = s.theme.replace(/-/, '');
@@ -9022,23 +9286,15 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.controlManager = new tinymce.ControlManager(t);
- t.undoManager = new tinymce.UndoManager(t);
-
- // Pass through
- t.undoManager.onAdd.add(function(um, l) {
- if (!l.initial)
- return t.onChange.dispatch(t, l, um);
- });
-
- t.undoManager.onUndo.add(function(um, l) {
- return t.onUndo.dispatch(t, l, um);
- });
-
- t.undoManager.onRedo.add(function(um, l) {
- return t.onRedo.dispatch(t, l, um);
- });
-
if (s.custom_undo_redo) {
+ // Add initial undo level
+ t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
+ if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
+ if (!t.undoManager.hasUndo())
+ t.undoManager.add();
+ }
+ });
+
t.onExecCommand.add(function(ed, cmd, ui, val, a) {
if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
t.undoManager.add();
@@ -9113,7 +9369,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
if (s.document_base_url != tinymce.documentBaseURL)
t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
- t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
+ t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
if (tinymce.relaxedDomain)
t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
@@ -9202,19 +9458,107 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
hex_colors : s.force_hex_style_colors,
class_filter : s.class_filter,
update_styles : 1,
- fix_ie_paragraphs : 1
+ fix_ie_paragraphs : 1,
+ valid_styles : s.valid_styles
});
+ t.schema = new tinymce.dom.Schema();
+
t.serializer = new tinymce.dom.Serializer(extend(s, {
valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
- dom : t.dom
+ dom : t.dom,
+ schema : t.schema
}));
t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
+ t.formatter = new tinymce.Formatter(this);
+
+ // Register default formats
+ t.formatter.register({
+ alignleft : [
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
+ {selector : 'img,table', styles : {'float' : 'left'}}
+ ],
+
+ aligncenter : [
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
+ {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
+ {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
+ ],
+
+ alignright : [
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
+ {selector : 'img,table', styles : {'float' : 'right'}}
+ ],
+
+ alignfull : [
+ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
+ ],
+
+ bold : [
+ {inline : 'strong'},
+ {inline : 'span', styles : {fontWeight : 'bold'}},
+ {inline : 'b'}
+ ],
+
+ italic : [
+ {inline : 'em'},
+ {inline : 'span', styles : {fontStyle : 'italic'}},
+ {inline : 'i'}
+ ],
+
+ underline : [
+ {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
+ {inline : 'u'}
+ ],
+
+ strikethrough : [
+ {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
+ {inline : 'u'}
+ ],
+
+ forecolor : {inline : 'span', styles : {color : '%value'}},
+ hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
+ fontname : {inline : 'span', styles : {fontFamily : '%value'}},
+ fontsize : {inline : 'span', styles : {fontSize : '%value'}},
+ blockquote : {block : 'blockquote', wrapper : 1},
+
+ removeformat : [
+ {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
+ {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
+ {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
+ ]
+ });
+
+ // Register default block formats
+ each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
+ t.formatter.register(name, {block : name});
+ });
+
+ // Register user defined formats
+ t.formatter.register(t.settings.formats);
+
+ t.undoManager = new tinymce.UndoManager(t);
+
+ // Pass through
+ t.undoManager.onAdd.add(function(um, l) {
+ if (!l.initial)
+ return t.onChange.dispatch(t, l, um);
+ });
+
+ t.undoManager.onUndo.add(function(um, l) {
+ return t.onUndo.dispatch(t, l, um);
+ });
+
+ t.undoManager.onRedo.add(function(um, l) {
+ return t.onRedo.dispatch(t, l, um);
+ });
+
t.forceBlocks = new tinymce.ForceBlocks(t, {
forced_root_block : s.forced_root_block
});
+
t.editorCommands = new tinymce.EditorCommands(t);
// Pass through
@@ -9254,7 +9598,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
} else
n = 'div';
- o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' mce_name="$1"$2>');
+ o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
});
};
@@ -9337,12 +9681,6 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
};
}
- if (s.convert_fonts_to_spans)
- t._convertFonts();
-
- if (s.inline_styles)
- t._convertInlineElements();
-
if (s.cleanup_callback) {
t.onBeforeSetContent.add(function(ed, o) {
o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
@@ -9399,7 +9737,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
var pn = n.parentNode;
if (ed.dom.isBlock(pn) && pn.lastChild === n)
- ed.dom.add(pn, 'br', {'mce_bogus' : 1});
+ ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
});
};
@@ -9430,7 +9768,6 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
t.startContent = t.getContent({format : 'raw'});
- t.undoManager.add({initial : true});
t.initialized = true;
t.onInit.dispatch(t);
@@ -9449,7 +9786,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Handle auto focus
if (s.auto_focus) {
setTimeout(function () {
- var ed = EditorManager.get(s.auto_focus);
+ var ed = tinymce.get(s.auto_focus);
ed.selection.select(ed.getBody(), 1);
ed.selection.collapse(1);
@@ -9466,22 +9803,20 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
var oed, t = this, ce = t.settings.content_editable;
if (!sf) {
- // Is not content editable or the selection is outside the area in IE
- // the IE statement is needed to avoid bluring if element selections inside layers since
- // the layer is like it's own document in IE
- if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
+ // Is not content editable
+ if (!ce)
t.getWin().focus();
}
- if (EditorManager.activeEditor != t) {
- if ((oed = EditorManager.activeEditor) != null)
+ if (tinymce.activeEditor != t) {
+ if ((oed = tinymce.activeEditor) != null)
oed.onDeactivate.dispatch(oed, t);
t.onActivate.dispatch(t, oed);
}
- EditorManager._setActive(t);
+ tinymce._setActive(t);
},
execCallback : function(n) {
@@ -9508,7 +9843,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
},
translate : function(s) {
- var c = this.settings.language || 'en', i18n = EditorManager.i18n;
+ var c = this.settings.language || 'en', i18n = tinymce.i18n;
if (!s)
return '';
@@ -9519,7 +9854,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
},
getLang : function(n, dv) {
- return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
+ return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
},
getParam : function(n, dv, ty) {
@@ -9551,10 +9886,22 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
if (t.initialized) {
+ o = o || {};
+ n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
+
+ // Get parents and add them to object
+ o.parents = [];
+ t.dom.getParent(n, function(node) {
+ if (node.nodeName == 'BODY')
+ return true;
+
+ o.parents.push(node);
+ });
+
t.onNodeChange.dispatch(
t,
o ? o.controlManager || t.controlManager : t.controlManager,
- isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state
+ n,
s.isCollapsed(),
o
);
@@ -9697,7 +10044,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.onExecCommand.dispatch(t, cmd, ui, val, a);
},
- queryCommandState : function(c) {
+ queryCommandState : function(cmd) {
var t = this, o, s;
// Is hidden then return undefined
@@ -9705,7 +10052,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
return;
// Registred commands
- if (o = t.queryStateCommands[c]) {
+ if (o = t.queryStateCommands[cmd]) {
s = o.func.call(o.scope);
// Fall though on true
@@ -9714,13 +10061,13 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
}
// Registred commands
- o = t.editorCommands.queryCommandState(c);
+ o = t.editorCommands.queryCommandState(cmd);
if (o !== -1)
return o;
// Browser commands
try {
- return this.getDoc().queryCommandState(c);
+ return this.getDoc().queryCommandState(cmd);
} catch (ex) {
// Fails sometimes see bug: 1896577
}
@@ -9863,7 +10210,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
// It will also be impossible to place the caret in the editor unless there is a BR element present
if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
- o.content = t.dom.setHTML(t.getBody(), '<br mce_bogus="1" />');
+ o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
o.format = 'raw';
}
@@ -10032,7 +10379,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
t.onExecCommand.listeners = [];
- EditorManager.remove(t);
+ tinymce.remove(t);
DOM.remove(e);
},
@@ -10159,7 +10506,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
each(ed.dom.select('img'), function(e) {
var v;
- if (v = e.getAttribute('mce_src'))
+ if (v = e.getAttribute('_mce_src'))
e.src = t.documentBaseURI.toAbsolute(v);
})
});*/
@@ -10169,7 +10516,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
e = e.target;
- if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('mce_src')))
+ if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
e.src = t.documentBaseURI.toAbsolute(v);
});
}
@@ -10210,6 +10557,19 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.onMouseDown.add(setOpts);
}
+ // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
+ // WebKit can't even do simple things like selecting an image
+ // This also fixes so it's possible to select mceItemAnchors
+ if (tinymce.isWebKit) {
+ t.onClick.add(function(ed, e) {
+ e = e.target;
+
+ // Needs tobe the setBaseAndExtend or it will fail to select floated images
+ if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
+ t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
+ });
+ }
+
// Add node change handlers
t.onMouseUp.add(t.nodeChanged);
t.onClick.add(t.nodeChanged);
@@ -10241,7 +10601,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// BlockFormat shortcuts keys
for (i=1; i<=6; i++)
- t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, '<h' + i + '>']);
+ t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
@@ -10347,7 +10707,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
case 8:
// Fix IE control + backspace browser bug
if (t.selection.getRng().item) {
- t.selection.getRng().item(0).removeNode();
+ ed.dom.remove(t.selection.getRng().item(0));
return Event.cancel(e);
}
}
@@ -10377,43 +10737,21 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.undoManager.add();
};
- // Add undo level on editor blur
- if (tinymce.isIE) {
- t.dom.bind(t.getWin(), 'blur', function(e) {
- var n;
-
- // Check added for fullscreen bug
- if (t.selection) {
- n = t.selection.getNode();
-
- // Add undo level is selection was lost to another document
- if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc())
- addUndo();
- }
- });
- } else {
- t.dom.bind(t.getDoc(), 'blur', function() {
- if (t.selection && !t.removed)
- addUndo();
- });
- }
-
- t.onMouseDown.add(addUndo);
+ t.dom.bind(t.getDoc(), 'focusout', function(e) {
+ if (!t.removed && t.undoManager.typing)
+ addUndo();
+ });
t.onKeyUp.add(function(ed, e) {
- if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) {
- t.undoManager.typing = 0;
- t.undoManager.add();
- }
+ if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
+ addUndo();
});
t.onKeyDown.add(function(ed, e) {
// Is caracter positon keys
if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
- if (t.undoManager.typing) {
- t.undoManager.add();
- t.undoManager.typing = 0;
- }
+ if (t.undoManager.typing)
+ addUndo();
return;
}
@@ -10423,137 +10761,14 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
t.undoManager.typing = 1;
}
});
- }
- },
- _convertInlineElements : function() {
- var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp;
-
- function convert(ed, o) {
- if (!s.inline_styles)
- return;
-
- if (o.get) {
- each(t.dom.select('table,u,strike', o.node), function(n) {
- switch (n.nodeName) {
- case 'TABLE':
- if (v = dom.getAttrib(n, 'height')) {
- dom.setStyle(n, 'height', v);
- dom.setAttrib(n, 'height', '');
- }
- break;
-
- case 'U':
- case 'STRIKE':
- //sp = dom.create('span', {style : dom.getAttrib(n, 'style')});
- n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through';
- dom.setAttrib(n, 'mce_style', '');
- dom.setAttrib(n, 'mce_name', 'span');
- break;
- }
- });
- } else if (o.set) {
- each(t.dom.select('table,span', o.node).reverse(), function(n) {
- if (n.nodeName == 'TABLE') {
- if (v = dom.getStyle(n, 'height'))
- dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, ''));
- } else {
- // Convert spans to elements
- if (n.style.textDecoration == 'underline')
- na = 'u';
- else if (n.style.textDecoration == 'line-through')
- na = 'strike';
- else
- na = '';
-
- if (na) {
- n.style.textDecoration = '';
- dom.setAttrib(n, 'mce_style', '');
-
- e = dom.create(na, {
- style : dom.getAttrib(n, 'style')
- });
-
- dom.replace(e, n, 1);
- }
- }
- });
- }
- };
-
- t.onPreProcess.add(convert);
-
- if (!s.cleanup_on_startup) {
- t.onSetContent.add(function(ed, o) {
- if (o.initial)
- convert(t, {node : t.getBody(), set : 1});
+ t.onMouseDown.add(function() {
+ if (t.undoManager.typing)
+ addUndo();
});
}
},
- _convertFonts : function() {
- var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl;
-
- // No need
- if (!s.inline_styles)
- return;
-
- // Font pt values and font size names
- fz = [8, 10, 12, 14, 18, 24, 36];
- fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large'];
-
- if (sl = s.font_size_style_values)
- sl = explode(sl);
-
- if (cl = s.font_size_classes)
- cl = explode(cl);
-
- function process(no) {
- var n, sp, nl, x;
-
- // Keep unit tests happy
- if (!s.inline_styles)
- return;
-
- nl = t.dom.select('font', no);
- for (x = nl.length - 1; x >= 0; x--) {
- n = nl[x];
-
- sp = dom.create('span', {
- style : dom.getAttrib(n, 'style'),
- 'class' : dom.getAttrib(n, 'class')
- });
-
- dom.setStyles(sp, {
- fontFamily : dom.getAttrib(n, 'face'),
- color : dom.getAttrib(n, 'color'),
- backgroundColor : n.style.backgroundColor
- });
-
- if (n.size) {
- if (sl)
- dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]);
- else
- dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]);
- }
-
- dom.setAttrib(sp, 'mce_style', '');
- dom.replace(sp, n, 1);
- }
- };
-
- // Run on cleanup
- t.onPreProcess.add(function(ed, o) {
- if (o.get)
- process(o.node);
- });
-
- t.onSetContent.add(function(ed, o) {
- if (o.initial)
- process(o.node);
- });
- },
-
_isHidden : function() {
var s;
@@ -10617,1070 +10832,519 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
}
});
})(tinymce);
-(function(tinymce) {
- var each = tinymce.each, isIE = tinymce.isIE, isGecko = tinymce.isGecko, isOpera = tinymce.isOpera, isWebKit = tinymce.isWebKit;
-
- tinymce.create('tinymce.EditorCommands', {
- EditorCommands : function(ed) {
- this.editor = ed;
- },
-
- execCommand : function(cmd, ui, val) {
- var t = this, ed = t.editor, f;
- switch (cmd) {
- // Ignore these
- case 'mceResetDesignMode':
- case 'mceBeginUndoLevel':
- return true;
-
- // Ignore these
- case 'unlink':
- t.UnLink();
- return true;
-
- // Bundle these together
- case 'JustifyLeft':
- case 'JustifyCenter':
- case 'JustifyRight':
- case 'JustifyFull':
- t.mceJustify(cmd, cmd.substring(7).toLowerCase());
- return true;
-
- default:
- f = this[cmd];
-
- if (f) {
- f.call(this, ui, val);
- return true;
- }
- }
-
- return false;
- },
-
- Indent : function() {
- var ed = this.editor, d = ed.dom, s = ed.selection, e, iv, iu;
-
- // Setup indent level
- iv = ed.settings.indentation;
- iu = /[a-z%]+$/i.exec(iv);
- iv = parseInt(iv);
-
- if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
- each(s.getSelectedBlocks(), function(e) {
- d.setStyle(e, 'paddingLeft', (parseInt(e.style.paddingLeft || 0) + iv) + iu);
- });
-
- return;
- }
-
- ed.getDoc().execCommand('Indent', false, null);
-
- if (isIE) {
- d.getParent(s.getNode(), function(n) {
- if (n.nodeName == 'BLOCKQUOTE') {
- n.dir = n.style.cssText = '';
- }
- });
- }
- },
-
- Outdent : function() {
- var ed = this.editor, d = ed.dom, s = ed.selection, e, v, iv, iu;
-
- // Setup indent level
- iv = ed.settings.indentation;
- iu = /[a-z%]+$/i.exec(iv);
- iv = parseInt(iv);
-
- if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
- each(s.getSelectedBlocks(), function(e) {
- v = Math.max(0, parseInt(e.style.paddingLeft || 0) - iv);
- d.setStyle(e, 'paddingLeft', v ? v + iu : '');
- });
-
- return;
- }
-
- ed.getDoc().execCommand('Outdent', false, null);
- },
-
-/*
- mceSetAttribute : function(u, v) {
- var ed = this.editor, d = ed.dom, e;
-
- if (e = d.getParent(ed.selection.getNode(), d.isBlock))
- d.setAttrib(e, v.name, v.value);
- },
-*/
- mceSetContent : function(u, v) {
- this.editor.setContent(v);
- },
-
- mceToggleVisualAid : function() {
- var ed = this.editor;
-
- ed.hasVisual = !ed.hasVisual;
- ed.addVisual();
- },
-
- mceReplaceContent : function(u, v) {
- var s = this.editor.selection;
-
- s.setContent(v.replace(/\{\$selection\}/g, s.getContent({format : 'text'})));
- },
-
- mceInsertLink : function(u, v) {
- var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'a');
-
- if (tinymce.is(v, 'string'))
- v = {href : v};
-
- function set(e) {
- each(v, function(v, k) {
- ed.dom.setAttrib(e, k, v);
- });
- };
-
- if (!e) {
- ed.execCommand('CreateLink', false, 'javascript:mctmp(0);');
- each(ed.dom.select('a[href=javascript:mctmp(0);]'), function(e) {
- set(e);
- });
- } else {
- if (v.href)
- set(e);
- else
- ed.dom.remove(e, 1);
- }
- },
-
- UnLink : function() {
- var ed = this.editor, s = ed.selection;
-
- if (s.isCollapsed())
- s.select(s.getNode());
-
- ed.getDoc().execCommand('unlink', false, null);
- s.collapse(0);
- },
-
- FontName : function(u, v) {
- var t = this, ed = t.editor, s = ed.selection, e;
-
- if (!v) {
- if (s.isCollapsed())
- s.select(s.getNode());
- } else {
- if (ed.settings.convert_fonts_to_spans)
- t._applyInlineStyle('span', {style : {fontFamily : v}});
- else
- ed.getDoc().execCommand('FontName', false, v);
- }
- },
+(function(tinymce) {
+ // Added for compression purposes
+ var each = tinymce.each, undefined, TRUE = true, FALSE = false;
- FontSize : function(u, v) {
- var ed = this.editor, s = ed.settings, fc, fs;
+ tinymce.EditorCommands = function(editor) {
+ var dom = editor.dom,
+ selection = editor.selection,
+ commands = {state: {}, exec : {}, value : {}},
+ settings = editor.settings,
+ bookmark;
- // Use style options instead
- if (s.convert_fonts_to_spans && v >= 1 && v <= 7) {
- fs = tinymce.explode(s.font_size_style_values);
- fc = tinymce.explode(s.font_size_classes);
+ function execCommand(command, ui, value) {
+ var func;
- if (fc)
- v = fc[v - 1] || v;
- else
- v = fs[v - 1] || v;
+ command = command.toLowerCase();
+ if (func = commands.exec[command]) {
+ func(command, ui, value);
+ return TRUE;
}
- if (v >= 1 && v <= 7)
- ed.getDoc().execCommand('FontSize', false, v);
- else
- this._applyInlineStyle('span', {style : {fontSize : v}});
- },
-
- queryCommandValue : function(c) {
- var f = this['queryValue' + c];
-
- if (f)
- return f.call(this, c);
-
- return false;
- },
-
- queryCommandState : function(cmd) {
- var f;
+ return FALSE;
+ };
- switch (cmd) {
- // Bundle these together
- case 'JustifyLeft':
- case 'JustifyCenter':
- case 'JustifyRight':
- case 'JustifyFull':
- return this.queryStateJustify(cmd, cmd.substring(7).toLowerCase());
+ function queryCommandState(command) {
+ var func;
- default:
- if (f = this['queryState' + cmd])
- return f.call(this, cmd);
- }
+ command = command.toLowerCase();
+ if (func = commands.state[command])
+ return func(command);
return -1;
- },
+ };
- _queryState : function(c) {
- try {
- return this.editor.getDoc().queryCommandState(c);
- } catch (ex) {
- // Ignore exception
- }
- },
+ function queryCommandValue(command) {
+ var func;
- _queryVal : function(c) {
- try {
- return this.editor.getDoc().queryCommandValue(c);
- } catch (ex) {
- // Ignore exception
- }
- },
+ command = command.toLowerCase();
+ if (func = commands.value[command])
+ return func(command);
- queryValueFontSize : function() {
- var ed = this.editor, v = 0, p;
+ return FALSE;
+ };
- if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
- v = p.style.fontSize;
+ function addCommands(command_list, type) {
+ type = type || 'exec';
- if (!v && (isOpera || isWebKit)) {
- if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
- v = p.size;
+ each(command_list, function(callback, command) {
+ each(command.toLowerCase().split(','), function(command) {
+ commands[type][command] = callback;
+ });
+ });
+ };
- return v;
- }
+ // Expose public methods
+ tinymce.extend(this, {
+ execCommand : execCommand,
+ queryCommandState : queryCommandState,
+ queryCommandValue : queryCommandValue,
+ addCommands : addCommands
+ });
- return v || this._queryVal('FontSize');
- },
+ // Private methods
- queryValueFontName : function() {
- var ed = this.editor, v = 0, p;
+ function execNativeCommand(command, ui, value) {
+ if (ui === undefined)
+ ui = FALSE;
- if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
- v = p.face;
+ if (value === undefined)
+ value = null;
- if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
- v = p.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
+ return editor.getDoc().execCommand(command, ui, value);
+ };
- if (!v)
- v = this._queryVal('FontName');
+ function isFormatMatch(name) {
+ return editor.formatter.match(name);
+ };
- return v;
- },
+ function toggleFormat(name, value) {
+ editor.formatter.toggle(name, value ? {value : value} : undefined);
+ };
- mceJustify : function(c, v) {
- var ed = this.editor, se = ed.selection, n = se.getNode(), nn = n.nodeName, bl, nb, dom = ed.dom, rm;
+ function storeSelection(type) {
+ bookmark = selection.getBookmark(type);
+ };
- if (ed.settings.inline_styles && this.queryStateJustify(c, v))
- rm = 1;
+ function restoreSelection() {
+ selection.moveToBookmark(bookmark);
+ };
- bl = dom.getParent(n, ed.dom.isBlock);
+ // Add execCommand overrides
+ addCommands({
+ // Ignore these, added for compatibility
+ 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
- if (nn == 'IMG') {
- if (v == 'full')
- return;
+ // Add undo manager logic
+ 'mceEndUndoLevel,mceAddUndoLevel' : function() {
+ editor.undoManager.add();
+ },
- if (rm) {
- if (v == 'center')
- dom.setStyle(bl || n.parentNode, 'textAlign', '');
+ 'Cut,Copy,Paste' : function(command) {
+ var doc = editor.getDoc(), failed;
- dom.setStyle(n, 'float', '');
- this.mceRepaint();
- return;
+ // Try executing the native command
+ try {
+ execNativeCommand(command);
+ } catch (ex) {
+ // Command failed
+ failed = TRUE;
}
- if (v == 'center') {
- // Do not change table elements
- if (bl && /^(TD|TH)$/.test(bl.nodeName))
- bl = 0;
-
- if (!bl || bl.childNodes.length > 1) {
- nb = dom.create('p');
- nb.appendChild(n.cloneNode(false));
-
- if (bl)
- dom.insertAfter(nb, bl);
- else
- dom.insertAfter(nb, n);
-
- dom.remove(n);
- n = nb.firstChild;
- bl = nb;
- }
-
- dom.setStyle(bl, 'textAlign', v);
- dom.setStyle(n, 'float', '');
- } else {
- dom.setStyle(n, 'float', v);
- dom.setStyle(bl || n.parentNode, 'textAlign', '');
+ // Present alert message about clipboard access not being available
+ if (failed || !doc.queryCommandEnabled(command)) {
+ if (tinymce.isGecko) {
+ editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
+ if (state)
+ open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
+ });
+ } else
+ editor.windowManager.alert(editor.getLang('clipboard_no_support'));
}
+ },
- this.mceRepaint();
- return;
- }
-
- // Handle the alignment outselfs, less quirks in all browsers
- if (ed.settings.inline_styles && ed.settings.forced_root_block) {
- if (rm)
- v = '';
-
- each(se.getSelectedBlocks(dom.getParent(se.getStart(), dom.isBlock), dom.getParent(se.getEnd(), dom.isBlock)), function(e) {
- dom.setAttrib(e, 'align', '');
- dom.setStyle(e, 'textAlign', v == 'full' ? 'justify' : v);
- });
-
- return;
- } else if (!rm)
- ed.getDoc().execCommand(c, false, null);
-
- if (ed.settings.inline_styles) {
- if (rm) {
- dom.getParent(ed.selection.getNode(), function(n) {
- if (n.style && n.style.textAlign)
- dom.setStyle(n, 'textAlign', '');
- });
+ // Override unlink command
+ unlink : function(command) {
+ if (selection.isCollapsed())
+ selection.select(selection.getNode());
- return;
- }
-
- each(dom.select('*'), function(n) {
- var v = n.align;
+ execNativeCommand(command);
+ selection.collapse(FALSE);
+ },
- if (v) {
- if (v == 'full')
- v = 'justify';
+ // Override justify commands to use the text formatter engine
+ 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
+ var align = command.substring(7);
- dom.setStyle(n, 'textAlign', v);
- dom.setAttrib(n, 'align', '');
- }
+ // Remove all other alignments first
+ each('left,center,right,full'.split(','), function(name) {
+ if (align != name)
+ editor.formatter.remove('align' + name);
});
- }
- },
-
- mceSetCSSClass : function(u, v) {
- this.mceSetStyleInfo(0, {command : 'setattrib', name : 'class', value : v});
- },
-
- getSelectedElement : function() {
- var t = this, ed = t.editor, dom = ed.dom, se = ed.selection, r = se.getRng(), r1, r2, sc, ec, so, eo, e, sp, ep, re;
-
- if (se.isCollapsed() || r.item)
- return se.getNode();
-
- // Setup regexp
- re = ed.settings.merge_styles_invalid_parents;
- if (tinymce.is(re, 'string'))
- re = new RegExp(re, 'i');
-
- if (isIE) {
- r1 = r.duplicate();
- r1.collapse(true);
- sc = r1.parentElement();
- r2 = r.duplicate();
- r2.collapse(false);
- ec = r2.parentElement();
-
- if (sc != ec) {
- r1.move('character', 1);
- sc = r1.parentElement();
- }
+ toggleFormat('align' + align);
+ },
- if (sc == ec) {
- r1 = r.duplicate();
- r1.moveToElementText(sc);
+ // Override list commands to fix WebKit bug
+ 'InsertUnorderedList,InsertOrderedList' : function(command) {
+ var listElm, listParent;
- if (r1.compareEndPoints('StartToStart', r) == 0 && r1.compareEndPoints('EndToEnd', r) == 0)
- return re && re.test(sc.nodeName) ? null : sc;
- }
- } else {
- function getParent(n) {
- return dom.getParent(n, '*');
- };
+ execNativeCommand(command);
- sc = r.startContainer;
- ec = r.endContainer;
- so = r.startOffset;
- eo = r.endOffset;
+ // WebKit produces lists within block elements so we need to split them
+ // we will replace the native list creation logic to custom logic later on
+ // TODO: Remove this when the list creation logic is removed
+ listElm = dom.getParent(selection.getNode(), 'ol,ul');
+ if (listElm) {
+ listParent = listElm.parentNode;
- if (!r.collapsed) {
- if (sc == ec) {
- if (so - eo < 2) {
- if (sc.hasChildNodes()) {
- sp = sc.childNodes[so];
- return re && re.test(sp.nodeName) ? null : sp;
- }
- }
+ // If list is within a text block then split that block
+ if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
+ storeSelection();
+ dom.split(listParent, listElm);
+ restoreSelection();
}
}
+ },
- if (sc.nodeType != 3 || ec.nodeType != 3)
- return null;
-
- if (so == 0) {
- sp = getParent(sc);
-
- if (sp && sp.firstChild != sc)
- sp = null;
- }
+ // Override commands to use the text formatter engine
+ 'Bold,Italic,Underline,Strikethrough' : function(command) {
+ toggleFormat(command);
+ },
- if (so == sc.nodeValue.length) {
- e = sc.nextSibling;
+ // Override commands to use the text formatter engine
+ 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
+ toggleFormat(command, value);
+ },
- if (e && e.nodeType == 1)
- sp = sc.nextSibling;
- }
+ FontSize : function(command, ui, value) {
+ var fontClasses, fontSizes;
- if (eo == 0) {
- e = ec.previousSibling;
+ // Convert font size 1-7 to styles
+ if (value >= 1 && value <= 7) {
+ fontSizes = tinymce.explode(settings.font_size_style_values);
+ fontClasses = tinymce.explode(settings.font_size_classes);
- if (e && e.nodeType == 1)
- ep = e;
+ if (fontClasses)
+ value = fontClasses[value - 1] || value;
+ else
+ value = fontSizes[value - 1] || value;
}
- if (eo == ec.nodeValue.length) {
- ep = getParent(ec);
-
- if (ep && ep.lastChild != ec)
- ep = null;
- }
+ toggleFormat(command, value);
+ },
- // Same element
- if (sp == ep)
- return re && sp && re.test(sp.nodeName) ? null : sp;
- }
+ RemoveFormat : function(command) {
+ editor.formatter.remove(command);
+ },
- return null;
- },
+ mceBlockQuote : function(command) {
+ toggleFormat('blockquote');
+ },
- mceSetStyleInfo : function(u, v) {
- var t = this, ed = t.editor, d = ed.getDoc(), dom = ed.dom, e, b, s = ed.selection, nn = v.wrapper || 'span', b = s.getBookmark(), re;
+ FormatBlock : function(command, ui, value) {
+ return toggleFormat(value);
+ },
- function set(n, e) {
- if (n.nodeType == 1) {
- switch (v.command) {
- case 'setattrib':
- return dom.setAttrib(n, v.name, v.value);
+ mceCleanup : function() {
+ storeSelection();
+ editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
+ restoreSelection();
+ },
- case 'setstyle':
- return dom.setStyle(n, v.name, v.value);
+ mceRemoveNode : function(command, ui, value) {
+ var node = value || selection.getNode();
- case 'removeformat':
- return dom.setAttrib(n, 'class', '');
- }
+ // Make sure that the body node isn't removed
+ if (node != ed.getBody()) {
+ storeSelection();
+ editor.dom.remove(node, TRUE);
+ restoreSelection();
}
- };
-
- // Setup regexp
- re = ed.settings.merge_styles_invalid_parents;
- if (tinymce.is(re, 'string'))
- re = new RegExp(re, 'i');
-
- // Set style info on selected element
- if ((e = t.getSelectedElement()) && !ed.settings.force_span_wrappers)
- set(e, 1);
- else {
- // Generate wrappers and set styles on them
- d.execCommand('FontName', false, '__');
- each(dom.select('span,font'), function(n) {
- var sp, e;
-
- if (dom.getAttrib(n, 'face') == '__' || n.style.fontFamily === '__') {
- sp = dom.create(nn, {mce_new : '1'});
-
- set(sp);
+ },
- each (n.childNodes, function(n) {
- sp.appendChild(n.cloneNode(true));
- });
+ mceSelectNodeDepth : function(command, ui, value) {
+ var counter = 0;
- dom.replace(sp, n);
+ dom.getParent(selection.getNode(), function(node) {
+ if (node.nodeType == 1 && counter++ == value) {
+ selection.select(node);
+ return FALSE;
}
- });
- }
-
- // Remove wrappers inside new ones
- each(dom.select(nn).reverse(), function(n) {
- var p = n.parentNode;
-
- // Check if it's an old span in a new wrapper
- if (!dom.getAttrib(n, 'mce_new')) {
- // Find new wrapper
- p = dom.getParent(n, '*[mce_new]');
-
- if (p)
- dom.remove(n, 1);
- }
- });
-
- // Merge wrappers with parent wrappers
- each(dom.select(nn).reverse(), function(n) {
- var p = n.parentNode;
-
- if (!p || !dom.getAttrib(n, 'mce_new'))
- return;
-
- if (ed.settings.force_span_wrappers && p.nodeName != 'SPAN')
- return;
-
- // Has parent of the same type and only child
- if (p.nodeName == nn.toUpperCase() && p.childNodes.length == 1)
- return dom.remove(p, 1);
-
- // Has parent that is more suitable to have the class and only child
- if (n.nodeType == 1 && (!re || !re.test(p.nodeName)) && p.childNodes.length == 1) {
- set(p); // Set style info on parent instead
- dom.setAttrib(n, 'class', '');
- }
- });
-
- // Remove empty wrappers
- each(dom.select(nn).reverse(), function(n) {
- if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) {
- if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style'))
- return dom.remove(n, 1);
-
- dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker
- }
- });
-
- s.moveToBookmark(b);
- },
-
- queryStateJustify : function(c, v) {
- var ed = this.editor, n = ed.selection.getNode(), dom = ed.dom;
-
- if (n && n.nodeName == 'IMG') {
- if (dom.getStyle(n, 'float') == v)
- return 1;
+ }, editor.getBody());
+ },
- return n.parentNode.style.textAlign == v;
- }
+ mceSelectNode : function(command, ui, value) {
+ selection.select(value);
+ },
- n = dom.getParent(ed.selection.getStart(), function(n) {
- return n.nodeType == 1 && n.style.textAlign;
- });
+ mceInsertContent : function(command, ui, value) {
+ selection.setContent(value);
+ },
- if (v == 'full')
- v = 'justify';
+ mceInsertRawHTML : function(command, ui, value) {
+ selection.setContent('tiny_mce_marker');
+ editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
+ },
- if (ed.settings.inline_styles)
- return (n && n.style.textAlign == v);
+ mceSetContent : function(command, ui, value) {
+ editor.setContent(value);
+ },
- return this._queryState(c);
- },
+ 'Indent,Outdent' : function(command) {
+ var intentValue, indentUnit, value;
- ForeColor : function(ui, v) {
- var ed = this.editor;
+ // Setup indent level
+ intentValue = settings.indentation;
+ indentUnit = /[a-z%]+$/i.exec(intentValue);
+ intentValue = parseInt(intentValue);
- if (ed.settings.convert_fonts_to_spans) {
- this._applyInlineStyle('span', {style : {color : v}});
- return;
- } else
- ed.getDoc().execCommand('ForeColor', false, v);
- },
+ if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
+ each(selection.getSelectedBlocks(), function(element) {
+ if (command == 'outdent') {
+ value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
+ dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
+ } else
+ dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
+ });
+ } else
+ execNativeCommand(command);
+ },
- HiliteColor : function(ui, val) {
- var t = this, ed = t.editor, d = ed.getDoc();
+ mceRepaint : function() {
+ var bookmark;
- if (ed.settings.convert_fonts_to_spans) {
- this._applyInlineStyle('span', {style : {backgroundColor : val}});
- return;
- }
+ if (tinymce.isGecko) {
+ try {
+ storeSelection(TRUE);
- function set(s) {
- if (!isGecko)
- return;
+ if (selection.getSel())
+ selection.getSel().selectAllChildren(editor.getBody());
- try {
- // Try new Gecko method
- d.execCommand("styleWithCSS", 0, s);
- } catch (ex) {
- // Use old
- d.execCommand("useCSS", 0, !s);
+ selection.collapse(TRUE);
+ restoreSelection();
+ } catch (ex) {
+ // Ignore
+ }
}
- };
+ },
- if (isGecko || isOpera) {
- set(true);
- d.execCommand('hilitecolor', false, val);
- set(false);
- } else
- d.execCommand('BackColor', false, val);
- },
+ mceToggleFormat : function(command, ui, value) {
+ editor.formatter.toggle(value);
+ },
- FormatBlock : function(ui, val) {
- var t = this, ed = t.editor, s = ed.selection, dom = ed.dom, bl, nb, b;
+ InsertHorizontalRule : function() {
+ selection.setContent('<hr />');
+ },
- function isBlock(n) {
- return /^(P|DIV|H[1-6]|ADDRESS|BLOCKQUOTE|PRE)$/.test(n.nodeName);
- };
+ mceToggleVisualAid : function() {
+ editor.hasVisual = !editor.hasVisual;
+ editor.addVisual();
+ },
- bl = dom.getParent(s.getNode(), function(n) {
- return isBlock(n);
- });
+ mceReplaceContent : function(command, ui, value) {
+ selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
+ },
- // IE has an issue where it removes the parent div if you change format on the paragrah in <div><p>Content</p></div>
- // FF and Opera doesn't change parent DIV elements if you switch format
- if (bl) {
- if ((isIE && isBlock(bl.parentNode)) || bl.nodeName == 'DIV') {
- // Rename block element
- nb = ed.dom.create(val);
+ mceInsertLink : function(command, ui, value) {
+ var link = dom.getParent(selection.getNode(), 'a');
- each(dom.getAttribs(bl), function(v) {
- dom.setAttrib(nb, v.nodeName, dom.getAttrib(bl, v.nodeName));
- });
+ if (tinymce.is(value, 'string'))
+ value = {href : value};
- b = s.getBookmark();
- dom.replace(nb, bl, 1);
- s.moveToBookmark(b);
- ed.nodeChanged();
- return;
+ if (!link) {
+ execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
+ each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
+ dom.setAttribs(link, value);
+ });
+ } else {
+ if (value.href)
+ dom.setAttribs(link, value);
+ else
+ ed.dom.remove(link, TRUE);
}
}
+ });
- val = ed.settings.forced_root_block ? (val || '<p>') : val;
-
- if (val.indexOf('<') == -1)
- val = '<' + val + '>';
-
- if (tinymce.isGecko)
- val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1');
-
- ed.getDoc().execCommand('FormatBlock', false, val);
- },
-
- mceCleanup : function() {
- var ed = this.editor, s = ed.selection, b = s.getBookmark();
- ed.setContent(ed.getContent());
- s.moveToBookmark(b);
- },
+ // Add queryCommandState overrides
+ addCommands({
+ // Override justify commands
+ 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
+ return isFormatMatch('align' + command.substring(7));
+ },
- mceRemoveNode : function(ui, val) {
- var ed = this.editor, s = ed.selection, b, n = val || s.getNode();
+ 'Bold,Italic,Underline,Strikethrough' : function(command) {
+ return isFormatMatch(command);
+ },
- // Make sure that the body node isn't removed
- if (n == ed.getBody())
- return;
+ mceBlockQuote : function() {
+ return isFormatMatch('blockquote');
+ },
- b = s.getBookmark();
- ed.dom.remove(n, 1);
- s.moveToBookmark(b);
- ed.nodeChanged();
- },
+ Outdent : function() {
+ var node;
- mceSelectNodeDepth : function(ui, val) {
- var ed = this.editor, s = ed.selection, c = 0;
+ if (settings.inline_styles) {
+ if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
+ return TRUE;
- ed.dom.getParent(s.getNode(), function(n) {
- if (n.nodeType == 1 && c++ == val) {
- s.select(n);
- ed.nodeChanged();
- return false;
+ if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
+ return TRUE;
}
- }, ed.getBody());
- },
-
- mceSelectNode : function(u, v) {
- this.editor.selection.select(v);
- },
-
- mceInsertContent : function(ui, val) {
- this.editor.selection.setContent(val);
- },
-
- mceInsertRawHTML : function(ui, val) {
- var ed = this.editor;
-
- ed.selection.setContent('tiny_mce_marker');
- ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val));
- },
- mceRepaint : function() {
- var s, b, e = this.editor;
-
- if (tinymce.isGecko) {
- try {
- s = e.selection;
- b = s.getBookmark(true);
-
- if (s.getSel())
- s.getSel().selectAllChildren(e.getBody());
+ return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
+ },
- s.collapse(true);
- s.moveToBookmark(b);
- } catch (ex) {
- // Ignore
- }
+ 'InsertUnorderedList,InsertOrderedList' : function(command) {
+ return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
}
- },
+ }, 'state');
- queryStateUnderline : function() {
- var ed = this.editor, n = ed.selection.getNode();
+ // Add queryCommandValue overrides
+ addCommands({
+ 'FontSize,FontName' : function(command) {
+ var value = 0, parent;
- if (n && n.nodeName == 'A')
- return false;
-
- return this._queryState('Underline');
- },
-
- queryStateOutdent : function() {
- var ed = this.editor, n;
-
- if (ed.settings.inline_styles) {
- if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
- return true;
+ if (parent = dom.getParent(selection.getNode(), 'span')) {
+ if (command == 'fontsize')
+ value = parent.style.fontSize;
+ else
+ value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
+ }
- if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
- return true;
+ return value;
}
+ }, 'value');
- return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE'));
- },
-
- queryStateInsertUnorderedList : function() {
- return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL');
- },
-
- queryStateInsertOrderedList : function() {
- return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL');
- },
-
- queryStatemceBlockQuote : function() {
- return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';});
- },
-
- _applyInlineStyle : function(na, at, op) {
- var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found;
-
- na = na.toUpperCase();
-
- if (op && op.check_classes && at['class'])
- op.check_classes.push(at['class']);
-
- function removeEmpty() {
- each(dom.select(na).reverse(), function(n) {
- var c = 0;
-
- // Check if there is any attributes
- each(dom.getAttribs(n), function(an) {
- if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') {
- //console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName));
- c++;
- }
- });
-
- // No attributes then remove the element and keep the children
- if (c == 0)
- dom.remove(n, 1);
- });
- };
-
- function replaceFonts() {
- var bm;
-
- each(dom.select('span,font'), function(n) {
- if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') {
- if (!bm)
- bm = ed.selection.getBookmark();
-
- at._mce_new = '1';
- dom.replace(dom.create(na, at), n, 1);
- }
- });
-
- // Remove redundant elements
- each(dom.select(na + '[_mce_new]'), function(n) {
- function removeStyle(n) {
- if (n.nodeType == 1) {
- each(at.style, function(v, k) {
- dom.setStyle(n, k, '');
- });
-
- // Remove spans with the same class or marked classes
- if (at['class'] && n.className && op) {
- each(op.check_classes, function(c) {
- if (dom.hasClass(n, c))
- dom.removeClass(n, c);
- });
- }
- }
- };
-
- // Remove specified style information from child elements
- each(dom.select(na, n), removeStyle);
-
- // Remove the specified style information on parent if current node is only child (IE)
- if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1)
- removeStyle(n.parentNode);
-
- // Remove the child elements style info if a parent already has it
- dom.getParent(n.parentNode, function(pn) {
- if (pn.nodeType == 1) {
- if (at.style) {
- each(at.style, function(v, k) {
- var sv;
-
- if (!lo[k] && (sv = dom.getStyle(pn, k))) {
- if (sv === v)
- dom.setStyle(n, k, '');
-
- lo[k] = 1;
- }
- });
- }
-
- // Remove spans with the same class or marked classes
- if (at['class'] && pn.className && op) {
- each(op.check_classes, function(c) {
- if (dom.hasClass(pn, c))
- dom.removeClass(n, c);
- });
- }
- }
-
- return false;
- });
+ // Add undo manager logic
+ if (settings.custom_undo_redo) {
+ addCommands({
+ Undo : function() {
+ editor.undoManager.undo();
+ },
- n.removeAttribute('_mce_new');
- });
-
- removeEmpty();
- ed.selection.moveToBookmark(bm);
-
- return !!bm;
- };
-
- // Create inline elements
- ed.focus();
- ed.getDoc().execCommand('FontName', false, 'mceinline');
- replaceFonts();
-
- if (kh = t._applyInlineStyle.keyhandler) {
- ed.onKeyUp.remove(kh);
- ed.onKeyPress.remove(kh);
- ed.onKeyDown.remove(kh);
- ed.onSetContent.remove(t._applyInlineStyle.chandler);
- }
-
- if (ed.selection.isCollapsed()) {
- // IE will format the current word so this code can't be executed on that browser
- if (!isIE) {
- each(dom.getParents(ed.selection.getNode(), 'span'), function(n) {
- each(at.style, function(v, k) {
- var kv;
-
- if (kv = dom.getStyle(n, k)) {
- if (kv == v) {
- dom.setStyle(n, k, '');
- found = 2;
- return false;
- }
+ Redo : function() {
+ editor.undoManager.redo();
+ }
+ });
+ }
+ };
+})(tinymce);
+(function(tinymce) {
+ var Dispatcher = tinymce.util.Dispatcher;
- found = 1;
- return false;
- }
- });
+ tinymce.UndoManager = function(editor) {
+ var self, index = 0, data = [];
- if (found)
- return false;
- });
+ function getContent() {
+ return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
+ };
- if (found == 2) {
- bm = ed.selection.getBookmark();
+ return self = {
+ typing : 0,
- removeEmpty();
+ onAdd : new Dispatcher(self),
+ onUndo : new Dispatcher(self),
+ onRedo : new Dispatcher(self),
- ed.selection.moveToBookmark(bm);
+ add : function(level) {
+ var i, settings = editor.settings, lastLevel;
- // Node change needs to be detached since the onselect event
- // for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix!
- window.setTimeout(function() {
- ed.nodeChanged();
- }, 1);
+ level = level || {};
+ level.content = getContent();
- return;
- }
+ // Add undo level if needed
+ lastLevel = data[index];
+ if (lastLevel && lastLevel.content == level.content) {
+ if (index > 0 || data.length == 1)
+ return null;
}
- // Start collecting styles
- t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style);
-
- t._applyInlineStyle.chandler = ed.onSetContent.add(function() {
- delete t._pendingStyles;
- });
+ // Time to compress
+ if (settings.custom_undo_redo_levels) {
+ if (data.length > settings.custom_undo_redo_levels) {
+ for (i = 0; i < data.length - 1; i++)
+ data[i] = data[i + 1];
- t._applyInlineStyle.keyhandler = kh = function(e) {
- // Use pending styles
- if (t._pendingStyles) {
- at.style = t._pendingStyles;
- delete t._pendingStyles;
+ data.length--;
+ index = data.length;
}
+ }
- if (replaceFonts()) {
- ed.onKeyDown.remove(t._applyInlineStyle.keyhandler);
- ed.onKeyPress.remove(t._applyInlineStyle.keyhandler);
- }
-
- if (e.type == 'keyup')
- ed.onKeyUp.remove(t._applyInlineStyle.keyhandler);
- };
-
- ed.onKeyDown.add(kh);
- ed.onKeyPress.add(kh);
- ed.onKeyUp.add(kh);
- } else
- t._pendingStyles = 0;
- }
- });
-})(tinymce);(function(tinymce) {
- tinymce.create('tinymce.UndoManager', {
- index : 0,
- data : null,
- typing : 0,
-
- UndoManager : function(ed) {
- var t = this, Dispatcher = tinymce.util.Dispatcher;
-
- t.editor = ed;
- t.data = [];
- t.onAdd = new Dispatcher(this);
- t.onUndo = new Dispatcher(this);
- t.onRedo = new Dispatcher(this);
- },
-
- add : function(l) {
- var t = this, i, ed = t.editor, b, s = ed.settings, la;
-
- l = l || {};
- l.content = l.content || ed.getContent({format : 'raw', no_events : 1});
-
- // Add undo level if needed
- l.content = l.content.replace(/^\s*|\s*$/g, '');
- la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index];
- if (!l.initial && la && l.content == la.content)
- return null;
-
- // Time to compress
- if (s.custom_undo_redo_levels) {
- if (t.data.length > s.custom_undo_redo_levels) {
- for (i = 0; i < t.data.length - 1; i++)
- t.data[i] = t.data[i + 1];
+ // Get a non intrusive normalized bookmark
+ level.bookmark = editor.selection.getBookmark(2, true);
- t.data.length--;
- t.index = t.data.length;
+ // Crop array if needed
+ if (index < data.length - 1) {
+ // Treat first level as initial
+ if (index == 0)
+ data = [];
+ else
+ data.length = index + 1;
}
- }
-
- if (s.custom_undo_redo_restore_selection && !l.initial)
- l.bookmark = b = l.bookmark || ed.selection.getBookmark();
- if (t.index < t.data.length)
- t.index++;
+ data.push(level);
+ index = data.length - 1;
- // Only initial marked undo levels should be allowed as first item
- // This to workaround a bug with Firefox and the blur event
- if (t.data.length === 0 && !l.initial)
- return null;
+ self.onAdd.dispatch(self, level);
+ editor.isNotDirty = 0;
- // Add level
- t.data.length = t.index + 1;
- t.data[t.index++] = l;
+ return level;
+ },
- if (l.initial)
- t.index = 0;
+ undo : function() {
+ var level, i;
- // Set initial bookmark use first real undo level
- if (t.data.length == 2 && t.data[0].initial)
- t.data[0].bookmark = b;
+ if (self.typing) {
+ self.add();
+ self.typing = 0;
+ }
- t.onAdd.dispatch(t, l);
- ed.isNotDirty = 0;
+ if (index > 0) {
+ level = data[--index];
- //console.dir(t.data);
+ editor.setContent(level.content, {format : 'raw'});
+ editor.selection.moveToBookmark(level.bookmark);
- return l;
- },
+ self.onUndo.dispatch(self, level);
+ }
- undo : function() {
- var t = this, ed = t.editor, l = l, i;
+ return level;
+ },
- if (t.typing) {
- t.add();
- t.typing = 0;
- }
+ redo : function() {
+ var level;
- if (t.index > 0) {
- // If undo on last index then take snapshot
- if (t.index == t.data.length && t.index > 1) {
- i = t.index;
- t.typing = 0;
+ if (index < data.length - 1) {
+ level = data[++index];
- if (!t.add())
- t.index = i;
+ editor.setContent(level.content, {format : 'raw'});
+ editor.selection.moveToBookmark(level.bookmark);
- --t.index;
+ self.onRedo.dispatch(self, level);
}
- l = t.data[--t.index];
- ed.setContent(l.content, {format : 'raw'});
- ed.selection.moveToBookmark(l.bookmark);
-
- t.onUndo.dispatch(t, l);
- }
-
- return l;
- },
+ return level;
+ },
- redo : function() {
- var t = this, ed = t.editor, l = null;
+ clear : function() {
+ data = [];
+ index = self.typing = 0;
+ },
- if (t.index < t.data.length - 1) {
- l = t.data[++t.index];
- ed.setContent(l.content, {format : 'raw'});
- ed.selection.moveToBookmark(l.bookmark);
+ hasUndo : function() {
+ return index > 0 || self.typing;
+ },
- t.onRedo.dispatch(t, l);
+ hasRedo : function() {
+ return index < data.length - 1;
}
-
- return l;
- },
-
- clear : function() {
- var t = this;
-
- t.data = [];
- t.index = 0;
- t.typing = 0;
- t.add({initial : true});
- },
-
- hasUndo : function() {
- return this.index != 0 || this.typing;
- },
-
- hasRedo : function() {
- return this.index < this.data.length - 1;
- }
- });
+ };
+ };
})(tinymce);
+
(function(tinymce) {
// Shorten names
- var Event, isIE, isGecko, isOpera, each, extend;
-
- Event = tinymce.dom.Event;
- isIE = tinymce.isIE;
- isGecko = tinymce.isGecko;
- isOpera = tinymce.isOpera;
- each = tinymce.each;
- extend = tinymce.extend;
+ var Event = tinymce.dom.Event,
+ isIE = tinymce.isIE,
+ isGecko = tinymce.isGecko,
+ isOpera = tinymce.isOpera,
+ each = tinymce.each,
+ extend = tinymce.extend,
+ TRUE = true,
+ FALSE = false;
// Checks if the selection/caret is at the end of the specified block element
function isAtEnd(rng, par) {
@@ -11699,7 +11363,26 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
n = n.replace(/<[^>]+>/g, ''); // Remove all tags
- return n.replace(/[ \t\r\n]+/g, '') == '';
+ return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
+ };
+
+ function splitList(selection, dom, li) {
+ var listBlock, block;
+
+ if (isEmpty(li)) {
+ listBlock = dom.getParent(li, 'ul,ol');
+
+ if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
+ dom.split(listBlock, li);
+ block = dom.create('p', 0, '<br _mce_bogus="1" />');
+ dom.replace(block, li);
+ selection.select(block, 1);
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
};
tinymce.create('tinymce.ForceBlocks', {
@@ -11744,10 +11427,11 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
},
setup : function() {
- var t = this, ed = t.editor, s = ed.settings;
+ var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
// Force root blocks when typing and when getting output
if (s.forced_root_block) {
+ ed.onBeforeExecCommand.add(t.forceRoots, t);
ed.onKeyUp.add(t.forceRoots, t);
ed.onPreProcess.add(t.forceRoots, t);
}
@@ -11756,39 +11440,24 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Force IE to produce BRs on enter
if (isIE) {
ed.onKeyPress.add(function(ed, e) {
- var n, s = ed.selection;
+ var n;
- if (e.keyCode == 13 && s.getNode().nodeName != 'LI') {
- s.setContent('<br id="__" /> ', {format : 'raw'});
- n = ed.dom.get('__');
+ if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
+ selection.setContent('<br id="__" /> ', {format : 'raw'});
+ n = dom.get('__');
n.removeAttribute('id');
- s.select(n);
- s.collapse();
+ selection.select(n);
+ selection.collapse();
return Event.cancel(e);
}
});
}
-
- return;
}
if (!isIE && s.force_p_newlines) {
-/* ed.onPreProcess.add(function(ed, o) {
- each(ed.dom.select('br', o.node), function(n) {
- var p = n.parentNode;
-
- // Replace <p><br /></p> with <p>&nbsp;</p>
- if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) {
- p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n);
- }
- });
- });*/
-
ed.onKeyPress.add(function(ed, e) {
- if (e.keyCode == 13 && !e.shiftKey) {
- if (!t.insertPara(e))
- Event.cancel(e);
- }
+ if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
+ Event.cancel(e);
});
if (isGecko) {
@@ -11799,32 +11468,53 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
}
}
- function ren(rn, na) {
- var ne = ed.dom.create(na);
+ // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
+ if (tinymce.isWebKit) {
+ function insertBr(ed) {
+ var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
- each(rn.attributes, function(a) {
- if (a.specified && a.nodeValue)
- ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue);
- });
+ // Insert BR element
+ rng.insertNode(br = dom.create('br'));
- each(rn.childNodes, function(n) {
- ne.appendChild(n.cloneNode(true));
- });
+ // Place caret after BR
+ rng.setStartAfter(br);
+ rng.setEndAfter(br);
+ selection.setRng(rng);
- rn.parentNode.replaceChild(ne, rn);
+ // Could not place caret after BR then insert an nbsp entity and move the caret
+ if (selection.getSel().focusNode == br.previousSibling) {
+ selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
+ selection.collapse(TRUE);
+ }
- return ne;
- };
+ // Create a temporary DIV after the BR and get the position as it
+ // seems like getPos() returns 0 for text nodes and BR elements.
+ dom.insertAfter(div, br);
+ divYPos = dom.getPos(div).y;
+ dom.remove(div);
+
+ // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
+ if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
+ ed.getWin().scrollTo(0, divYPos);
+ };
+
+ ed.onKeyPress.add(function(ed, e) {
+ if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) {
+ insertBr(ed);
+ Event.cancel(e);
+ }
+ });
+ }
// Padd empty inline elements within block elements
// For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
ed.onPreProcess.add(function(ed, o) {
- each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
+ each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
if (isEmpty(p)) {
- each(ed.dom.select('span,em,strong,b,i', o.node), function(n) {
+ each(dom.select('span,em,strong,b,i', o.node), function(n) {
if (!n.hasChildNodes()) {
n.appendChild(ed.getDoc().createTextNode('\u00a0'));
- return false; // Break the loop one padding is enough
+ return FALSE; // Break the loop one padding is enough
}
});
}
@@ -11836,22 +11526,22 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Replaces IE:s auto generated paragraphs with the specified element name
if (s.element != 'P') {
ed.onKeyPress.add(function(ed, e) {
- t.lastElm = ed.selection.getNode().nodeName;
+ t.lastElm = selection.getNode().nodeName;
});
ed.onKeyUp.add(function(ed, e) {
- var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody();
+ var bl, n = selection.getNode(), b = ed.getBody();
if (b.childNodes.length === 1 && n.nodeName == 'P') {
- n = ren(n, s.element);
- sel.select(n);
- sel.collapse();
+ n = dom.rename(n, s.element);
+ selection.select(n);
+ selection.collapse();
ed.nodeChanged();
} else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
- bl = ed.dom.getParent(n, 'p');
+ bl = dom.getParent(n, 'p');
if (bl) {
- ren(bl, s.element);
+ dom.rename(bl, s.element);
ed.nodeChanged();
}
}
@@ -11861,7 +11551,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
},
find : function(n, t, s) {
- var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, false), c = -1;
+ var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
while (n = w.nextNode()) {
c++;
@@ -11884,12 +11574,18 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// Fix for bug #1863847
//if (e && e.keyCode == 13)
- // return true;
+ // return TRUE;
// Wrap non blocks into blocks
for (i = nl.length - 1; i >= 0; i--) {
nx = nl[i];
+ // Ignore internal elements
+ if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
+ bl = null;
+ continue;
+ }
+
// Is text or non block element
if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
if (!bl) {
@@ -12009,21 +11705,21 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
// If root blocks are forced then use Operas default behavior since it's really good
// Removed due to bug: #1853816
// if (se.forced_root_block && isOpera)
-// return true;
+// return TRUE;
// Setup before range
rb = d.createRange();
// If is before the first block element and in body, then move it into first block element
rb.setStart(s.anchorNode, s.anchorOffset);
- rb.collapse(true);
+ rb.collapse(TRUE);
// Setup after range
ra = d.createRange();
// If is before the first block element and in body, then move it into first block element
ra.setStart(s.focusNode, s.focusOffset);
- ra.collapse(true);
+ ra.collapse(TRUE);
// Setup start/end points
dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
@@ -12054,7 +11750,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
r.collapse(1);
ed.selection.setRng(r);
- return false;
+ return FALSE;
}
// If the caret is in an invalid location in FF we need to move it into the first block
@@ -12079,8 +11775,12 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
bn = sb ? sb.nodeName : se.element; // Get block name to create
// Return inside list use default browser behavior
- if (t.dom.getParent(sb, 'ol,ul,pre'))
- return true;
+ if (n = t.dom.getParent(sb, 'li,pre')) {
+ if (n.nodeName == 'LI')
+ return splitList(ed.selection, t.dom, n);
+
+ return TRUE;
+ }
// If caption or absolute layers then always generate new blocks within
if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
@@ -12192,7 +11892,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
do {
// We only want style specific elements
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
- nn = n.cloneNode(false);
+ nn = n.cloneNode(FALSE);
dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
nl.push(nn);
}
@@ -12229,7 +11929,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
bef.normalize();
function first(n) {
- return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false).nextNode() || n;
+ return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
};
// Move cursor and scroll into view
@@ -12249,51 +11949,12 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
//console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
}
- return false;
+ return FALSE;
},
backspaceDelete : function(e, bs) {
var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn;
- /*
- var par, rng, nextBlock;
-
- // Delete key will not merge paragraphs on Gecko so we need to do this manually
- // Hitting the delete key at the following caret position doesn't merge the elements <p>A|</p><p>B</p>
- // This logic will merge them into this: <p>A|B</p>
- if (e.keyCode == 46) {
- if (r.collapsed) {
- par = dom.getParent(sc, 'p,h1,h2,h3,h4,h5,h6,div');
-
- if (par) {
- rng = dom.createRng();
-
- rng.setStart(sc, r.startOffset);
- rng.setEndAfter(par);
-
- // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
- if (dom.getOuterHTML(rng.cloneContents()).replace(/<[^>]+>/g, '').length == 0) {
- nextBlock = dom.getNext(par, 'p,h1,h2,h3,h4,h5,h6,div');
-
- // Copy all children from next sibling block and remove it
- if (nextBlock) {
- each(nextBlock.childNodes, function(node) {
- par.appendChild(node.cloneNode(true));
- });
-
- dom.remove(nextBlock);
- }
-
- // Block the default even since the Gecko team might eventually fix this
- // We will remove this logic once they do we can't feature detect this one
- e.preventDefault();
- return;
- }
- }
- }
- }
- */
-
// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
// This workaround removes the element by hand and moves the caret to the previous element
if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
@@ -12305,7 +11966,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
if (n) {
if (sc != b.firstChild) {
// Find last text node
- w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+ w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
while (tn = w.nextNode())
n = tn;
@@ -12357,6 +12018,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
}
});
})(tinymce);
+
(function(tinymce) {
// Shorten names
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
@@ -12459,7 +12121,8 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
if (!s.onclick) {
s.onclick = function(v) {
- ed.execCommand(s.cmd, s.ui || false, s.value);
+ if (s.cmd)
+ ed.execCommand(s.cmd, s.ui || false, s.value);
};
}
});
@@ -12717,6 +12380,7 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
}
});
})(tinymce);
+
(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
@@ -12821,14 +12485,19 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
cb.call(s || t);
},
+ resizeBy : function(dw, dh, win) {
+ win.resizeBy(dw, dh);
+ },
+
// Internal functions
_decode : function(s) {
return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
}
});
-}(tinymce));(function(tinymce) {
- tinymce.CommandManager = function() {
+}(tinymce));
+(function(tinymce) {
+ function CommandManager() {
var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
function add(collection, cmd, func, scope) {
@@ -12872,360 +12541,1268 @@ var tinyMCE = window.tinyMCE = tinymce.EditorManager;
});
};
- tinymce.GlobalCommands = new tinymce.CommandManager();
-})(tinymce);(function(tinymce) {
- function processRange(dom, start, end, callback) {
- var ancestor, n, startPoint, endPoint, sib;
+ tinymce.GlobalCommands = new CommandManager();
+})(tinymce);
+(function(tinymce) {
+ tinymce.Formatter = function(ed) {
+ var formats = {},
+ each = tinymce.each,
+ dom = ed.dom,
+ selection = ed.selection,
+ TreeWalker = tinymce.dom.TreeWalker,
+ rangeUtils = new tinymce.dom.RangeUtils(dom),
+ isValid = ed.schema.isValid,
+ isBlock = dom.isBlock,
+ forcedRootBlock = ed.settings.forced_root_block,
+ nodeIndex = dom.nodeIndex,
+ INVISIBLE_CHAR = '\uFEFF',
+ MCE_ATTR_RE = /^(src|href|style)$/,
+ FALSE = false,
+ TRUE = true,
+ undefined,
+ pendingFormats = {apply : [], remove : []};
+
+ function getParents(node, selector) {
+ return dom.getParents(node, selector, dom.getRoot());
+ };
- function findEndPoint(n, c) {
- do {
- if (n.parentNode == c)
- return n;
+ function isCaretNode(node) {
+ return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
+ };
- n = n.parentNode;
- } while(n);
+ // Public functions
+
+ function get(name) {
+ return name ? formats[name] : formats;
};
- function process(n) {
- callback(n);
- tinymce.walk(n, callback, 'childNodes');
+ function register(name, format) {
+ if (name) {
+ if (typeof(name) !== 'string') {
+ each(name, function(format, name) {
+ register(name, format);
+ });
+ } else {
+ // Force format into array and add it to internal collection
+ format = format.length ? format : [format];
+
+ each(format, function(format) {
+ // Set deep to false by default on selector formats this to avoid removing
+ // alignment on images inside paragraphs when alignment is changed on paragraphs
+ if (format.deep === undefined)
+ format.deep = !format.selector;
+
+ // Default to true
+ if (format.split === undefined)
+ format.split = !format.selector || format.inline;
+
+ // Default to true
+ if (format.remove === undefined && format.selector && !format.inline)
+ format.remove = 'none';
+
+ // Mark format as a mixed format inline + block level
+ if (format.selector && format.inline) {
+ format.mixed = true;
+ format.block_expand = true;
+ }
+
+ // Split classes if needed
+ if (typeof(format.classes) === 'string')
+ format.classes = format.classes.split(/\s+/);
+ });
+
+ formats[name] = format;
+ }
+ }
};
- // Find common ancestor and end points
- ancestor = dom.findCommonAncestor(start, end);
- startPoint = findEndPoint(start, ancestor) || start;
- endPoint = findEndPoint(end, ancestor) || end;
+ function apply(name, vars, node) {
+ var formatList = get(name), format = formatList[0], bookmark, rng, i;
- // Process left leaf
- for (n = start; n && n != startPoint; n = n.parentNode) {
- for (sib = n.nextSibling; sib; sib = sib.nextSibling)
- process(sib);
- }
+ function moveStart(rng) {
+ var container = rng.startContainer,
+ offset = rng.startOffset,
+ walker, node;
- // Process middle from start to end point
- if (startPoint != endPoint) {
- for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling)
- process(n);
- } else
- process(startPoint);
-
- // Process right leaf
- for (n = end; n && n != endPoint; n = n.parentNode) {
- for (sib = n.previousSibling; sib; sib = sib.previousSibling)
- process(sib);
- }
- };
+ // Move startContainer/startOffset in to a suitable node
+ if (container.nodeType == 1 || container.nodeValue === "") {
+ walker = new TreeWalker(container.childNodes[offset]);
+ for (node = walker.current(); node; node = walker.next()) {
+ if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
+ rng.setStart(node, 0);
+ break;
+ }
+ }
+ }
+
+ return rng;
+ };
+
+ function setElementFormat(elm, fmt) {
+ fmt = fmt || format;
+
+ if (elm) {
+ each(fmt.styles, function(value, name) {
+ dom.setStyle(elm, name, replaceVars(value, vars));
+ });
+
+ each(fmt.attributes, function(value, name) {
+ dom.setAttrib(elm, name, replaceVars(value, vars));
+ });
+
+ each(fmt.classes, function(value) {
+ value = replaceVars(value, vars);
+
+ if (!dom.hasClass(elm, value))
+ dom.addClass(elm, value);
+ });
+ }
+ };
+
+ function applyRngStyle(rng) {
+ var newWrappers = [], wrapName, wrapElm;
+
+ // Setup wrapper element
+ wrapName = format.inline || format.block;
+ wrapElm = dom.create(wrapName);
+ setElementFormat(wrapElm);
+
+ rangeUtils.walk(rng, function(nodes) {
+ var currentWrapElm;
- tinymce.GlobalCommands.add('RemoveFormat', function() {
- var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n;
+ function process(node) {
+ var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
- function findFormatRoot(n) {
- var sp;
+ // Stop wrapping on br elements
+ if (isEq(nodeName, 'br')) {
+ currentWrapElm = 0;
- dom.getParent(n, function(n) {
- if (dom.is(n, ed.getParam('removeformat_selector')))
- sp = n;
+ // Remove any br elements when we wrap things
+ if (format.block)
+ dom.remove(node);
- return dom.isBlock(n);
- }, ed.getBody());
+ return;
+ }
+
+ // If node is wrapper type
+ if (format.wrapper && matchNode(node, name, vars)) {
+ currentWrapElm = 0;
+ return;
+ }
+
+ // Can we rename the block
+ if (format.block && !format.wrapper && isTextBlock(nodeName)) {
+ node = dom.rename(node, wrapName);
+ setElementFormat(node);
+ newWrappers.push(node);
+ currentWrapElm = 0;
+ return;
+ }
- return sp;
+ // Handle selector patterns
+ if (format.selector) {
+ // Look for matching formats
+ each(formatList, function(format) {
+ if (dom.is(node, format.selector) && !isCaretNode(node)) {
+ setElementFormat(node, format);
+ found = true;
+ }
+ });
+
+ // Contine processing if a selector match wasn't found and a inline element is defined
+ if (!format.inline || found) {
+ currentWrapElm = 0;
+ return;
+ }
+ }
+
+ // Is it valid to wrap this item
+ if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
+ // Start wrapping
+ if (!currentWrapElm) {
+ // Wrap the node
+ currentWrapElm = wrapElm.cloneNode(FALSE);
+ node.parentNode.insertBefore(currentWrapElm, node);
+ newWrappers.push(currentWrapElm);
+ }
+
+ currentWrapElm.appendChild(node);
+ } else {
+ // Start a new wrapper for possible children
+ currentWrapElm = 0;
+
+ each(tinymce.grep(node.childNodes), process);
+
+ // End the last wrapper
+ currentWrapElm = 0;
+ }
+ };
+
+ // Process siblings from range
+ each(nodes, process);
+ });
+
+ // Cleanup
+ each(newWrappers, function(node) {
+ var childCount;
+
+ function getChildCount(node) {
+ var count = 0;
+
+ each(node.childNodes, function(node) {
+ if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
+ count++;
+ });
+
+ return count;
+ };
+
+ function mergeStyles(node) {
+ var child, clone;
+
+ each(node.childNodes, function(node) {
+ if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
+ child = node;
+ return FALSE; // break loop
+ }
+ });
+
+ // If child was found and of the same type as the current node
+ if (child && matchName(child, format)) {
+ clone = child.cloneNode(FALSE);
+ setElementFormat(clone);
+
+ dom.replace(clone, node, TRUE);
+ dom.remove(child, 1);
+ }
+
+ return clone || node;
+ };
+
+ childCount = getChildCount(node);
+
+ // Remove empty nodes
+ if (childCount === 0) {
+ dom.remove(node, 1);
+ return;
+ }
+
+ if (format.inline || format.wrapper) {
+ // Merges the current node with it's children of similar type to reduce the number of elements
+ if (!format.exact && childCount === 1)
+ node = mergeStyles(node);
+
+ // Remove/merge children
+ each(formatList, function(format) {
+ // Merge all children of similar type will move styles from child to parent
+ // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
+ // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
+ each(dom.select(format.inline, node), function(child) {
+ removeFormat(format, vars, child, format.exact ? child : null);
+ });
+ });
+
+ // Look for parent with similar style format
+ dom.getParent(node.parentNode, function(parent) {
+ if (matchNode(parent, name, vars)) {
+ dom.remove(node, 1);
+ node = 0;
+ return TRUE;
+ }
+ });
+
+ // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
+ if (node) {
+ node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
+ node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
+ }
+ }
+ });
+ };
+
+ if (format) {
+ if (node) {
+ rng = dom.createRng();
+
+ rng.setStartBefore(node);
+ rng.setEndAfter(node);
+
+ applyRngStyle(rng);
+ } else {
+ if (!selection.isCollapsed() || !format.inline) {
+ // Apply formatting to selection
+ bookmark = selection.getBookmark();
+ applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
+
+ selection.moveToBookmark(bookmark);
+ selection.setRng(moveStart(selection.getRng(TRUE)));
+ ed.nodeChanged();
+ } else
+ performCaretAction('apply', name, vars);
+ }
+ }
};
- function collect(n) {
- if (dom.is(n, ed.getParam('removeformat_selector')))
- nodes.push(n);
+ function remove(name, vars, node) {
+ var formatList = get(name), format = formatList[0], bookmark, i, rng;
+
+ // Merges the styles for each node
+ function process(node) {
+ var children, i, l;
+
+ // Grab the children first since the nodelist might be changed
+ children = tinymce.grep(node.childNodes);
+
+ // Process current node
+ for (i = 0, l = formatList.length; i < l; i++) {
+ if (removeFormat(formatList[i], vars, node, node))
+ break;
+ }
+
+ // Process the children
+ if (format.deep) {
+ for (i = 0, l = children.length; i < l; i++)
+ process(children[i]);
+ }
+ };
+
+ function findFormatRoot(container) {
+ var formatRoot;
+
+ // Find format root
+ each(getParents(container.parentNode).reverse(), function(parent) {
+ var format;
+
+ // Find format root element
+ if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
+ // Is the node matching the format we are looking for
+ format = matchNode(parent, name, vars);
+ if (format && format.split !== false)
+ formatRoot = parent;
+ }
+ });
+
+ return formatRoot;
+ };
+
+ function wrapAndSplit(format_root, container, target, split) {
+ var parent, clone, lastClone, firstClone, i, formatRootParent;
+
+ // Format root found then clone formats and split it
+ if (format_root) {
+ formatRootParent = format_root.parentNode;
+
+ for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
+ clone = parent.cloneNode(FALSE);
+
+ for (i = 0; i < formatList.length; i++) {
+ if (removeFormat(formatList[i], vars, clone, clone)) {
+ clone = 0;
+ break;
+ }
+ }
+
+ // Build wrapper node
+ if (clone) {
+ if (lastClone)
+ clone.appendChild(lastClone);
+
+ if (!firstClone)
+ firstClone = clone;
+
+ lastClone = clone;
+ }
+ }
+
+ // Never split block elements if the format is mixed
+ if (split && (!format.mixed || !isBlock(format_root)))
+ container = dom.split(format_root, container);
+
+ // Wrap container in cloned formats
+ if (lastClone) {
+ target.parentNode.insertBefore(lastClone, target);
+ firstClone.appendChild(target);
+ }
+ }
+
+ return container;
+ };
+
+ function splitToFormatRoot(container) {
+ return wrapAndSplit(findFormatRoot(container), container, container, true);
+ };
+
+ function unwrap(start) {
+ var node = dom.get(start ? '_start' : '_end'),
+ out = node[start ? 'firstChild' : 'lastChild'];
+
+ dom.remove(node, 1);
+
+ return out;
+ };
+
+ function removeRngStyle(rng) {
+ var startContainer, endContainer;
+
+ rng = expandRng(rng, formatList, TRUE);
+
+ if (format.split) {
+ startContainer = getContainer(rng, TRUE);
+ endContainer = getContainer(rng);
+
+ if (startContainer != endContainer) {
+ // Wrap start/end nodes in span element since these might be cloned/moved
+ startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
+ endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
+
+ // Split start/end
+ splitToFormatRoot(startContainer);
+ splitToFormatRoot(endContainer);
+
+ // Unwrap start/end to get real elements again
+ startContainer = unwrap(TRUE);
+ endContainer = unwrap();
+ } else
+ startContainer = endContainer = splitToFormatRoot(startContainer);
+
+ // Update range positions since they might have changed after the split operations
+ rng.startContainer = startContainer.parentNode;
+ rng.startOffset = nodeIndex(startContainer);
+ rng.endContainer = endContainer.parentNode;
+ rng.endOffset = nodeIndex(endContainer) + 1;
+ }
+
+ // Remove items between start/end
+ rangeUtils.walk(rng, function(nodes) {
+ each(nodes, function(node) {
+ process(node);
+ });
+ });
+ };
+
+ // Handle node
+ if (node) {
+ rng = dom.createRng();
+ rng.setStartBefore(node);
+ rng.setEndAfter(node);
+ removeRngStyle(rng);
+ return;
+ }
+
+ if (!selection.isCollapsed() || !format.inline) {
+ bookmark = selection.getBookmark();
+ removeRngStyle(selection.getRng(TRUE));
+ selection.moveToBookmark(bookmark);
+ ed.nodeChanged();
+ } else
+ performCaretAction('remove', name, vars);
};
- function walk(n) {
- collect(n);
- tinymce.walk(n, collect, 'childNodes');
+ function toggle(name, vars, node) {
+ if (match(name, vars, node))
+ remove(name, vars, node);
+ else
+ apply(name, vars, node);
};
- bm = s.getBookmark();
- sc = r.startContainer;
- ec = r.endContainer;
- so = r.startOffset;
- eo = r.endOffset;
- sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
- ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
+ function matchNode(node, name, vars) {
+ var formatList = get(name), format, i, classes;
- // Same container
- if (sc == ec) { // TEXT_NODE
- start = findFormatRoot(sc);
+ function matchItems(node, format, item_name) {
+ var key, value, items = format[item_name], i;
- // Handle single text node
- if (sc.nodeType == 3) {
- if (start && start.nodeType == 1) { // ELEMENT
- n = sc.splitText(so);
- n.splitText(eo - so);
- dom.split(start, n);
+ // Check all items
+ if (items) {
+ // Non indexed object
+ if (items.length === undefined) {
+ for (key in items) {
+ if (items.hasOwnProperty(key)) {
+ if (item_name === 'attributes')
+ value = dom.getAttrib(node, key);
+ else
+ value = getStyle(node, key);
- s.moveToBookmark(bm);
+ if (!isEq(value, replaceVars(items[key], vars)))
+ return;
+ }
+ }
+ } else {
+ // Only one match needed for indexed arrays
+ for (i = 0; i < items.length; i++) {
+ if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
+ return format;
+ }
+ }
}
- return;
+ return format;
+ };
+
+ if (formatList && node) {
+ // Check each format in list
+ for (i = 0; i < formatList.length; i++) {
+ format = formatList[i];
+
+ // Name name, attributes, styles and classes
+ if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
+ // Match classes
+ if (classes = format.classes) {
+ for (i = 0; i < classes.length; i++) {
+ if (!dom.hasClass(node, classes[i]))
+ return;
+ }
+ }
+
+ return format;
+ }
+ }
}
+ };
- // Handle single element
- walk(dom.split(start, sc) || sc);
- } else {
- // Find start/end format root
- start = findFormatRoot(sc);
- end = findFormatRoot(ec);
+ function match(name, vars, node) {
+ var startNode, i;
+
+ function matchParents(node) {
+ // Find first node with similar format settings
+ node = dom.getParent(node, function(node) {
+ return !!matchNode(node, name, vars);
+ });
- // Split start text node
- if (start) {
- if (sc.nodeType == 3) { // TEXT
- // Since IE doesn't support white space nodes in the DOM we need to
- // add this invisible character so that the splitText function can split the contents
- if (so == sc.nodeValue.length)
- sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix
+ // Do an exact check on the similar format element
+ return matchNode(node, name, vars);
+ };
+
+ // Check specified node
+ if (node)
+ return matchParents(node);
+
+ // Check pending formats
+ if (selection.isCollapsed()) {
+ for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
+ if (pendingFormats.apply[i].name == name)
+ return true;
+ }
- sc = sc.splitText(so);
+ for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
+ if (pendingFormats.remove[i].name == name)
+ return false;
}
+
+ return matchParents(selection.getNode());
}
- // Split end text node
- if (end) {
- if (ec.nodeType == 3) // TEXT
- ec.splitText(eo);
+ // Check selected node
+ node = selection.getNode();
+ if (matchParents(node))
+ return TRUE;
+
+ // Check start node if it's different
+ startNode = selection.getStart();
+ if (startNode != node) {
+ if (matchParents(startNode))
+ return TRUE;
}
- // If the start and end format root is the same then we need to wrap
- // the end node in a span since the split calls might change the reference
- // Example: <p><b><em>x[yz<span>---</span>12]3</em></b></p>
- if (start && start == end)
- dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec);
+ return FALSE;
+ };
- // Split all start containers down to the format root
- if (start)
- start = dom.split(start, sc);
- else
- start = sc;
+ function canApply(name) {
+ var formatList = get(name), startNode, parents, i, x, selector;
- // If there is a span wrapper use that one instead
- if (n = dom.get('__end')) {
- ec = n;
- end = findFormatRoot(ec);
- }
+ if (formatList) {
+ startNode = selection.getStart();
+ parents = getParents(startNode);
- // Split all end containers down to the format root
- if (end)
- end = dom.split(end, ec);
- else
- end = ec;
+ for (x = formatList.length - 1; x >= 0; x--) {
+ selector = formatList[x].selector;
- // Collect nodes in between
- processRange(dom, start, end, collect);
+ // Format is not selector based, then always return TRUE
+ if (!selector)
+ return TRUE;
- // Remove invisible character for IE workaround if we find it
- if (sc.nodeValue == '\uFEFF')
- sc.nodeValue = '';
+ for (i = parents.length - 1; i >= 0; i--) {
+ if (dom.is(parents[i], selector))
+ return TRUE;
+ }
+ }
+ }
- // Process start/end container elements
- walk(ec);
- walk(sc);
- }
+ return FALSE;
+ };
- // Remove all collected nodes
- tinymce.each(nodes, function(n) {
- dom.remove(n, 1);
+ // Expose to public
+ tinymce.extend(this, {
+ get : get,
+ register : register,
+ apply : apply,
+ remove : remove,
+ toggle : toggle,
+ match : match,
+ matchNode : matchNode,
+ canApply : canApply
});
- // Remove leftover wrapper
- dom.remove('__end', 1);
+ // Private functions
- s.moveToBookmark(bm);
- });
-})(tinymce);
-(function(tinymce) {
- tinymce.GlobalCommands.add('mceBlockQuote', function() {
- var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl;
+ function matchName(node, format) {
+ // Check for inline match
+ if (isEq(node, format.inline))
+ return TRUE;
+
+ // Check for block match
+ if (isEq(node, format.block))
+ return TRUE;
+
+ // Check for selector match
+ if (format.selector)
+ return dom.is(node, format.selector);
+ };
- function getBQ(e) {
- return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';});
+ function isEq(str1, str2) {
+ str1 = str1 || '';
+ str2 = str2 || '';
+
+ str1 = str1.nodeName || str1;
+ str2 = str2.nodeName || str2;
+
+ return str1.toLowerCase() == str2.toLowerCase();
};
- // Get start/end block
- sb = dom.getParent(s.getStart(), dom.isBlock);
- eb = dom.getParent(s.getEnd(), dom.isBlock);
+ function getStyle(node, name) {
+ var styleVal = dom.getStyle(node, name);
- // Remove blockquote(s)
- if (bq = getBQ(sb)) {
- if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
- bm = s.getBookmark();
+ // Force the format to hex
+ if (name == 'color' || name == 'backgroundColor')
+ styleVal = dom.toHex(styleVal);
- // Move all elements after the end block into new bq
- if (getBQ(eb)) {
- bq2 = bq.cloneNode(false);
+ // Opera will return bold as 700
+ if (name == 'fontWeight' && styleVal == 700)
+ styleVal = 'bold';
+
+ return '' + styleVal;
+ };
- while (n = eb.nextSibling)
- bq2.appendChild(n.parentNode.removeChild(n));
+ function replaceVars(value, vars) {
+ if (typeof(value) != "string")
+ value = value(vars);
+ else if (vars) {
+ value = value.replace(/%(\w+)/g, function(str, name) {
+ return vars[name] || str;
+ });
}
- // Add new bq after
- if (bq2)
- dom.insertAfter(bq2, bq);
+ return value;
+ };
- // Move all selected blocks after the current bq
- nl = s.getSelectedBlocks(sb, eb);
- for (i = nl.length - 1; i >= 0; i--) {
- dom.insertAfter(nl[i], bq);
+ function isWhiteSpaceNode(node) {
+ return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue);
+ };
+
+ function wrap(node, name, attrs) {
+ var wrapper = dom.create(name, attrs);
+
+ node.parentNode.insertBefore(wrapper, node);
+ wrapper.appendChild(node);
+
+ return wrapper;
+ };
+
+ function expandRng(rng, format, remove) {
+ var startContainer = rng.startContainer,
+ startOffset = rng.startOffset,
+ endContainer = rng.endContainer,
+ endOffset = rng.endOffset, sibling, lastIdx;
+
+ // This function walks up the tree if there is no siblings before/after the node
+ function findParentContainer(container, child_name, sibling_name, root) {
+ var parent, child;
+
+ root = root || dom.getRoot();
+
+ for (;;) {
+ // Check if we can move up are we at root level or body level
+ parent = container.parentNode;
+
+ // Stop expanding on block elements or root depending on format
+ if (parent == root || (!format[0].block_expand && isBlock(parent)))
+ return container;
+
+ for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
+ if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
+ return container;
+
+ if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
+ return container;
+ }
+
+ container = container.parentNode;
+ }
+
+ return container;
+ };
+
+ // If index based start position then resolve it
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
+ lastIdx = startContainer.childNodes.length - 1;
+ startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
+
+ if (startContainer.nodeType == 3)
+ startOffset = 0;
}
- // Empty bq, then remove it
- if (/^\s*$/.test(bq.innerHTML))
- dom.remove(bq, 1); // Keep children so boomark restoration works correctly
+ // If index based end position then resolve it
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
+ lastIdx = endContainer.childNodes.length - 1;
+ endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
- // Empty bq, then remote it
- if (bq2 && /^\s*$/.test(bq2.innerHTML))
- dom.remove(bq2, 1); // Keep children so boomark restoration works correctly
+ if (endContainer.nodeType == 3)
+ endOffset = endContainer.nodeValue.length;
+ }
- if (!bm) {
- // Move caret inside empty block element
- if (!tinymce.isIE) {
- r = ed.getDoc().createRange();
- r.setStart(sb, 0);
- r.setEnd(sb, 0);
- s.setRng(r);
- } else {
- s.select(sb);
- s.collapse(0);
+ // Exclude bookmark nodes if possible
+ if (isBookmarkNode(startContainer.parentNode))
+ startContainer = startContainer.parentNode;
- // IE misses the empty block some times element so we must move back the caret
- if (dom.getParent(s.getStart(), dom.isBlock) != sb) {
- r = s.getRng();
- r.move('character', -1);
- r.select();
+ if (isBookmarkNode(startContainer))
+ startContainer = startContainer.nextSibling || startContainer;
+
+ if (isBookmarkNode(endContainer.parentNode))
+ endContainer = endContainer.parentNode;
+
+ if (isBookmarkNode(endContainer))
+ endContainer = endContainer.previousSibling || endContainer;
+
+ // Move start/end point up the tree if the leaves are sharp and if we are in different containers
+ // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
+ // This will reduce the number of wrapper elements that needs to be created
+ // Move start point up the tree
+ if (format[0].inline || format[0].block_expand) {
+ startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
+ endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
+ }
+
+ // Expand start/end container to matching selector
+ if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
+ function findSelectorEndPoint(container, sibling_name) {
+ var parents, i, y;
+
+ if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
+ container = container[sibling_name];
+
+ parents = getParents(container);
+ for (i = 0; i < parents.length; i++) {
+ for (y = 0; y < format.length; y++) {
+ if (dom.is(parents[i], format[y].selector))
+ return parents[i];
+ }
}
+
+ return container;
+ };
+
+ // Find new startContainer/endContainer if there is better one
+ startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
+ endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
+ }
+
+ // Expand start/end container to matching block element or text node
+ if (format[0].block || format[0].selector) {
+ function findBlockEndPoint(container, sibling_name, sibling_name2) {
+ var node;
+
+ // Expand to block of similar type
+ if (!format[0].wrapper)
+ node = dom.getParent(container, format[0].block);
+
+ // Expand to first wrappable block element or any block element
+ if (!node)
+ node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
+
+ // Exclude inner lists from wrapping
+ if (node && format[0].wrapper)
+ node = getParents(node, 'ul,ol').reverse()[0] || node;
+
+ // Didn't find a block element look for first/last wrappable element
+ if (!node) {
+ node = container;
+
+ while (node[sibling_name] && !isBlock(node[sibling_name])) {
+ node = node[sibling_name];
+
+ // Break on BR but include it will be removed later on
+ // we can't remove it now since we need to check if it can be wrapped
+ if (isEq(node, 'br'))
+ break;
+ }
+ }
+
+ return node || container;
+ };
+
+ // Find new startContainer/endContainer if there is better one
+ startContainer = findBlockEndPoint(startContainer, 'previousSibling');
+ endContainer = findBlockEndPoint(endContainer, 'nextSibling');
+
+ // Non block element then try to expand up the leaf
+ if (format[0].block) {
+ if (!isBlock(startContainer))
+ startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
+
+ if (!isBlock(endContainer))
+ endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
}
- } else
- ed.selection.moveToBookmark(bm);
+ }
- return;
- }
+ // Setup index for startContainer
+ if (startContainer.nodeType == 1) {
+ startOffset = nodeIndex(startContainer);
+ startContainer = startContainer.parentNode;
+ }
- // Since IE can start with a totally empty document we need to add the first bq and paragraph
- if (tinymce.isIE && !sb && !eb) {
- ed.getDoc().execCommand('Indent');
- n = getBQ(s.getNode());
- n.style.margin = n.dir = ''; // IE adds margin and dir to bq
- return;
+ // Setup index for endContainer
+ if (endContainer.nodeType == 1) {
+ endOffset = nodeIndex(endContainer) + 1;
+ endContainer = endContainer.parentNode;
+ }
+
+ // Return new range like object
+ return {
+ startContainer : startContainer,
+ startOffset : startOffset,
+ endContainer : endContainer,
+ endOffset : endOffset
+ };
}
- if (!sb || !eb)
- return;
+ function removeFormat(format, vars, node, compare_node) {
+ var i, attrs, stylesModified;
- // If empty paragraph node then do not use bookmark
- if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
- bm = s.getBookmark();
+ // Check if node matches format
+ if (!matchName(node, format))
+ return FALSE;
- // Move selected block elements into a bq
- tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) {
- // Found existing BQ add to this one
- if (e.nodeName == 'BLOCKQUOTE' && !bq) {
- bq = e;
- return;
+ // Should we compare with format attribs and styles
+ if (format.remove != 'all') {
+ // Remove styles
+ each(format.styles, function(value, name) {
+ value = replaceVars(value, vars);
+
+ // Indexed array
+ if (typeof(name) === 'number') {
+ name = value;
+ compare_node = 0;
+ }
+
+ if (!compare_node || isEq(getStyle(compare_node, name), value))
+ dom.setStyle(node, name, '');
+
+ stylesModified = 1;
+ });
+
+ // Remove style attribute if it's empty
+ if (stylesModified && dom.getAttrib(node, 'style') == '') {
+ node.removeAttribute('style');
+ node.removeAttribute('_mce_style');
+ }
+
+ // Remove attributes
+ each(format.attributes, function(value, name) {
+ var valueOut;
+
+ value = replaceVars(value, vars);
+
+ // Indexed array
+ if (typeof(name) === 'number') {
+ name = value;
+ compare_node = 0;
+ }
+
+ if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
+ // Keep internal classes
+ if (name == 'class') {
+ value = dom.getAttrib(node, name);
+ if (value) {
+ // Build new class value where everything is removed except the internal prefixed classes
+ valueOut = '';
+ each(value.split(/\s+/), function(cls) {
+ if (/mce\w+/.test(cls))
+ valueOut += (valueOut ? ' ' : '') + cls;
+ });
+
+ // We got some internal classes left
+ if (valueOut) {
+ dom.setAttrib(node, name, valueOut);
+ return;
+ }
+ }
+ }
+
+ // IE6 has a bug where the attribute doesn't get removed correctly
+ if (name == "class")
+ node.removeAttribute('className');
+
+ // Remove mce prefixed attributes
+ if (MCE_ATTR_RE.test(name))
+ node.removeAttribute('_mce_' + name);
+
+ node.removeAttribute(name);
+ }
+ });
+
+ // Remove classes
+ each(format.classes, function(value) {
+ value = replaceVars(value, vars);
+
+ if (!compare_node || dom.hasClass(compare_node, value))
+ dom.removeClass(node, value);
+ });
+
+ // Check for non internal attributes
+ attrs = dom.getAttribs(node);
+ for (i = 0; i < attrs.length; i++) {
+ if (attrs[i].nodeName.indexOf('_') !== 0)
+ return FALSE;
+ }
}
- // No BQ found, create one
- if (!bq) {
- bq = dom.create('blockquote');
- e.parentNode.insertBefore(bq, e);
+ // Remove the inline child if it's empty for example <b> or <span>
+ if (format.remove != 'none') {
+ removeNode(node, format);
+ return TRUE;
}
+ };
- // Add children from existing BQ
- if (e.nodeName == 'BLOCKQUOTE' && bq) {
- n = e.firstChild;
+ function removeNode(node, format) {
+ var parentNode = node.parentNode, rootBlockElm;
- while (n) {
- bq.appendChild(n.cloneNode(true));
- n = n.nextSibling;
+ if (format.block) {
+ if (!forcedRootBlock) {
+ function find(node, next, inc) {
+ node = getNonWhiteSpaceSibling(node, next, inc);
+
+ return !node || (node.nodeName == 'BR' || isBlock(node));
+ };
+
+ // Append BR elements if needed before we remove the block
+ if (isBlock(node) && !isBlock(parentNode)) {
+ if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
+ node.insertBefore(dom.create('br'), node.firstChild);
+
+ if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
+ node.appendChild(dom.create('br'));
+ }
+ } else {
+ // Wrap the block in a forcedRootBlock if we are at the root of document
+ if (parentNode == dom.getRoot()) {
+ if (!format.list_block || !isEq(node, format.list_block)) {
+ each(tinymce.grep(node.childNodes), function(node) {
+ if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
+ if (!rootBlockElm)
+ rootBlockElm = wrap(node, forcedRootBlock);
+ else
+ rootBlockElm.appendChild(node);
+ } else
+ rootBlockElm = 0;
+ });
+ }
+ }
}
+ }
- dom.remove(e);
+ // Never remove nodes that isn't the specified inline element if a selector is specified too
+ if (format.selector && format.inline && !isEq(format.inline, node))
return;
+
+ dom.remove(node, 1);
+ };
+
+ function getNonWhiteSpaceSibling(node, next, inc) {
+ if (node) {
+ next = next ? 'nextSibling' : 'previousSibling';
+
+ for (node = inc ? node : node[next]; node; node = node[next]) {
+ if (node.nodeType == 1 || !isWhiteSpaceNode(node))
+ return node;
+ }
}
+ };
- // Add non BQ element to BQ
- bq.appendChild(dom.remove(e));
- });
+ function isBookmarkNode(node) {
+ return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
+ };
- if (!bm) {
- // Move caret inside empty block element
- if (!tinymce.isIE) {
- r = ed.getDoc().createRange();
- r.setStart(sb, 0);
- r.setEnd(sb, 0);
- s.setRng(r);
- } else {
- s.select(sb);
- s.collapse(1);
+ function mergeSiblings(prev, next) {
+ var marker, sibling, tmpSibling;
+
+ function compareElements(node1, node2) {
+ // Not the same name
+ if (node1.nodeName != node2.nodeName)
+ return FALSE;
+
+ function getAttribs(node) {
+ var attribs = {};
+
+ each(dom.getAttribs(node), function(attr) {
+ var name = attr.nodeName.toLowerCase();
+
+ // Don't compare internal attributes or style
+ if (name.indexOf('_') !== 0 && name !== 'style')
+ attribs[name] = dom.getAttrib(node, name);
+ });
+
+ return attribs;
+ };
+
+ function compareObjects(obj1, obj2) {
+ var value, name;
+
+ for (name in obj1) {
+ // Obj1 has item obj2 doesn't have
+ if (obj1.hasOwnProperty(name)) {
+ value = obj2[name];
+
+ // Obj2 doesn't have obj1 item
+ if (value === undefined)
+ return FALSE;
+
+ // Obj2 item has a different value
+ if (obj1[name] != value)
+ return FALSE;
+
+ // Delete similar value
+ delete obj2[name];
+ }
+ }
+
+ // Check if obj 2 has something obj 1 doesn't have
+ for (name in obj2) {
+ // Obj2 has item obj1 doesn't have
+ if (obj2.hasOwnProperty(name))
+ return FALSE;
+ }
+
+ return TRUE;
+ };
+
+ // Attribs are not the same
+ if (!compareObjects(getAttribs(node1), getAttribs(node2)))
+ return FALSE;
+
+ // Styles are not the same
+ if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
+ return FALSE;
+
+ return TRUE;
+ };
+
+ // Check if next/prev exists and that they are elements
+ if (prev && next) {
+ function findElementSibling(node, sibling_name) {
+ for (sibling = node; sibling; sibling = sibling[sibling_name]) {
+ if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
+ return node;
+
+ if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
+ return sibling;
+ }
+
+ return node;
+ };
+
+ // If previous sibling is empty then jump over it
+ prev = findElementSibling(prev, 'previousSibling');
+ next = findElementSibling(next, 'nextSibling');
+
+ // Compare next and previous nodes
+ if (compareElements(prev, next)) {
+ // Append nodes between
+ for (sibling = prev.nextSibling; sibling && sibling != next;) {
+ tmpSibling = sibling;
+ sibling = sibling.nextSibling;
+ prev.appendChild(tmpSibling);
+ }
+
+ // Remove next node
+ dom.remove(next);
+
+ // Move children into prev node
+ each(tinymce.grep(next.childNodes), function(node) {
+ prev.appendChild(node);
+ });
+
+ return prev;
+ }
}
- } else
- s.moveToBookmark(bm);
- });
-})(tinymce);
-(function(tinymce) {
- tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) {
- tinymce.GlobalCommands.add(cmd, function() {
- var ed = this, doc = ed.getDoc();
- try {
- doc.execCommand(cmd, false, null);
+ return next;
+ };
- // On WebKit the command will just be ignored if it's not enabled
- if (!doc.queryCommandEnabled(cmd))
- throw 'Error';
- } catch (ex) {
- if (tinymce.isGecko) {
- ed.windowManager.confirm(ed.getLang('clipboard_msg'), function(s) {
- if (s)
- open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
+ function isTextBlock(name) {
+ return /^(h[1-6]|p|div|pre|address)$/.test(name);
+ };
+
+ function getContainer(rng, start) {
+ var container, offset, lastIdx;
+
+ container = rng[start ? 'startContainer' : 'endContainer'];
+ offset = rng[start ? 'startOffset' : 'endOffset'];
+
+ if (container.nodeType == 1) {
+ lastIdx = container.childNodes.length - 1;
+
+ if (!start && offset)
+ offset--;
+
+ container = container.childNodes[offset > lastIdx ? lastIdx : offset];
+ }
+
+ return container;
+ };
+
+ function performCaretAction(type, name, vars) {
+ var i, currentPendingFormats = pendingFormats[type],
+ otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
+
+ function hasPending() {
+ return pendingFormats.apply.length || pendingFormats.remove.length;
+ };
+
+ function resetPending() {
+ pendingFormats.apply = [];
+ pendingFormats.remove = [];
+ };
+
+ function perform(caret_node) {
+ // Apply pending formats
+ each(pendingFormats.apply.reverse(), function(item) {
+ apply(item.name, item.vars, caret_node);
+ });
+
+ // Remove pending formats
+ each(pendingFormats.remove.reverse(), function(item) {
+ remove(item.name, item.vars, caret_node);
+ });
+
+ dom.remove(caret_node, 1);
+ resetPending();
+ };
+
+ // Check if it already exists then ignore it
+ for (i = currentPendingFormats.length - 1; i >= 0; i--) {
+ if (currentPendingFormats[i].name == name)
+ return;
+ }
+
+ currentPendingFormats.push({name : name, vars : vars});
+
+ // Check if it's in the other type, then remove it
+ for (i = otherPendingFormats.length - 1; i >= 0; i--) {
+ if (otherPendingFormats[i].name == name)
+ otherPendingFormats.splice(i, 1);
+ }
+
+ // Pending apply or remove formats
+ if (hasPending()) {
+ ed.getDoc().execCommand('FontName', false, 'mceinline');
+
+ // IE will convert the current word
+ each(dom.select('font,span'), function(node) {
+ var bookmark;
+
+ if (isCaretNode(node)) {
+ bookmark = selection.getBookmark();
+ perform(node);
+ selection.moveToBookmark(bookmark);
+ ed.nodeChanged();
+ }
+ });
+
+ // Only register listeners once if we need to
+ if (!pendingFormats.isListening && hasPending()) {
+ pendingFormats.isListening = true;
+
+ each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
+ ed[event].addToTop(function(ed, e) {
+ if (hasPending()) {
+ each(dom.select('font,span'), function(node) {
+ var bookmark, textNode, rng;
+
+ // Look for marker
+ if (isCaretNode(node)) {
+ textNode = node.firstChild;
+
+ perform(node);
+
+ rng = dom.createRng();
+ rng.setStart(textNode, textNode.nodeValue.length);
+ rng.setEnd(textNode, textNode.nodeValue.length);
+ selection.setRng(rng);
+ ed.nodeChanged();
+ }
+ });
+
+ // Always unbind and clear pending styles on keyup
+ if (e.type == 'keyup' || e.type == 'mouseup')
+ resetPending();
+ }
+ });
});
- } else
- ed.windowManager.alert(ed.getLang('clipboard_no_support'));
+ }
}
- });
- });
+ };
+ };
})(tinymce);
-(function(tinymce) {
- tinymce.GlobalCommands.add('InsertHorizontalRule', function() {
- if (tinymce.isOpera)
- return this.getDoc().execCommand('InsertHorizontalRule', false, '');
- this.selection.setContent('<hr />');
- });
-})(tinymce);
-(function() {
- var cmds = tinymce.GlobalCommands;
+tinymce.onAddEditor.add(function(tinymce, ed) {
+ var filters, fontSizes, dom, settings = ed.settings;
- cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() {
- this.undoManager.add();
- });
+ if (settings.inline_styles) {
+ fontSizes = tinymce.explode(settings.font_size_style_values);
- cmds.add('Undo', function() {
- var ed = this;
+ function replaceWithSpan(node, styles) {
+ dom.replace(dom.create('span', {
+ style : styles
+ }), node, 1);
+ };
- if (ed.settings.custom_undo_redo) {
- ed.undoManager.undo();
- ed.nodeChanged();
- return true;
- }
+ filters = {
+ font : function(dom, node) {
+ replaceWithSpan(node, {
+ backgroundColor : node.style.backgroundColor,
+ color : node.color,
+ fontFamily : node.face,
+ fontSize : fontSizes[parseInt(node.size) - 1]
+ });
+ },
- return false; // Run browser command
- });
+ u : function(dom, node) {
+ replaceWithSpan(node, {
+ textDecoration : 'underline'
+ });
+ },
- cmds.add('Redo', function() {
- var ed = this;
+ strike : function(dom, node) {
+ replaceWithSpan(node, {
+ textDecoration : 'line-through'
+ });
+ }
+ };
- if (ed.settings.custom_undo_redo) {
- ed.undoManager.redo();
- ed.nodeChanged();
- return true;
- }
+ function convert(editor, params) {
+ dom = editor.dom;
+
+ if (settings.convert_fonts_to_spans) {
+ tinymce.each(dom.select('font,u,strike', params.node), function(node) {
+ filters[node.nodeName.toLowerCase()](ed.dom, node);
+ });
+ }
+ };
+
+ ed.onPreProcess.add(convert);
+
+ ed.onInit.add(function() {
+ ed.selection.onSetContent.add(convert);
+ });
+ }
+});
- return false; // Run browser command
- });
-})();