annotate static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js @ 55:0176eca97d1d

In the middle of revamping the band application. Moved the base and home templates out of the band directory. Started hacking on the band models, finally getting rid of older models and views. Not everything works yet in this commit.
author Brian Neal <bgneal@gmail.com>
date Sat, 07 Apr 2012 15:58:51 -0500
parents 966cde8635c0
children
rev   line source
bgneal@45 1 (function(win) {
bgneal@45 2 var whiteSpaceRe = /^\s*|\s*$/g,
bgneal@45 3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
bgneal@45 4
bgneal@45 5 var tinymce = {
bgneal@45 6 majorVersion : '3',
bgneal@45 7
bgneal@45 8 minorVersion : '5b1',
bgneal@45 9
bgneal@45 10 releaseDate : '2012-03-08',
bgneal@45 11
bgneal@45 12 _init : function() {
bgneal@45 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
bgneal@45 14
bgneal@45 15 t.isOpera = win.opera && opera.buildNumber;
bgneal@45 16
bgneal@45 17 t.isWebKit = /WebKit/.test(ua);
bgneal@45 18
bgneal@45 19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
bgneal@45 20
bgneal@45 21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
bgneal@45 22
bgneal@45 23 t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
bgneal@45 24
bgneal@45 25 t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
bgneal@45 26
bgneal@45 27 t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
bgneal@45 28
bgneal@45 29 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
bgneal@45 30
bgneal@45 31 t.isMac = ua.indexOf('Mac') != -1;
bgneal@45 32
bgneal@45 33 t.isAir = /adobeair/i.test(ua);
bgneal@45 34
bgneal@45 35 t.isIDevice = /(iPad|iPhone)/.test(ua);
bgneal@45 36
bgneal@45 37 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
bgneal@45 38
bgneal@45 39 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
bgneal@45 40 if (win.tinyMCEPreInit) {
bgneal@45 41 t.suffix = tinyMCEPreInit.suffix;
bgneal@45 42 t.baseURL = tinyMCEPreInit.base;
bgneal@45 43 t.query = tinyMCEPreInit.query;
bgneal@45 44 return;
bgneal@45 45 }
bgneal@45 46
bgneal@45 47 // Get suffix and base
bgneal@45 48 t.suffix = '';
bgneal@45 49
bgneal@45 50 // If base element found, add that infront of baseURL
bgneal@45 51 nl = d.getElementsByTagName('base');
bgneal@45 52 for (i=0; i<nl.length; i++) {
bgneal@45 53 if (v = nl[i].href) {
bgneal@45 54 // Host only value like http://site.com or http://site.com:8008
bgneal@45 55 if (/^https?:\/\/[^\/]+$/.test(v))
bgneal@45 56 v += '/';
bgneal@45 57
bgneal@45 58 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
bgneal@45 59 }
bgneal@45 60 }
bgneal@45 61
bgneal@45 62 function getBase(n) {
bgneal@45 63 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
bgneal@45 64 if (/_(src|dev)\.js/g.test(n.src))
bgneal@45 65 t.suffix = '_src';
bgneal@45 66
bgneal@45 67 if ((p = n.src.indexOf('?')) != -1)
bgneal@45 68 t.query = n.src.substring(p + 1);
bgneal@45 69
bgneal@45 70 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
bgneal@45 71
bgneal@45 72 // If path to script is relative and a base href was found add that one infront
bgneal@45 73 // the src property will always be an absolute one on non IE browsers and IE 8
bgneal@45 74 // so this logic will basically only be executed on older IE versions
bgneal@45 75 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
bgneal@45 76 t.baseURL = base + t.baseURL;
bgneal@45 77
bgneal@45 78 return t.baseURL;
bgneal@45 79 }
bgneal@45 80
bgneal@45 81 return null;
bgneal@45 82 };
bgneal@45 83
bgneal@45 84 // Check document
bgneal@45 85 nl = d.getElementsByTagName('script');
bgneal@45 86 for (i=0; i<nl.length; i++) {
bgneal@45 87 if (getBase(nl[i]))
bgneal@45 88 return;
bgneal@45 89 }
bgneal@45 90
bgneal@45 91 // Check head
bgneal@45 92 n = d.getElementsByTagName('head')[0];
bgneal@45 93 if (n) {
bgneal@45 94 nl = n.getElementsByTagName('script');
bgneal@45 95 for (i=0; i<nl.length; i++) {
bgneal@45 96 if (getBase(nl[i]))
bgneal@45 97 return;
bgneal@45 98 }
bgneal@45 99 }
bgneal@45 100
bgneal@45 101 return;
bgneal@45 102 },
bgneal@45 103
bgneal@45 104 is : function(o, t) {
bgneal@45 105 if (!t)
bgneal@45 106 return o !== undefined;
bgneal@45 107
bgneal@45 108 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
bgneal@45 109 return true;
bgneal@45 110
bgneal@45 111 return typeof(o) == t;
bgneal@45 112 },
bgneal@45 113
bgneal@45 114 makeMap : function(items, delim, map) {
bgneal@45 115 var i;
bgneal@45 116
bgneal@45 117 items = items || [];
bgneal@45 118 delim = delim || ',';
bgneal@45 119
bgneal@45 120 if (typeof(items) == "string")
bgneal@45 121 items = items.split(delim);
bgneal@45 122
bgneal@45 123 map = map || {};
bgneal@45 124
bgneal@45 125 i = items.length;
bgneal@45 126 while (i--)
bgneal@45 127 map[items[i]] = {};
bgneal@45 128
bgneal@45 129 return map;
bgneal@45 130 },
bgneal@45 131
bgneal@45 132 each : function(o, cb, s) {
bgneal@45 133 var n, l;
bgneal@45 134
bgneal@45 135 if (!o)
bgneal@45 136 return 0;
bgneal@45 137
bgneal@45 138 s = s || o;
bgneal@45 139
bgneal@45 140 if (o.length !== undefined) {
bgneal@45 141 // Indexed arrays, needed for Safari
bgneal@45 142 for (n=0, l = o.length; n < l; n++) {
bgneal@45 143 if (cb.call(s, o[n], n, o) === false)
bgneal@45 144 return 0;
bgneal@45 145 }
bgneal@45 146 } else {
bgneal@45 147 // Hashtables
bgneal@45 148 for (n in o) {
bgneal@45 149 if (o.hasOwnProperty(n)) {
bgneal@45 150 if (cb.call(s, o[n], n, o) === false)
bgneal@45 151 return 0;
bgneal@45 152 }
bgneal@45 153 }
bgneal@45 154 }
bgneal@45 155
bgneal@45 156 return 1;
bgneal@45 157 },
bgneal@45 158
bgneal@45 159
bgneal@45 160 map : function(a, f) {
bgneal@45 161 var o = [];
bgneal@45 162
bgneal@45 163 tinymce.each(a, function(v) {
bgneal@45 164 o.push(f(v));
bgneal@45 165 });
bgneal@45 166
bgneal@45 167 return o;
bgneal@45 168 },
bgneal@45 169
bgneal@45 170 grep : function(a, f) {
bgneal@45 171 var o = [];
bgneal@45 172
bgneal@45 173 tinymce.each(a, function(v) {
bgneal@45 174 if (!f || f(v))
bgneal@45 175 o.push(v);
bgneal@45 176 });
bgneal@45 177
bgneal@45 178 return o;
bgneal@45 179 },
bgneal@45 180
bgneal@45 181 inArray : function(a, v) {
bgneal@45 182 var i, l;
bgneal@45 183
bgneal@45 184 if (a) {
bgneal@45 185 for (i = 0, l = a.length; i < l; i++) {
bgneal@45 186 if (a[i] === v)
bgneal@45 187 return i;
bgneal@45 188 }
bgneal@45 189 }
bgneal@45 190
bgneal@45 191 return -1;
bgneal@45 192 },
bgneal@45 193
bgneal@45 194 extend : function(o, e) {
bgneal@45 195 var i, l, a = arguments;
bgneal@45 196
bgneal@45 197 for (i = 1, l = a.length; i < l; i++) {
bgneal@45 198 e = a[i];
bgneal@45 199
bgneal@45 200 tinymce.each(e, function(v, n) {
bgneal@45 201 if (v !== undefined)
bgneal@45 202 o[n] = v;
bgneal@45 203 });
bgneal@45 204 }
bgneal@45 205
bgneal@45 206 return o;
bgneal@45 207 },
bgneal@45 208
bgneal@45 209
bgneal@45 210 trim : function(s) {
bgneal@45 211 return (s ? '' + s : '').replace(whiteSpaceRe, '');
bgneal@45 212 },
bgneal@45 213
bgneal@45 214 create : function(s, p, root) {
bgneal@45 215 var t = this, sp, ns, cn, scn, c, de = 0;
bgneal@45 216
bgneal@45 217 // Parse : <prefix> <class>:<super class>
bgneal@45 218 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
bgneal@45 219 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
bgneal@45 220
bgneal@45 221 // Create namespace for new class
bgneal@45 222 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
bgneal@45 223
bgneal@45 224 // Class already exists
bgneal@45 225 if (ns[cn])
bgneal@45 226 return;
bgneal@45 227
bgneal@45 228 // Make pure static class
bgneal@45 229 if (s[2] == 'static') {
bgneal@45 230 ns[cn] = p;
bgneal@45 231
bgneal@45 232 if (this.onCreate)
bgneal@45 233 this.onCreate(s[2], s[3], ns[cn]);
bgneal@45 234
bgneal@45 235 return;
bgneal@45 236 }
bgneal@45 237
bgneal@45 238 // Create default constructor
bgneal@45 239 if (!p[cn]) {
bgneal@45 240 p[cn] = function() {};
bgneal@45 241 de = 1;
bgneal@45 242 }
bgneal@45 243
bgneal@45 244 // Add constructor and methods
bgneal@45 245 ns[cn] = p[cn];
bgneal@45 246 t.extend(ns[cn].prototype, p);
bgneal@45 247
bgneal@45 248 // Extend
bgneal@45 249 if (s[5]) {
bgneal@45 250 sp = t.resolve(s[5]).prototype;
bgneal@45 251 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
bgneal@45 252
bgneal@45 253 // Extend constructor
bgneal@45 254 c = ns[cn];
bgneal@45 255 if (de) {
bgneal@45 256 // Add passthrough constructor
bgneal@45 257 ns[cn] = function() {
bgneal@45 258 return sp[scn].apply(this, arguments);
bgneal@45 259 };
bgneal@45 260 } else {
bgneal@45 261 // Add inherit constructor
bgneal@45 262 ns[cn] = function() {
bgneal@45 263 this.parent = sp[scn];
bgneal@45 264 return c.apply(this, arguments);
bgneal@45 265 };
bgneal@45 266 }
bgneal@45 267 ns[cn].prototype[cn] = ns[cn];
bgneal@45 268
bgneal@45 269 // Add super methods
bgneal@45 270 t.each(sp, function(f, n) {
bgneal@45 271 ns[cn].prototype[n] = sp[n];
bgneal@45 272 });
bgneal@45 273
bgneal@45 274 // Add overridden methods
bgneal@45 275 t.each(p, function(f, n) {
bgneal@45 276 // Extend methods if needed
bgneal@45 277 if (sp[n]) {
bgneal@45 278 ns[cn].prototype[n] = function() {
bgneal@45 279 this.parent = sp[n];
bgneal@45 280 return f.apply(this, arguments);
bgneal@45 281 };
bgneal@45 282 } else {
bgneal@45 283 if (n != cn)
bgneal@45 284 ns[cn].prototype[n] = f;
bgneal@45 285 }
bgneal@45 286 });
bgneal@45 287 }
bgneal@45 288
bgneal@45 289 // Add static methods
bgneal@45 290 t.each(p['static'], function(f, n) {
bgneal@45 291 ns[cn][n] = f;
bgneal@45 292 });
bgneal@45 293
bgneal@45 294 if (this.onCreate)
bgneal@45 295 this.onCreate(s[2], s[3], ns[cn].prototype);
bgneal@45 296 },
bgneal@45 297
bgneal@45 298 walk : function(o, f, n, s) {
bgneal@45 299 s = s || this;
bgneal@45 300
bgneal@45 301 if (o) {
bgneal@45 302 if (n)
bgneal@45 303 o = o[n];
bgneal@45 304
bgneal@45 305 tinymce.each(o, function(o, i) {
bgneal@45 306 if (f.call(s, o, i, n) === false)
bgneal@45 307 return false;
bgneal@45 308
bgneal@45 309 tinymce.walk(o, f, n, s);
bgneal@45 310 });
bgneal@45 311 }
bgneal@45 312 },
bgneal@45 313
bgneal@45 314 createNS : function(n, o) {
bgneal@45 315 var i, v;
bgneal@45 316
bgneal@45 317 o = o || win;
bgneal@45 318
bgneal@45 319 n = n.split('.');
bgneal@45 320 for (i=0; i<n.length; i++) {
bgneal@45 321 v = n[i];
bgneal@45 322
bgneal@45 323 if (!o[v])
bgneal@45 324 o[v] = {};
bgneal@45 325
bgneal@45 326 o = o[v];
bgneal@45 327 }
bgneal@45 328
bgneal@45 329 return o;
bgneal@45 330 },
bgneal@45 331
bgneal@45 332 resolve : function(n, o) {
bgneal@45 333 var i, l;
bgneal@45 334
bgneal@45 335 o = o || win;
bgneal@45 336
bgneal@45 337 n = n.split('.');
bgneal@45 338 for (i = 0, l = n.length; i < l; i++) {
bgneal@45 339 o = o[n[i]];
bgneal@45 340
bgneal@45 341 if (!o)
bgneal@45 342 break;
bgneal@45 343 }
bgneal@45 344
bgneal@45 345 return o;
bgneal@45 346 },
bgneal@45 347
bgneal@45 348 addUnload : function(f, s) {
bgneal@45 349 var t = this;
bgneal@45 350
bgneal@45 351 f = {func : f, scope : s || this};
bgneal@45 352
bgneal@45 353 if (!t.unloads) {
bgneal@45 354 function unload() {
bgneal@45 355 var li = t.unloads, o, n;
bgneal@45 356
bgneal@45 357 if (li) {
bgneal@45 358 // Call unload handlers
bgneal@45 359 for (n in li) {
bgneal@45 360 o = li[n];
bgneal@45 361
bgneal@45 362 if (o && o.func)
bgneal@45 363 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
bgneal@45 364 }
bgneal@45 365
bgneal@45 366 // Detach unload function
bgneal@45 367 if (win.detachEvent) {
bgneal@45 368 win.detachEvent('onbeforeunload', fakeUnload);
bgneal@45 369 win.detachEvent('onunload', unload);
bgneal@45 370 } else if (win.removeEventListener)
bgneal@45 371 win.removeEventListener('unload', unload, false);
bgneal@45 372
bgneal@45 373 // Destroy references
bgneal@45 374 t.unloads = o = li = w = unload = 0;
bgneal@45 375
bgneal@45 376 // Run garbarge collector on IE
bgneal@45 377 if (win.CollectGarbage)
bgneal@45 378 CollectGarbage();
bgneal@45 379 }
bgneal@45 380 };
bgneal@45 381
bgneal@45 382 function fakeUnload() {
bgneal@45 383 var d = document;
bgneal@45 384
bgneal@45 385 // Is there things still loading, then do some magic
bgneal@45 386 if (d.readyState == 'interactive') {
bgneal@45 387 function stop() {
bgneal@45 388 // Prevent memory leak
bgneal@45 389 d.detachEvent('onstop', stop);
bgneal@45 390
bgneal@45 391 // Call unload handler
bgneal@45 392 if (unload)
bgneal@45 393 unload();
bgneal@45 394
bgneal@45 395 d = 0;
bgneal@45 396 };
bgneal@45 397
bgneal@45 398 // Fire unload when the currently loading page is stopped
bgneal@45 399 if (d)
bgneal@45 400 d.attachEvent('onstop', stop);
bgneal@45 401
bgneal@45 402 // Remove onstop listener after a while to prevent the unload function
bgneal@45 403 // to execute if the user presses cancel in an onbeforeunload
bgneal@45 404 // confirm dialog and then presses the browser stop button
bgneal@45 405 win.setTimeout(function() {
bgneal@45 406 if (d)
bgneal@45 407 d.detachEvent('onstop', stop);
bgneal@45 408 }, 0);
bgneal@45 409 }
bgneal@45 410 };
bgneal@45 411
bgneal@45 412 // Attach unload handler
bgneal@45 413 if (win.attachEvent) {
bgneal@45 414 win.attachEvent('onunload', unload);
bgneal@45 415 win.attachEvent('onbeforeunload', fakeUnload);
bgneal@45 416 } else if (win.addEventListener)
bgneal@45 417 win.addEventListener('unload', unload, false);
bgneal@45 418
bgneal@45 419 // Setup initial unload handler array
bgneal@45 420 t.unloads = [f];
bgneal@45 421 } else
bgneal@45 422 t.unloads.push(f);
bgneal@45 423
bgneal@45 424 return f;
bgneal@45 425 },
bgneal@45 426
bgneal@45 427 removeUnload : function(f) {
bgneal@45 428 var u = this.unloads, r = null;
bgneal@45 429
bgneal@45 430 tinymce.each(u, function(o, i) {
bgneal@45 431 if (o && o.func == f) {
bgneal@45 432 u.splice(i, 1);
bgneal@45 433 r = f;
bgneal@45 434 return false;
bgneal@45 435 }
bgneal@45 436 });
bgneal@45 437
bgneal@45 438 return r;
bgneal@45 439 },
bgneal@45 440
bgneal@45 441 explode : function(s, d) {
bgneal@45 442 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
bgneal@45 443 },
bgneal@45 444
bgneal@45 445 _addVer : function(u) {
bgneal@45 446 var v;
bgneal@45 447
bgneal@45 448 if (!this.query)
bgneal@45 449 return u;
bgneal@45 450
bgneal@45 451 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
bgneal@45 452
bgneal@45 453 if (u.indexOf('#') == -1)
bgneal@45 454 return u + v;
bgneal@45 455
bgneal@45 456 return u.replace('#', v + '#');
bgneal@45 457 },
bgneal@45 458
bgneal@45 459 // Fix function for IE 9 where regexps isn't working correctly
bgneal@45 460 // Todo: remove me once MS fixes the bug
bgneal@45 461 _replace : function(find, replace, str) {
bgneal@45 462 // On IE9 we have to fake $x replacement
bgneal@45 463 if (isRegExpBroken) {
bgneal@45 464 return str.replace(find, function() {
bgneal@45 465 var val = replace, args = arguments, i;
bgneal@45 466
bgneal@45 467 for (i = 0; i < args.length - 2; i++) {
bgneal@45 468 if (args[i] === undefined) {
bgneal@45 469 val = val.replace(new RegExp('\\$' + i, 'g'), '');
bgneal@45 470 } else {
bgneal@45 471 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
bgneal@45 472 }
bgneal@45 473 }
bgneal@45 474
bgneal@45 475 return val;
bgneal@45 476 });
bgneal@45 477 }
bgneal@45 478
bgneal@45 479 return str.replace(find, replace);
bgneal@45 480 }
bgneal@45 481
bgneal@45 482 };
bgneal@45 483
bgneal@45 484 // Initialize the API
bgneal@45 485 tinymce._init();
bgneal@45 486
bgneal@45 487 // Expose tinymce namespace to the global namespace (window)
bgneal@45 488 win.tinymce = win.tinyMCE = tinymce;
bgneal@45 489
bgneal@45 490 // Describe the different namespaces
bgneal@45 491
bgneal@45 492 })(window);
bgneal@45 493
bgneal@45 494
bgneal@45 495
bgneal@45 496 tinymce.create('tinymce.util.Dispatcher', {
bgneal@45 497 scope : null,
bgneal@45 498 listeners : null,
bgneal@45 499
bgneal@45 500 Dispatcher : function(s) {
bgneal@45 501 this.scope = s || this;
bgneal@45 502 this.listeners = [];
bgneal@45 503 },
bgneal@45 504
bgneal@45 505 add : function(cb, s) {
bgneal@45 506 this.listeners.push({cb : cb, scope : s || this.scope});
bgneal@45 507
bgneal@45 508 return cb;
bgneal@45 509 },
bgneal@45 510
bgneal@45 511 addToTop : function(cb, s) {
bgneal@45 512 this.listeners.unshift({cb : cb, scope : s || this.scope});
bgneal@45 513
bgneal@45 514 return cb;
bgneal@45 515 },
bgneal@45 516
bgneal@45 517 remove : function(cb) {
bgneal@45 518 var l = this.listeners, o = null;
bgneal@45 519
bgneal@45 520 tinymce.each(l, function(c, i) {
bgneal@45 521 if (cb == c.cb) {
bgneal@45 522 o = cb;
bgneal@45 523 l.splice(i, 1);
bgneal@45 524 return false;
bgneal@45 525 }
bgneal@45 526 });
bgneal@45 527
bgneal@45 528 return o;
bgneal@45 529 },
bgneal@45 530
bgneal@45 531 dispatch : function() {
bgneal@45 532 var s, a = arguments, i, li = this.listeners, c;
bgneal@45 533
bgneal@45 534 // Needs to be a real loop since the listener count might change while looping
bgneal@45 535 // And this is also more efficient
bgneal@45 536 for (i = 0; i<li.length; i++) {
bgneal@45 537 c = li[i];
bgneal@45 538 s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);
bgneal@45 539
bgneal@45 540 if (s === false)
bgneal@45 541 break;
bgneal@45 542 }
bgneal@45 543
bgneal@45 544 return s;
bgneal@45 545 }
bgneal@45 546
bgneal@45 547 });
bgneal@45 548
bgneal@45 549 (function() {
bgneal@45 550 var each = tinymce.each;
bgneal@45 551
bgneal@45 552 tinymce.create('tinymce.util.URI', {
bgneal@45 553 URI : function(u, s) {
bgneal@45 554 var t = this, o, a, b, base_url;
bgneal@45 555
bgneal@45 556 // Trim whitespace
bgneal@45 557 u = tinymce.trim(u);
bgneal@45 558
bgneal@45 559 // Default settings
bgneal@45 560 s = t.settings = s || {};
bgneal@45 561
bgneal@45 562 // Strange app protocol that isn't http/https or local anchor
bgneal@45 563 // For example: mailto,skype,tel etc.
bgneal@45 564 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
bgneal@45 565 t.source = u;
bgneal@45 566 return;
bgneal@45 567 }
bgneal@45 568
bgneal@45 569 // Absolute path with no host, fake host and protocol
bgneal@45 570 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
bgneal@45 571 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
bgneal@45 572
bgneal@45 573 // Relative path http:// or protocol relative //path
bgneal@45 574 if (!/^[\w-]*:?\/\//.test(u)) {
bgneal@45 575 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
bgneal@45 576 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
bgneal@45 577 }
bgneal@45 578
bgneal@45 579 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
bgneal@45 580 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
bgneal@45 581 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
bgneal@45 582 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
bgneal@45 583 var s = u[i];
bgneal@45 584
bgneal@45 585 // Zope 3 workaround, they use @@something
bgneal@45 586 if (s)
bgneal@45 587 s = s.replace(/\(mce_at\)/g, '@@');
bgneal@45 588
bgneal@45 589 t[v] = s;
bgneal@45 590 });
bgneal@45 591
bgneal@45 592 if (b = s.base_uri) {
bgneal@45 593 if (!t.protocol)
bgneal@45 594 t.protocol = b.protocol;
bgneal@45 595
bgneal@45 596 if (!t.userInfo)
bgneal@45 597 t.userInfo = b.userInfo;
bgneal@45 598
bgneal@45 599 if (!t.port && t.host == 'mce_host')
bgneal@45 600 t.port = b.port;
bgneal@45 601
bgneal@45 602 if (!t.host || t.host == 'mce_host')
bgneal@45 603 t.host = b.host;
bgneal@45 604
bgneal@45 605 t.source = '';
bgneal@45 606 }
bgneal@45 607
bgneal@45 608 //t.path = t.path || '/';
bgneal@45 609 },
bgneal@45 610
bgneal@45 611 setPath : function(p) {
bgneal@45 612 var t = this;
bgneal@45 613
bgneal@45 614 p = /^(.*?)\/?(\w+)?$/.exec(p);
bgneal@45 615
bgneal@45 616 // Update path parts
bgneal@45 617 t.path = p[0];
bgneal@45 618 t.directory = p[1];
bgneal@45 619 t.file = p[2];
bgneal@45 620
bgneal@45 621 // Rebuild source
bgneal@45 622 t.source = '';
bgneal@45 623 t.getURI();
bgneal@45 624 },
bgneal@45 625
bgneal@45 626 toRelative : function(u) {
bgneal@45 627 var t = this, o;
bgneal@45 628
bgneal@45 629 if (u === "./")
bgneal@45 630 return u;
bgneal@45 631
bgneal@45 632 u = new tinymce.util.URI(u, {base_uri : t});
bgneal@45 633
bgneal@45 634 // Not on same domain/port or protocol
bgneal@45 635 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
bgneal@45 636 return u.getURI();
bgneal@45 637
bgneal@45 638 o = t.toRelPath(t.path, u.path);
bgneal@45 639
bgneal@45 640 // Add query
bgneal@45 641 if (u.query)
bgneal@45 642 o += '?' + u.query;
bgneal@45 643
bgneal@45 644 // Add anchor
bgneal@45 645 if (u.anchor)
bgneal@45 646 o += '#' + u.anchor;
bgneal@45 647
bgneal@45 648 return o;
bgneal@45 649 },
bgneal@45 650
bgneal@45 651 toAbsolute : function(u, nh) {
bgneal@45 652 var u = new tinymce.util.URI(u, {base_uri : this});
bgneal@45 653
bgneal@45 654 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
bgneal@45 655 },
bgneal@45 656
bgneal@45 657 toRelPath : function(base, path) {
bgneal@45 658 var items, bp = 0, out = '', i, l;
bgneal@45 659
bgneal@45 660 // Split the paths
bgneal@45 661 base = base.substring(0, base.lastIndexOf('/'));
bgneal@45 662 base = base.split('/');
bgneal@45 663 items = path.split('/');
bgneal@45 664
bgneal@45 665 if (base.length >= items.length) {
bgneal@45 666 for (i = 0, l = base.length; i < l; i++) {
bgneal@45 667 if (i >= items.length || base[i] != items[i]) {
bgneal@45 668 bp = i + 1;
bgneal@45 669 break;
bgneal@45 670 }
bgneal@45 671 }
bgneal@45 672 }
bgneal@45 673
bgneal@45 674 if (base.length < items.length) {
bgneal@45 675 for (i = 0, l = items.length; i < l; i++) {
bgneal@45 676 if (i >= base.length || base[i] != items[i]) {
bgneal@45 677 bp = i + 1;
bgneal@45 678 break;
bgneal@45 679 }
bgneal@45 680 }
bgneal@45 681 }
bgneal@45 682
bgneal@45 683 if (bp == 1)
bgneal@45 684 return path;
bgneal@45 685
bgneal@45 686 for (i = 0, l = base.length - (bp - 1); i < l; i++)
bgneal@45 687 out += "../";
bgneal@45 688
bgneal@45 689 for (i = bp - 1, l = items.length; i < l; i++) {
bgneal@45 690 if (i != bp - 1)
bgneal@45 691 out += "/" + items[i];
bgneal@45 692 else
bgneal@45 693 out += items[i];
bgneal@45 694 }
bgneal@45 695
bgneal@45 696 return out;
bgneal@45 697 },
bgneal@45 698
bgneal@45 699 toAbsPath : function(base, path) {
bgneal@45 700 var i, nb = 0, o = [], tr, outPath;
bgneal@45 701
bgneal@45 702 // Split paths
bgneal@45 703 tr = /\/$/.test(path) ? '/' : '';
bgneal@45 704 base = base.split('/');
bgneal@45 705 path = path.split('/');
bgneal@45 706
bgneal@45 707 // Remove empty chunks
bgneal@45 708 each(base, function(k) {
bgneal@45 709 if (k)
bgneal@45 710 o.push(k);
bgneal@45 711 });
bgneal@45 712
bgneal@45 713 base = o;
bgneal@45 714
bgneal@45 715 // Merge relURLParts chunks
bgneal@45 716 for (i = path.length - 1, o = []; i >= 0; i--) {
bgneal@45 717 // Ignore empty or .
bgneal@45 718 if (path[i].length == 0 || path[i] == ".")
bgneal@45 719 continue;
bgneal@45 720
bgneal@45 721 // Is parent
bgneal@45 722 if (path[i] == '..') {
bgneal@45 723 nb++;
bgneal@45 724 continue;
bgneal@45 725 }
bgneal@45 726
bgneal@45 727 // Move up
bgneal@45 728 if (nb > 0) {
bgneal@45 729 nb--;
bgneal@45 730 continue;
bgneal@45 731 }
bgneal@45 732
bgneal@45 733 o.push(path[i]);
bgneal@45 734 }
bgneal@45 735
bgneal@45 736 i = base.length - nb;
bgneal@45 737
bgneal@45 738 // If /a/b/c or /
bgneal@45 739 if (i <= 0)
bgneal@45 740 outPath = o.reverse().join('/');
bgneal@45 741 else
bgneal@45 742 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
bgneal@45 743
bgneal@45 744 // Add front / if it's needed
bgneal@45 745 if (outPath.indexOf('/') !== 0)
bgneal@45 746 outPath = '/' + outPath;
bgneal@45 747
bgneal@45 748 // Add traling / if it's needed
bgneal@45 749 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
bgneal@45 750 outPath += tr;
bgneal@45 751
bgneal@45 752 return outPath;
bgneal@45 753 },
bgneal@45 754
bgneal@45 755 getURI : function(nh) {
bgneal@45 756 var s, t = this;
bgneal@45 757
bgneal@45 758 // Rebuild source
bgneal@45 759 if (!t.source || nh) {
bgneal@45 760 s = '';
bgneal@45 761
bgneal@45 762 if (!nh) {
bgneal@45 763 if (t.protocol)
bgneal@45 764 s += t.protocol + '://';
bgneal@45 765
bgneal@45 766 if (t.userInfo)
bgneal@45 767 s += t.userInfo + '@';
bgneal@45 768
bgneal@45 769 if (t.host)
bgneal@45 770 s += t.host;
bgneal@45 771
bgneal@45 772 if (t.port)
bgneal@45 773 s += ':' + t.port;
bgneal@45 774 }
bgneal@45 775
bgneal@45 776 if (t.path)
bgneal@45 777 s += t.path;
bgneal@45 778
bgneal@45 779 if (t.query)
bgneal@45 780 s += '?' + t.query;
bgneal@45 781
bgneal@45 782 if (t.anchor)
bgneal@45 783 s += '#' + t.anchor;
bgneal@45 784
bgneal@45 785 t.source = s;
bgneal@45 786 }
bgneal@45 787
bgneal@45 788 return t.source;
bgneal@45 789 }
bgneal@45 790 });
bgneal@45 791 })();
bgneal@45 792
bgneal@45 793 (function() {
bgneal@45 794 var each = tinymce.each;
bgneal@45 795
bgneal@45 796 tinymce.create('static tinymce.util.Cookie', {
bgneal@45 797 getHash : function(n) {
bgneal@45 798 var v = this.get(n), h;
bgneal@45 799
bgneal@45 800 if (v) {
bgneal@45 801 each(v.split('&'), function(v) {
bgneal@45 802 v = v.split('=');
bgneal@45 803 h = h || {};
bgneal@45 804 h[unescape(v[0])] = unescape(v[1]);
bgneal@45 805 });
bgneal@45 806 }
bgneal@45 807
bgneal@45 808 return h;
bgneal@45 809 },
bgneal@45 810
bgneal@45 811 setHash : function(n, v, e, p, d, s) {
bgneal@45 812 var o = '';
bgneal@45 813
bgneal@45 814 each(v, function(v, k) {
bgneal@45 815 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
bgneal@45 816 });
bgneal@45 817
bgneal@45 818 this.set(n, o, e, p, d, s);
bgneal@45 819 },
bgneal@45 820
bgneal@45 821 get : function(n) {
bgneal@45 822 var c = document.cookie, e, p = n + "=", b;
bgneal@45 823
bgneal@45 824 // Strict mode
bgneal@45 825 if (!c)
bgneal@45 826 return;
bgneal@45 827
bgneal@45 828 b = c.indexOf("; " + p);
bgneal@45 829
bgneal@45 830 if (b == -1) {
bgneal@45 831 b = c.indexOf(p);
bgneal@45 832
bgneal@45 833 if (b != 0)
bgneal@45 834 return null;
bgneal@45 835 } else
bgneal@45 836 b += 2;
bgneal@45 837
bgneal@45 838 e = c.indexOf(";", b);
bgneal@45 839
bgneal@45 840 if (e == -1)
bgneal@45 841 e = c.length;
bgneal@45 842
bgneal@45 843 return unescape(c.substring(b + p.length, e));
bgneal@45 844 },
bgneal@45 845
bgneal@45 846 set : function(n, v, e, p, d, s) {
bgneal@45 847 document.cookie = n + "=" + escape(v) +
bgneal@45 848 ((e) ? "; expires=" + e.toGMTString() : "") +
bgneal@45 849 ((p) ? "; path=" + escape(p) : "") +
bgneal@45 850 ((d) ? "; domain=" + d : "") +
bgneal@45 851 ((s) ? "; secure" : "");
bgneal@45 852 },
bgneal@45 853
bgneal@45 854 remove : function(n, p) {
bgneal@45 855 var d = new Date();
bgneal@45 856
bgneal@45 857 d.setTime(d.getTime() - 1000);
bgneal@45 858
bgneal@45 859 this.set(n, '', d, p, d);
bgneal@45 860 }
bgneal@45 861 });
bgneal@45 862 })();
bgneal@45 863
bgneal@45 864 (function() {
bgneal@45 865 function serialize(o, quote) {
bgneal@45 866 var i, v, t;
bgneal@45 867
bgneal@45 868 quote = quote || '"';
bgneal@45 869
bgneal@45 870 if (o == null)
bgneal@45 871 return 'null';
bgneal@45 872
bgneal@45 873 t = typeof o;
bgneal@45 874
bgneal@45 875 if (t == 'string') {
bgneal@45 876 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
bgneal@45 877
bgneal@45 878 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
bgneal@45 879 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
bgneal@45 880 if (quote === '"' && a === "'")
bgneal@45 881 return a;
bgneal@45 882
bgneal@45 883 i = v.indexOf(b);
bgneal@45 884
bgneal@45 885 if (i + 1)
bgneal@45 886 return '\\' + v.charAt(i + 1);
bgneal@45 887
bgneal@45 888 a = b.charCodeAt().toString(16);
bgneal@45 889
bgneal@45 890 return '\\u' + '0000'.substring(a.length) + a;
bgneal@45 891 }) + quote;
bgneal@45 892 }
bgneal@45 893
bgneal@45 894 if (t == 'object') {
bgneal@45 895 if (o.hasOwnProperty && o instanceof Array) {
bgneal@45 896 for (i=0, v = '['; i<o.length; i++)
bgneal@45 897 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
bgneal@45 898
bgneal@45 899 return v + ']';
bgneal@45 900 }
bgneal@45 901
bgneal@45 902 v = '{';
bgneal@45 903
bgneal@45 904 for (i in o) {
bgneal@45 905 if (o.hasOwnProperty(i)) {
bgneal@45 906 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
bgneal@45 907 }
bgneal@45 908 }
bgneal@45 909
bgneal@45 910 return v + '}';
bgneal@45 911 }
bgneal@45 912
bgneal@45 913 return '' + o;
bgneal@45 914 };
bgneal@45 915
bgneal@45 916 tinymce.util.JSON = {
bgneal@45 917 serialize: serialize,
bgneal@45 918
bgneal@45 919 parse: function(s) {
bgneal@45 920 try {
bgneal@45 921 return eval('(' + s + ')');
bgneal@45 922 } catch (ex) {
bgneal@45 923 // Ignore
bgneal@45 924 }
bgneal@45 925 }
bgneal@45 926
bgneal@45 927 };
bgneal@45 928 })();
bgneal@45 929
bgneal@45 930 tinymce.create('static tinymce.util.XHR', {
bgneal@45 931 send : function(o) {
bgneal@45 932 var x, t, w = window, c = 0;
bgneal@45 933
bgneal@45 934 // Default settings
bgneal@45 935 o.scope = o.scope || this;
bgneal@45 936 o.success_scope = o.success_scope || o.scope;
bgneal@45 937 o.error_scope = o.error_scope || o.scope;
bgneal@45 938 o.async = o.async === false ? false : true;
bgneal@45 939 o.data = o.data || '';
bgneal@45 940
bgneal@45 941 function get(s) {
bgneal@45 942 x = 0;
bgneal@45 943
bgneal@45 944 try {
bgneal@45 945 x = new ActiveXObject(s);
bgneal@45 946 } catch (ex) {
bgneal@45 947 }
bgneal@45 948
bgneal@45 949 return x;
bgneal@45 950 };
bgneal@45 951
bgneal@45 952 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
bgneal@45 953
bgneal@45 954 if (x) {
bgneal@45 955 if (x.overrideMimeType)
bgneal@45 956 x.overrideMimeType(o.content_type);
bgneal@45 957
bgneal@45 958 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
bgneal@45 959
bgneal@45 960 if (o.content_type)
bgneal@45 961 x.setRequestHeader('Content-Type', o.content_type);
bgneal@45 962
bgneal@45 963 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bgneal@45 964
bgneal@45 965 x.send(o.data);
bgneal@45 966
bgneal@45 967 function ready() {
bgneal@45 968 if (!o.async || x.readyState == 4 || c++ > 10000) {
bgneal@45 969 if (o.success && c < 10000 && x.status == 200)
bgneal@45 970 o.success.call(o.success_scope, '' + x.responseText, x, o);
bgneal@45 971 else if (o.error)
bgneal@45 972 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
bgneal@45 973
bgneal@45 974 x = null;
bgneal@45 975 } else
bgneal@45 976 w.setTimeout(ready, 10);
bgneal@45 977 };
bgneal@45 978
bgneal@45 979 // Syncronous request
bgneal@45 980 if (!o.async)
bgneal@45 981 return ready();
bgneal@45 982
bgneal@45 983 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
bgneal@45 984 t = w.setTimeout(ready, 10);
bgneal@45 985 }
bgneal@45 986 }
bgneal@45 987 });
bgneal@45 988
bgneal@45 989 (function() {
bgneal@45 990 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
bgneal@45 991
bgneal@45 992 tinymce.create('tinymce.util.JSONRequest', {
bgneal@45 993 JSONRequest : function(s) {
bgneal@45 994 this.settings = extend({
bgneal@45 995 }, s);
bgneal@45 996 this.count = 0;
bgneal@45 997 },
bgneal@45 998
bgneal@45 999 send : function(o) {
bgneal@45 1000 var ecb = o.error, scb = o.success;
bgneal@45 1001
bgneal@45 1002 o = extend(this.settings, o);
bgneal@45 1003
bgneal@45 1004 o.success = function(c, x) {
bgneal@45 1005 c = JSON.parse(c);
bgneal@45 1006
bgneal@45 1007 if (typeof(c) == 'undefined') {
bgneal@45 1008 c = {
bgneal@45 1009 error : 'JSON Parse error.'
bgneal@45 1010 };
bgneal@45 1011 }
bgneal@45 1012
bgneal@45 1013 if (c.error)
bgneal@45 1014 ecb.call(o.error_scope || o.scope, c.error, x);
bgneal@45 1015 else
bgneal@45 1016 scb.call(o.success_scope || o.scope, c.result);
bgneal@45 1017 };
bgneal@45 1018
bgneal@45 1019 o.error = function(ty, x) {
bgneal@45 1020 if (ecb)
bgneal@45 1021 ecb.call(o.error_scope || o.scope, ty, x);
bgneal@45 1022 };
bgneal@45 1023
bgneal@45 1024 o.data = JSON.serialize({
bgneal@45 1025 id : o.id || 'c' + (this.count++),
bgneal@45 1026 method : o.method,
bgneal@45 1027 params : o.params
bgneal@45 1028 });
bgneal@45 1029
bgneal@45 1030 // JSON content type for Ruby on rails. Bug: #1883287
bgneal@45 1031 o.content_type = 'application/json';
bgneal@45 1032
bgneal@45 1033 XHR.send(o);
bgneal@45 1034 },
bgneal@45 1035
bgneal@45 1036 'static' : {
bgneal@45 1037 sendRPC : function(o) {
bgneal@45 1038 return new tinymce.util.JSONRequest().send(o);
bgneal@45 1039 }
bgneal@45 1040 }
bgneal@45 1041 });
bgneal@45 1042 }());
bgneal@45 1043 (function(tinymce){
bgneal@45 1044 tinymce.VK = {
bgneal@45 1045 BACKSPACE: 8,
bgneal@45 1046 DELETE: 46,
bgneal@45 1047 DOWN: 40,
bgneal@45 1048 ENTER: 13,
bgneal@45 1049 LEFT: 37,
bgneal@45 1050 RIGHT: 39,
bgneal@45 1051 SPACEBAR: 32,
bgneal@45 1052 TAB: 9,
bgneal@45 1053 UP: 38,
bgneal@45 1054
bgneal@45 1055 modifierPressed: function (e) {
bgneal@45 1056 return e.shiftKey || e.ctrlKey || e.altKey;
bgneal@45 1057 }
bgneal@45 1058 }
bgneal@45 1059 })(tinymce);
bgneal@45 1060
bgneal@45 1061 (function(tinymce) {
bgneal@45 1062 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
bgneal@45 1063
bgneal@45 1064 function cleanupStylesWhenDeleting(ed) {
bgneal@45 1065 var dom = ed.dom, selection = ed.selection;
bgneal@45 1066
bgneal@45 1067 ed.onKeyDown.add(function(ed, e) {
bgneal@45 1068 var rng, blockElm, node, clonedSpan, isDelete;
bgneal@45 1069
bgneal@45 1070 if (e.isDefaultPrevented()) {
bgneal@45 1071 return;
bgneal@45 1072 }
bgneal@45 1073
bgneal@45 1074 isDelete = e.keyCode == DELETE;
bgneal@45 1075 if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
bgneal@45 1076 e.preventDefault();
bgneal@45 1077 rng = selection.getRng();
bgneal@45 1078
bgneal@45 1079 // Find root block
bgneal@45 1080 blockElm = dom.getParent(rng.startContainer, dom.isBlock);
bgneal@45 1081
bgneal@45 1082 // On delete clone the root span of the next block element
bgneal@45 1083 if (isDelete)
bgneal@45 1084 blockElm = dom.getNext(blockElm, dom.isBlock);
bgneal@45 1085
bgneal@45 1086 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
bgneal@45 1087 if (blockElm) {
bgneal@45 1088 node = blockElm.firstChild;
bgneal@45 1089
bgneal@45 1090 // Ignore empty text nodes
bgneal@45 1091 while (node && node.nodeType == 3 && node.nodeValue.length == 0)
bgneal@45 1092 node = node.nextSibling;
bgneal@45 1093
bgneal@45 1094 if (node && node.nodeName === 'SPAN') {
bgneal@45 1095 clonedSpan = node.cloneNode(false);
bgneal@45 1096 }
bgneal@45 1097 }
bgneal@45 1098
bgneal@45 1099 // Do the backspace/delete action
bgneal@45 1100 ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
bgneal@45 1101
bgneal@45 1102 // Find all odd apple-style-spans
bgneal@45 1103 blockElm = dom.getParent(rng.startContainer, dom.isBlock);
bgneal@45 1104 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
bgneal@45 1105 var bm = selection.getBookmark();
bgneal@45 1106
bgneal@45 1107 if (clonedSpan) {
bgneal@45 1108 dom.replace(clonedSpan.cloneNode(false), span, true);
bgneal@45 1109 } else {
bgneal@45 1110 dom.remove(span, true);
bgneal@45 1111 }
bgneal@45 1112
bgneal@45 1113 // Restore the selection
bgneal@45 1114 selection.moveToBookmark(bm);
bgneal@45 1115 });
bgneal@45 1116 }
bgneal@45 1117 });
bgneal@45 1118 };
bgneal@45 1119
bgneal@45 1120 function emptyEditorWhenDeleting(ed) {
bgneal@45 1121 function serializeRng(rng) {
bgneal@45 1122 var body = ed.dom.create("body");
bgneal@45 1123 var contents = rng.cloneContents();
bgneal@45 1124 body.appendChild(contents);
bgneal@45 1125 return ed.selection.serializer.serialize(body, {format: 'html'});
bgneal@45 1126 }
bgneal@45 1127
bgneal@45 1128 function allContentsSelected(rng) {
bgneal@45 1129 var selection = serializeRng(rng);
bgneal@45 1130
bgneal@45 1131 var allRng = ed.dom.createRng();
bgneal@45 1132 allRng.selectNode(ed.getBody());
bgneal@45 1133
bgneal@45 1134 var allSelection = serializeRng(allRng);
bgneal@45 1135 return selection === allSelection;
bgneal@45 1136 }
bgneal@45 1137
bgneal@45 1138 ed.onKeyDown.addToTop(function(ed, e) {
bgneal@45 1139 var keyCode = e.keyCode;
bgneal@45 1140 if (keyCode == DELETE || keyCode == BACKSPACE) {
bgneal@45 1141 var rng = ed.selection.getRng(true);
bgneal@45 1142 if (!rng.collapsed && allContentsSelected(rng)) {
bgneal@45 1143 ed.setContent('', {format : 'raw'});
bgneal@45 1144 ed.nodeChanged();
bgneal@45 1145 e.preventDefault();
bgneal@45 1146 }
bgneal@45 1147 }
bgneal@45 1148 });
bgneal@45 1149 };
bgneal@45 1150
bgneal@45 1151 function inputMethodFocus(ed) {
bgneal@45 1152 ed.dom.bind(ed.getDoc(), 'focusin', function() {
bgneal@45 1153 ed.selection.setRng(ed.selection.getRng());
bgneal@45 1154 });
bgneal@45 1155 };
bgneal@45 1156
bgneal@45 1157 function removeHrOnBackspace(ed) {
bgneal@45 1158 ed.onKeyDown.add(function(ed, e) {
bgneal@45 1159 if (e.keyCode === BACKSPACE) {
bgneal@45 1160 if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
bgneal@45 1161 var node = ed.selection.getNode();
bgneal@45 1162 var previousSibling = node.previousSibling;
bgneal@45 1163 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
bgneal@45 1164 ed.dom.remove(previousSibling);
bgneal@45 1165 tinymce.dom.Event.cancel(e);
bgneal@45 1166 }
bgneal@45 1167 }
bgneal@45 1168 }
bgneal@45 1169 })
bgneal@45 1170 }
bgneal@45 1171
bgneal@45 1172 function focusBody(ed) {
bgneal@45 1173 // Fix for a focus bug in FF 3.x where the body element
bgneal@45 1174 // wouldn't get proper focus if the user clicked on the HTML element
bgneal@45 1175 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
bgneal@45 1176 ed.onMouseDown.add(function(ed, e) {
bgneal@45 1177 if (e.target.nodeName === "HTML") {
bgneal@45 1178 var body = ed.getBody();
bgneal@45 1179
bgneal@45 1180 // Blur the body it's focused but not correctly focused
bgneal@45 1181 body.blur();
bgneal@45 1182
bgneal@45 1183 // Refocus the body after a little while
bgneal@45 1184 setTimeout(function() {
bgneal@45 1185 body.focus();
bgneal@45 1186 }, 0);
bgneal@45 1187 }
bgneal@45 1188 });
bgneal@45 1189 }
bgneal@45 1190 };
bgneal@45 1191
bgneal@45 1192 function selectControlElements(ed) {
bgneal@45 1193 ed.onClick.add(function(ed, e) {
bgneal@45 1194 e = e.target;
bgneal@45 1195
bgneal@45 1196 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
bgneal@45 1197 // WebKit can't even do simple things like selecting an image
bgneal@45 1198 // Needs tobe the setBaseAndExtend or it will fail to select floated images
bgneal@45 1199 if (/^(IMG|HR)$/.test(e.nodeName))
bgneal@45 1200 ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);
bgneal@45 1201
bgneal@45 1202 if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))
bgneal@45 1203 ed.selection.select(e);
bgneal@45 1204
bgneal@45 1205 ed.nodeChanged();
bgneal@45 1206 });
bgneal@45 1207 };
bgneal@45 1208
bgneal@45 1209 function removeStylesWhenDeletingAccrossBlockElements(ed) {
bgneal@45 1210 var selection = ed.selection, dom = ed.dom;
bgneal@45 1211
bgneal@45 1212 function getAttributeApplyFunction() {
bgneal@45 1213 var template = dom.getAttribs(selection.getStart().cloneNode(false));
bgneal@45 1214
bgneal@45 1215 return function() {
bgneal@45 1216 var target = selection.getStart();
bgneal@45 1217
bgneal@45 1218 if (target !== ed.getBody()) {
bgneal@45 1219 dom.setAttrib(target, "style", null);
bgneal@45 1220
bgneal@45 1221 tinymce.each(template, function(attr) {
bgneal@45 1222 target.setAttributeNode(attr.cloneNode(true));
bgneal@45 1223 });
bgneal@45 1224 }
bgneal@45 1225 };
bgneal@45 1226 }
bgneal@45 1227
bgneal@45 1228 function isSelectionAcrossElements() {
bgneal@45 1229 return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
bgneal@45 1230 }
bgneal@45 1231
bgneal@45 1232 function blockEvent(ed, e) {
bgneal@45 1233 e.preventDefault();
bgneal@45 1234 return false;
bgneal@45 1235 }
bgneal@45 1236
bgneal@45 1237 ed.onKeyPress.add(function(ed, e) {
bgneal@45 1238 var applyAttributes;
bgneal@45 1239
bgneal@45 1240 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
bgneal@45 1241 applyAttributes = getAttributeApplyFunction();
bgneal@45 1242 ed.getDoc().execCommand('delete', false, null);
bgneal@45 1243 applyAttributes();
bgneal@45 1244 e.preventDefault();
bgneal@45 1245 return false;
bgneal@45 1246 }
bgneal@45 1247 });
bgneal@45 1248
bgneal@45 1249 dom.bind(ed.getDoc(), 'cut', function(e) {
bgneal@45 1250 var applyAttributes;
bgneal@45 1251
bgneal@45 1252 if (isSelectionAcrossElements()) {
bgneal@45 1253 applyAttributes = getAttributeApplyFunction();
bgneal@45 1254 ed.onKeyUp.addToTop(blockEvent);
bgneal@45 1255
bgneal@45 1256 setTimeout(function() {
bgneal@45 1257 applyAttributes();
bgneal@45 1258 ed.onKeyUp.remove(blockEvent);
bgneal@45 1259 }, 0);
bgneal@45 1260 }
bgneal@45 1261 });
bgneal@45 1262 }
bgneal@45 1263
bgneal@45 1264 /*
bgneal@45 1265 function removeStylesOnPTagsInheritedFromHeadingTag(ed) {
bgneal@45 1266 ed.onKeyDown.add(function(ed, event) {
bgneal@45 1267 function checkInHeadingTag(ed) {
bgneal@45 1268 var currentNode = ed.selection.getNode();
bgneal@45 1269 var headingTags = 'h1,h2,h3,h4,h5,h6';
bgneal@45 1270 return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;
bgneal@45 1271 }
bgneal@45 1272
bgneal@45 1273 if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {
bgneal@45 1274 setTimeout(function() {
bgneal@45 1275 var currentNode = ed.selection.getNode();
bgneal@45 1276 if (ed.dom.is(currentNode, 'p')) {
bgneal@45 1277 ed.dom.setAttrib(currentNode, 'style', null);
bgneal@45 1278 // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'
bgneal@45 1279 ed.execCommand('mceCleanup');
bgneal@45 1280 }
bgneal@45 1281 }, 0);
bgneal@45 1282 }
bgneal@45 1283 });
bgneal@45 1284 }
bgneal@45 1285 */
bgneal@45 1286
bgneal@45 1287 function selectionChangeNodeChanged(ed) {
bgneal@45 1288 var lastRng, selectionTimer;
bgneal@45 1289
bgneal@45 1290 ed.dom.bind(ed.getDoc(), 'selectionchange', function() {
bgneal@45 1291 if (selectionTimer) {
bgneal@45 1292 clearTimeout(selectionTimer);
bgneal@45 1293 selectionTimer = 0;
bgneal@45 1294 }
bgneal@45 1295
bgneal@45 1296 selectionTimer = window.setTimeout(function() {
bgneal@45 1297 var rng = ed.selection.getRng();
bgneal@45 1298
bgneal@45 1299 // Compare the ranges to see if it was a real change or not
bgneal@45 1300 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
bgneal@45 1301 ed.nodeChanged();
bgneal@45 1302 lastRng = rng;
bgneal@45 1303 }
bgneal@45 1304 }, 50);
bgneal@45 1305 });
bgneal@45 1306 }
bgneal@45 1307
bgneal@45 1308 function ensureBodyHasRoleApplication(ed) {
bgneal@45 1309 document.body.setAttribute("role", "application");
bgneal@45 1310 }
bgneal@45 1311
bgneal@45 1312 function disableBackspaceIntoATable(ed) {
bgneal@45 1313 ed.onKeyDown.add(function(ed, e) {
bgneal@45 1314 if (e.keyCode === BACKSPACE) {
bgneal@45 1315 if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
bgneal@45 1316 var previousSibling = ed.selection.getNode().previousSibling;
bgneal@45 1317 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
bgneal@45 1318 return tinymce.dom.Event.cancel(e);
bgneal@45 1319 }
bgneal@45 1320 }
bgneal@45 1321 }
bgneal@45 1322 })
bgneal@45 1323 }
bgneal@45 1324
bgneal@45 1325 tinymce.create('tinymce.util.Quirks', {
bgneal@45 1326 Quirks: function(ed) {
bgneal@45 1327 // All browsers
bgneal@45 1328 disableBackspaceIntoATable(ed);
bgneal@45 1329
bgneal@45 1330 // WebKit
bgneal@45 1331 if (tinymce.isWebKit) {
bgneal@45 1332 cleanupStylesWhenDeleting(ed);
bgneal@45 1333 emptyEditorWhenDeleting(ed);
bgneal@45 1334 inputMethodFocus(ed);
bgneal@45 1335 selectControlElements(ed);
bgneal@45 1336
bgneal@45 1337 // iOS
bgneal@45 1338 if (tinymce.isIDevice) {
bgneal@45 1339 selectionChangeNodeChanged(ed);
bgneal@45 1340 }
bgneal@45 1341 }
bgneal@45 1342
bgneal@45 1343 // IE
bgneal@45 1344 if (tinymce.isIE) {
bgneal@45 1345 removeHrOnBackspace(ed);
bgneal@45 1346 emptyEditorWhenDeleting(ed);
bgneal@45 1347 ensureBodyHasRoleApplication(ed);
bgneal@45 1348 //removeStylesOnPTagsInheritedFromHeadingTag(ed)
bgneal@45 1349 }
bgneal@45 1350
bgneal@45 1351 // Gecko
bgneal@45 1352 if (tinymce.isGecko) {
bgneal@45 1353 removeHrOnBackspace(ed);
bgneal@45 1354 focusBody(ed);
bgneal@45 1355 removeStylesWhenDeletingAccrossBlockElements(ed);
bgneal@45 1356 }
bgneal@45 1357 }
bgneal@45 1358 });
bgneal@45 1359 })(tinymce);
bgneal@45 1360
bgneal@45 1361 (function(tinymce) {
bgneal@45 1362 var namedEntities, baseEntities, reverseEntities,
bgneal@45 1363 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
bgneal@45 1364 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
bgneal@45 1365 rawCharsRegExp = /[<>&\"\']/g,
bgneal@45 1366 entityRegExp = /&(#x|#)?([\w]+);/g,
bgneal@45 1367 asciiMap = {
bgneal@45 1368 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
bgneal@45 1369 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
bgneal@45 1370 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
bgneal@45 1371 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
bgneal@45 1372 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
bgneal@45 1373 };
bgneal@45 1374
bgneal@45 1375 // Raw entities
bgneal@45 1376 baseEntities = {
bgneal@45 1377 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
bgneal@45 1378 "'" : '&#39;',
bgneal@45 1379 '<' : '&lt;',
bgneal@45 1380 '>' : '&gt;',
bgneal@45 1381 '&' : '&amp;'
bgneal@45 1382 };
bgneal@45 1383
bgneal@45 1384 // Reverse lookup table for raw entities
bgneal@45 1385 reverseEntities = {
bgneal@45 1386 '&lt;' : '<',
bgneal@45 1387 '&gt;' : '>',
bgneal@45 1388 '&amp;' : '&',
bgneal@45 1389 '&quot;' : '"',
bgneal@45 1390 '&apos;' : "'"
bgneal@45 1391 };
bgneal@45 1392
bgneal@45 1393 // Decodes text by using the browser
bgneal@45 1394 function nativeDecode(text) {
bgneal@45 1395 var elm;
bgneal@45 1396
bgneal@45 1397 elm = document.createElement("div");
bgneal@45 1398 elm.innerHTML = text;
bgneal@45 1399
bgneal@45 1400 return elm.textContent || elm.innerText || text;
bgneal@45 1401 };
bgneal@45 1402
bgneal@45 1403 // Build a two way lookup table for the entities
bgneal@45 1404 function buildEntitiesLookup(items, radix) {
bgneal@45 1405 var i, chr, entity, lookup = {};
bgneal@45 1406
bgneal@45 1407 if (items) {
bgneal@45 1408 items = items.split(',');
bgneal@45 1409 radix = radix || 10;
bgneal@45 1410
bgneal@45 1411 // Build entities lookup table
bgneal@45 1412 for (i = 0; i < items.length; i += 2) {
bgneal@45 1413 chr = String.fromCharCode(parseInt(items[i], radix));
bgneal@45 1414
bgneal@45 1415 // Only add non base entities
bgneal@45 1416 if (!baseEntities[chr]) {
bgneal@45 1417 entity = '&' + items[i + 1] + ';';
bgneal@45 1418 lookup[chr] = entity;
bgneal@45 1419 lookup[entity] = chr;
bgneal@45 1420 }
bgneal@45 1421 }
bgneal@45 1422
bgneal@45 1423 return lookup;
bgneal@45 1424 }
bgneal@45 1425 };
bgneal@45 1426
bgneal@45 1427 // Unpack entities lookup where the numbers are in radix 32 to reduce the size
bgneal@45 1428 namedEntities = buildEntitiesLookup(
bgneal@45 1429 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
bgneal@45 1430 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
bgneal@45 1431 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
bgneal@45 1432 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
bgneal@45 1433 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
bgneal@45 1434 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
bgneal@45 1435 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
bgneal@45 1436 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
bgneal@45 1437 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
bgneal@45 1438 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
bgneal@45 1439 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
bgneal@45 1440 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
bgneal@45 1441 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
bgneal@45 1442 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
bgneal@45 1443 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
bgneal@45 1444 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
bgneal@45 1445 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
bgneal@45 1446 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
bgneal@45 1447 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
bgneal@45 1448 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
bgneal@45 1449 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
bgneal@45 1450 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
bgneal@45 1451 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
bgneal@45 1452 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
bgneal@45 1453 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
bgneal@45 1454 , 32);
bgneal@45 1455
bgneal@45 1456 tinymce.html = tinymce.html || {};
bgneal@45 1457
bgneal@45 1458 tinymce.html.Entities = {
bgneal@45 1459 encodeRaw : function(text, attr) {
bgneal@45 1460 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@45 1461 return baseEntities[chr] || chr;
bgneal@45 1462 });
bgneal@45 1463 },
bgneal@45 1464
bgneal@45 1465 encodeAllRaw : function(text) {
bgneal@45 1466 return ('' + text).replace(rawCharsRegExp, function(chr) {
bgneal@45 1467 return baseEntities[chr] || chr;
bgneal@45 1468 });
bgneal@45 1469 },
bgneal@45 1470
bgneal@45 1471 encodeNumeric : function(text, attr) {
bgneal@45 1472 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@45 1473 // Multi byte sequence convert it to a single entity
bgneal@45 1474 if (chr.length > 1)
bgneal@45 1475 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
bgneal@45 1476
bgneal@45 1477 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
bgneal@45 1478 });
bgneal@45 1479 },
bgneal@45 1480
bgneal@45 1481 encodeNamed : function(text, attr, entities) {
bgneal@45 1482 entities = entities || namedEntities;
bgneal@45 1483
bgneal@45 1484 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@45 1485 return baseEntities[chr] || entities[chr] || chr;
bgneal@45 1486 });
bgneal@45 1487 },
bgneal@45 1488
bgneal@45 1489 getEncodeFunc : function(name, entities) {
bgneal@45 1490 var Entities = tinymce.html.Entities;
bgneal@45 1491
bgneal@45 1492 entities = buildEntitiesLookup(entities) || namedEntities;
bgneal@45 1493
bgneal@45 1494 function encodeNamedAndNumeric(text, attr) {
bgneal@45 1495 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@45 1496 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
bgneal@45 1497 });
bgneal@45 1498 };
bgneal@45 1499
bgneal@45 1500 function encodeCustomNamed(text, attr) {
bgneal@45 1501 return Entities.encodeNamed(text, attr, entities);
bgneal@45 1502 };
bgneal@45 1503
bgneal@45 1504 // Replace + with , to be compatible with previous TinyMCE versions
bgneal@45 1505 name = tinymce.makeMap(name.replace(/\+/g, ','));
bgneal@45 1506
bgneal@45 1507 // Named and numeric encoder
bgneal@45 1508 if (name.named && name.numeric)
bgneal@45 1509 return encodeNamedAndNumeric;
bgneal@45 1510
bgneal@45 1511 // Named encoder
bgneal@45 1512 if (name.named) {
bgneal@45 1513 // Custom names
bgneal@45 1514 if (entities)
bgneal@45 1515 return encodeCustomNamed;
bgneal@45 1516
bgneal@45 1517 return Entities.encodeNamed;
bgneal@45 1518 }
bgneal@45 1519
bgneal@45 1520 // Numeric
bgneal@45 1521 if (name.numeric)
bgneal@45 1522 return Entities.encodeNumeric;
bgneal@45 1523
bgneal@45 1524 // Raw encoder
bgneal@45 1525 return Entities.encodeRaw;
bgneal@45 1526 },
bgneal@45 1527
bgneal@45 1528 decode : function(text) {
bgneal@45 1529 return text.replace(entityRegExp, function(all, numeric, value) {
bgneal@45 1530 if (numeric) {
bgneal@45 1531 value = parseInt(value, numeric.length === 2 ? 16 : 10);
bgneal@45 1532
bgneal@45 1533 // Support upper UTF
bgneal@45 1534 if (value > 0xFFFF) {
bgneal@45 1535 value -= 0x10000;
bgneal@45 1536
bgneal@45 1537 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
bgneal@45 1538 } else
bgneal@45 1539 return asciiMap[value] || String.fromCharCode(value);
bgneal@45 1540 }
bgneal@45 1541
bgneal@45 1542 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
bgneal@45 1543 });
bgneal@45 1544 }
bgneal@45 1545 };
bgneal@45 1546 })(tinymce);
bgneal@45 1547
bgneal@45 1548 tinymce.html.Styles = function(settings, schema) {
bgneal@45 1549 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
bgneal@45 1550 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
bgneal@45 1551 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
bgneal@45 1552 trimRightRegExp = /\s+$/,
bgneal@45 1553 urlColorRegExp = /rgb/,
bgneal@45 1554 undef, i, encodingLookup = {}, encodingItems;
bgneal@45 1555
bgneal@45 1556 settings = settings || {};
bgneal@45 1557
bgneal@45 1558 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
bgneal@45 1559 for (i = 0; i < encodingItems.length; i++) {
bgneal@45 1560 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
bgneal@45 1561 encodingLookup['\uFEFF' + i] = encodingItems[i];
bgneal@45 1562 }
bgneal@45 1563
bgneal@45 1564 function toHex(match, r, g, b) {
bgneal@45 1565 function hex(val) {
bgneal@45 1566 val = parseInt(val).toString(16);
bgneal@45 1567
bgneal@45 1568 return val.length > 1 ? val : '0' + val; // 0 -> 00
bgneal@45 1569 };
bgneal@45 1570
bgneal@45 1571 return '#' + hex(r) + hex(g) + hex(b);
bgneal@45 1572 };
bgneal@45 1573
bgneal@45 1574 return {
bgneal@45 1575 toHex : function(color) {
bgneal@45 1576 return color.replace(rgbRegExp, toHex);
bgneal@45 1577 },
bgneal@45 1578
bgneal@45 1579 parse : function(css) {
bgneal@45 1580 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
bgneal@45 1581
bgneal@45 1582 function compress(prefix, suffix) {
bgneal@45 1583 var top, right, bottom, left;
bgneal@45 1584
bgneal@45 1585 // Get values and check it it needs compressing
bgneal@45 1586 top = styles[prefix + '-top' + suffix];
bgneal@45 1587 if (!top)
bgneal@45 1588 return;
bgneal@45 1589
bgneal@45 1590 right = styles[prefix + '-right' + suffix];
bgneal@45 1591 if (top != right)
bgneal@45 1592 return;
bgneal@45 1593
bgneal@45 1594 bottom = styles[prefix + '-bottom' + suffix];
bgneal@45 1595 if (right != bottom)
bgneal@45 1596 return;
bgneal@45 1597
bgneal@45 1598 left = styles[prefix + '-left' + suffix];
bgneal@45 1599 if (bottom != left)
bgneal@45 1600 return;
bgneal@45 1601
bgneal@45 1602 // Compress
bgneal@45 1603 styles[prefix + suffix] = left;
bgneal@45 1604 delete styles[prefix + '-top' + suffix];
bgneal@45 1605 delete styles[prefix + '-right' + suffix];
bgneal@45 1606 delete styles[prefix + '-bottom' + suffix];
bgneal@45 1607 delete styles[prefix + '-left' + suffix];
bgneal@45 1608 };
bgneal@45 1609
bgneal@45 1610 function canCompress(key) {
bgneal@45 1611 var value = styles[key], i;
bgneal@45 1612
bgneal@45 1613 if (!value || value.indexOf(' ') < 0)
bgneal@45 1614 return;
bgneal@45 1615
bgneal@45 1616 value = value.split(' ');
bgneal@45 1617 i = value.length;
bgneal@45 1618 while (i--) {
bgneal@45 1619 if (value[i] !== value[0])
bgneal@45 1620 return false;
bgneal@45 1621 }
bgneal@45 1622
bgneal@45 1623 styles[key] = value[0];
bgneal@45 1624
bgneal@45 1625 return true;
bgneal@45 1626 };
bgneal@45 1627
bgneal@45 1628 function compress2(target, a, b, c) {
bgneal@45 1629 if (!canCompress(a))
bgneal@45 1630 return;
bgneal@45 1631
bgneal@45 1632 if (!canCompress(b))
bgneal@45 1633 return;
bgneal@45 1634
bgneal@45 1635 if (!canCompress(c))
bgneal@45 1636 return;
bgneal@45 1637
bgneal@45 1638 // Compress
bgneal@45 1639 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
bgneal@45 1640 delete styles[a];
bgneal@45 1641 delete styles[b];
bgneal@45 1642 delete styles[c];
bgneal@45 1643 };
bgneal@45 1644
bgneal@45 1645 // Encodes the specified string by replacing all \" \' ; : with _<num>
bgneal@45 1646 function encode(str) {
bgneal@45 1647 isEncoded = true;
bgneal@45 1648
bgneal@45 1649 return encodingLookup[str];
bgneal@45 1650 };
bgneal@45 1651
bgneal@45 1652 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
bgneal@45 1653 // It will also decode the \" \' if keep_slashes is set to fale or omitted
bgneal@45 1654 function decode(str, keep_slashes) {
bgneal@45 1655 if (isEncoded) {
bgneal@45 1656 str = str.replace(/\uFEFF[0-9]/g, function(str) {
bgneal@45 1657 return encodingLookup[str];
bgneal@45 1658 });
bgneal@45 1659 }
bgneal@45 1660
bgneal@45 1661 if (!keep_slashes)
bgneal@45 1662 str = str.replace(/\\([\'\";:])/g, "$1");
bgneal@45 1663
bgneal@45 1664 return str;
bgneal@45 1665 }
bgneal@45 1666
bgneal@45 1667 if (css) {
bgneal@45 1668 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
bgneal@45 1669 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
bgneal@45 1670 return str.replace(/[;:]/g, encode);
bgneal@45 1671 });
bgneal@45 1672
bgneal@45 1673 // Parse styles
bgneal@45 1674 while (matches = styleRegExp.exec(css)) {
bgneal@45 1675 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
bgneal@45 1676 value = matches[2].replace(trimRightRegExp, '');
bgneal@45 1677
bgneal@45 1678 if (name && value.length > 0) {
bgneal@45 1679 // Opera will produce 700 instead of bold in their style values
bgneal@45 1680 if (name === 'font-weight' && value === '700')
bgneal@45 1681 value = 'bold';
bgneal@45 1682 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
bgneal@45 1683 value = value.toLowerCase();
bgneal@45 1684
bgneal@45 1685 // Convert RGB colors to HEX
bgneal@45 1686 value = value.replace(rgbRegExp, toHex);
bgneal@45 1687
bgneal@45 1688 // Convert URLs and force them into url('value') format
bgneal@45 1689 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
bgneal@45 1690 str = str || str2;
bgneal@45 1691
bgneal@45 1692 if (str) {
bgneal@45 1693 str = decode(str);
bgneal@45 1694
bgneal@45 1695 // Force strings into single quote format
bgneal@45 1696 return "'" + str.replace(/\'/g, "\\'") + "'";
bgneal@45 1697 }
bgneal@45 1698
bgneal@45 1699 url = decode(url || url2 || url3);
bgneal@45 1700
bgneal@45 1701 // Convert the URL to relative/absolute depending on config
bgneal@45 1702 if (urlConverter)
bgneal@45 1703 url = urlConverter.call(urlConverterScope, url, 'style');
bgneal@45 1704
bgneal@45 1705 // Output new URL format
bgneal@45 1706 return "url('" + url.replace(/\'/g, "\\'") + "')";
bgneal@45 1707 });
bgneal@45 1708
bgneal@45 1709 styles[name] = isEncoded ? decode(value, true) : value;
bgneal@45 1710 }
bgneal@45 1711
bgneal@45 1712 styleRegExp.lastIndex = matches.index + matches[0].length;
bgneal@45 1713 }
bgneal@45 1714
bgneal@45 1715 // Compress the styles to reduce it's size for example IE will expand styles
bgneal@45 1716 compress("border", "");
bgneal@45 1717 compress("border", "-width");
bgneal@45 1718 compress("border", "-color");
bgneal@45 1719 compress("border", "-style");
bgneal@45 1720 compress("padding", "");
bgneal@45 1721 compress("margin", "");
bgneal@45 1722 compress2('border', 'border-width', 'border-style', 'border-color');
bgneal@45 1723
bgneal@45 1724 // Remove pointless border, IE produces these
bgneal@45 1725 if (styles.border === 'medium none')
bgneal@45 1726 delete styles.border;
bgneal@45 1727 }
bgneal@45 1728
bgneal@45 1729 return styles;
bgneal@45 1730 },
bgneal@45 1731
bgneal@45 1732 serialize : function(styles, element_name) {
bgneal@45 1733 var css = '', name, value;
bgneal@45 1734
bgneal@45 1735 function serializeStyles(name) {
bgneal@45 1736 var styleList, i, l, value;
bgneal@45 1737
bgneal@45 1738 styleList = schema.styles[name];
bgneal@45 1739 if (styleList) {
bgneal@45 1740 for (i = 0, l = styleList.length; i < l; i++) {
bgneal@45 1741 name = styleList[i];
bgneal@45 1742 value = styles[name];
bgneal@45 1743
bgneal@45 1744 if (value !== undef && value.length > 0)
bgneal@45 1745 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
bgneal@45 1746 }
bgneal@45 1747 }
bgneal@45 1748 };
bgneal@45 1749
bgneal@45 1750 // Serialize styles according to schema
bgneal@45 1751 if (element_name && schema && schema.styles) {
bgneal@45 1752 // Serialize global styles and element specific styles
bgneal@45 1753 serializeStyles('*');
bgneal@45 1754 serializeStyles(element_name);
bgneal@45 1755 } else {
bgneal@45 1756 // Output the styles in the order they are inside the object
bgneal@45 1757 for (name in styles) {
bgneal@45 1758 value = styles[name];
bgneal@45 1759
bgneal@45 1760 if (value !== undef && value.length > 0)
bgneal@45 1761 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
bgneal@45 1762 }
bgneal@45 1763 }
bgneal@45 1764
bgneal@45 1765 return css;
bgneal@45 1766 }
bgneal@45 1767 };
bgneal@45 1768 };
bgneal@45 1769
bgneal@45 1770 (function(tinymce) {
bgneal@45 1771 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
bgneal@45 1772
bgneal@45 1773 function split(str, delim) {
bgneal@45 1774 return str.split(delim || ',');
bgneal@45 1775 };
bgneal@45 1776
bgneal@45 1777 function unpack(lookup, data) {
bgneal@45 1778 var key, elements = {};
bgneal@45 1779
bgneal@45 1780 function replace(value) {
bgneal@45 1781 return value.replace(/[A-Z]+/g, function(key) {
bgneal@45 1782 return replace(lookup[key]);
bgneal@45 1783 });
bgneal@45 1784 };
bgneal@45 1785
bgneal@45 1786 // Unpack lookup
bgneal@45 1787 for (key in lookup) {
bgneal@45 1788 if (lookup.hasOwnProperty(key))
bgneal@45 1789 lookup[key] = replace(lookup[key]);
bgneal@45 1790 }
bgneal@45 1791
bgneal@45 1792 // Unpack and parse data into object map
bgneal@45 1793 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
bgneal@45 1794 attributes = split(attributes, '|');
bgneal@45 1795
bgneal@45 1796 elements[name] = {
bgneal@45 1797 attributes : makeMap(attributes),
bgneal@45 1798 attributesOrder : attributes,
bgneal@45 1799 children : makeMap(children, '|', {'#comment' : {}})
bgneal@45 1800 }
bgneal@45 1801 });
bgneal@45 1802
bgneal@45 1803 return elements;
bgneal@45 1804 };
bgneal@45 1805
bgneal@45 1806 function getHTML5() {
bgneal@45 1807 var html5 = mapCache.html5;
bgneal@45 1808
bgneal@45 1809 if (!html5) {
bgneal@45 1810 html5 = mapCache.html5 = unpack({
bgneal@45 1811 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',
bgneal@45 1812 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',
bgneal@45 1813 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
bgneal@45 1814 }, 'html[A|manifest][body|head]' +
bgneal@45 1815 'head[A][base|command|link|meta|noscript|script|style|title]' +
bgneal@45 1816 'title[A][#]' +
bgneal@45 1817 'base[A|href|target][]' +
bgneal@45 1818 'link[A|href|rel|media|type|sizes][]' +
bgneal@45 1819 'meta[A|http-equiv|name|content|charset][]' +
bgneal@45 1820 'style[A|type|media|scoped][#]' +
bgneal@45 1821 'script[A|charset|type|src|defer|async][#]' +
bgneal@45 1822 'noscript[A][C]' +
bgneal@45 1823 'body[A][C]' +
bgneal@45 1824 'section[A][C]' +
bgneal@45 1825 'nav[A][C]' +
bgneal@45 1826 'article[A][C]' +
bgneal@45 1827 'aside[A][C]' +
bgneal@45 1828 'h1[A][B]' +
bgneal@45 1829 'h2[A][B]' +
bgneal@45 1830 'h3[A][B]' +
bgneal@45 1831 'h4[A][B]' +
bgneal@45 1832 'h5[A][B]' +
bgneal@45 1833 'h6[A][B]' +
bgneal@45 1834 'hgroup[A][h1|h2|h3|h4|h5|h6]' +
bgneal@45 1835 'header[A][C]' +
bgneal@45 1836 'footer[A][C]' +
bgneal@45 1837 'address[A][C]' +
bgneal@45 1838 'p[A][B]' +
bgneal@45 1839 'br[A][]' +
bgneal@45 1840 'pre[A][B]' +
bgneal@45 1841 'dialog[A][dd|dt]' +
bgneal@45 1842 'blockquote[A|cite][C]' +
bgneal@45 1843 'ol[A|start|reversed][li]' +
bgneal@45 1844 'ul[A][li]' +
bgneal@45 1845 'li[A|value][C]' +
bgneal@45 1846 'dl[A][dd|dt]' +
bgneal@45 1847 'dt[A][B]' +
bgneal@45 1848 'dd[A][C]' +
bgneal@45 1849 'a[A|href|target|ping|rel|media|type][C]' +
bgneal@45 1850 'em[A][B]' +
bgneal@45 1851 'strong[A][B]' +
bgneal@45 1852 'small[A][B]' +
bgneal@45 1853 'cite[A][B]' +
bgneal@45 1854 'q[A|cite][B]' +
bgneal@45 1855 'dfn[A][B]' +
bgneal@45 1856 'abbr[A][B]' +
bgneal@45 1857 'code[A][B]' +
bgneal@45 1858 'var[A][B]' +
bgneal@45 1859 'samp[A][B]' +
bgneal@45 1860 'kbd[A][B]' +
bgneal@45 1861 'sub[A][B]' +
bgneal@45 1862 'sup[A][B]' +
bgneal@45 1863 'i[A][B]' +
bgneal@45 1864 'b[A][B]' +
bgneal@45 1865 'mark[A][B]' +
bgneal@45 1866 'progress[A|value|max][B]' +
bgneal@45 1867 'meter[A|value|min|max|low|high|optimum][B]' +
bgneal@45 1868 'time[A|datetime][B]' +
bgneal@45 1869 'ruby[A][B|rt|rp]' +
bgneal@45 1870 'rt[A][B]' +
bgneal@45 1871 'rp[A][B]' +
bgneal@45 1872 'bdo[A][B]' +
bgneal@45 1873 'span[A][B]' +
bgneal@45 1874 'ins[A|cite|datetime][B]' +
bgneal@45 1875 'del[A|cite|datetime][B]' +
bgneal@45 1876 'figure[A][C|legend]' +
bgneal@45 1877 'img[A|alt|src|height|width|usemap|ismap][]' +
bgneal@45 1878 'iframe[A|name|src|height|width|sandbox|seamless][]' +
bgneal@45 1879 'embed[A|src|height|width|type][]' +
bgneal@45 1880 'object[A|data|type|height|width|usemap|name|form|classid][param]' +
bgneal@45 1881 'param[A|name|value][]' +
bgneal@45 1882 'details[A|open][C|legend]' +
bgneal@45 1883 'command[A|type|label|icon|disabled|checked|radiogroup][]' +
bgneal@45 1884 'menu[A|type|label][C|li]' +
bgneal@45 1885 'legend[A][C|B]' +
bgneal@45 1886 'div[A][C]' +
bgneal@45 1887 'source[A|src|type|media][]' +
bgneal@45 1888 'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
bgneal@45 1889 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
bgneal@45 1890 'hr[A][]' +
bgneal@45 1891 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
bgneal@45 1892 'fieldset[A|disabled|form|name][C|legend]' +
bgneal@45 1893 'label[A|form|for][B]' +
bgneal@45 1894 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
bgneal@45 1895 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
bgneal@45 1896 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
bgneal@45 1897 'datalist[A][B|option]' +
bgneal@45 1898 'optgroup[A|disabled|label][option]' +
bgneal@45 1899 'option[A|disabled|selected|label|value][]' +
bgneal@45 1900 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
bgneal@45 1901 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
bgneal@45 1902 'output[A|for|form|name][B]' +
bgneal@45 1903 'canvas[A|width|height][]' +
bgneal@45 1904 'map[A|name][B|C]' +
bgneal@45 1905 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
bgneal@45 1906 'mathml[A][]' +
bgneal@45 1907 'svg[A][]' +
bgneal@45 1908 'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
bgneal@45 1909 'caption[A][C]' +
bgneal@45 1910 'colgroup[A|span][col]' +
bgneal@45 1911 'col[A|span][]' +
bgneal@45 1912 'thead[A][tr]' +
bgneal@45 1913 'tfoot[A][tr]' +
bgneal@45 1914 'tbody[A][tr]' +
bgneal@45 1915 'tr[A][th|td]' +
bgneal@45 1916 'th[A|headers|rowspan|colspan|scope][B]' +
bgneal@45 1917 'td[A|headers|rowspan|colspan][C]'
bgneal@45 1918 );
bgneal@45 1919 }
bgneal@45 1920
bgneal@45 1921 return html5;
bgneal@45 1922 };
bgneal@45 1923
bgneal@45 1924 function getHTML4() {
bgneal@45 1925 var html4 = mapCache.html4;
bgneal@45 1926
bgneal@45 1927 if (!html4) {
bgneal@45 1928 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
bgneal@45 1929 html4 = mapCache.html4 = unpack({
bgneal@45 1930 Z : 'H|K|N|O|P',
bgneal@45 1931 Y : 'X|form|R|Q',
bgneal@45 1932 ZG : 'E|span|width|align|char|charoff|valign',
bgneal@45 1933 X : 'p|T|div|U|W|isindex|fieldset|table',
bgneal@45 1934 ZF : 'E|align|char|charoff|valign',
bgneal@45 1935 W : 'pre|hr|blockquote|address|center|noframes',
bgneal@45 1936 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
bgneal@45 1937 ZD : '[E][S]',
bgneal@45 1938 U : 'ul|ol|dl|menu|dir',
bgneal@45 1939 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
bgneal@45 1940 T : 'h1|h2|h3|h4|h5|h6',
bgneal@45 1941 ZB : 'X|S|Q',
bgneal@45 1942 S : 'R|P',
bgneal@45 1943 ZA : 'a|G|J|M|O|P',
bgneal@45 1944 R : 'a|H|K|N|O',
bgneal@45 1945 Q : 'noscript|P',
bgneal@45 1946 P : 'ins|del|script',
bgneal@45 1947 O : 'input|select|textarea|label|button',
bgneal@45 1948 N : 'M|L',
bgneal@45 1949 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
bgneal@45 1950 L : 'sub|sup',
bgneal@45 1951 K : 'J|I',
bgneal@45 1952 J : 'tt|i|b|u|s|strike',
bgneal@45 1953 I : 'big|small|font|basefont',
bgneal@45 1954 H : 'G|F',
bgneal@45 1955 G : 'br|span|bdo',
bgneal@45 1956 F : 'object|applet|img|map|iframe',
bgneal@45 1957 E : 'A|B|C',
bgneal@45 1958 D : 'accesskey|tabindex|onfocus|onblur',
bgneal@45 1959 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
bgneal@45 1960 B : 'lang|xml:lang|dir',
bgneal@45 1961 A : 'id|class|style|title'
bgneal@45 1962 }, 'script[id|charset|type|language|src|defer|xml:space][]' +
bgneal@45 1963 'style[B|id|type|media|title|xml:space][]' +
bgneal@45 1964 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
bgneal@45 1965 'param[id|name|value|valuetype|type][]' +
bgneal@45 1966 'p[E|align][#|S]' +
bgneal@45 1967 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
bgneal@45 1968 'br[A|clear][]' +
bgneal@45 1969 'span[E][#|S]' +
bgneal@45 1970 'bdo[A|C|B][#|S]' +
bgneal@45 1971 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
bgneal@45 1972 'h1[E|align][#|S]' +
bgneal@45 1973 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
bgneal@45 1974 'map[B|C|A|name][X|form|Q|area]' +
bgneal@45 1975 'h2[E|align][#|S]' +
bgneal@45 1976 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
bgneal@45 1977 'h3[E|align][#|S]' +
bgneal@45 1978 'tt[E][#|S]' +
bgneal@45 1979 'i[E][#|S]' +
bgneal@45 1980 'b[E][#|S]' +
bgneal@45 1981 'u[E][#|S]' +
bgneal@45 1982 's[E][#|S]' +
bgneal@45 1983 'strike[E][#|S]' +
bgneal@45 1984 'big[E][#|S]' +
bgneal@45 1985 'small[E][#|S]' +
bgneal@45 1986 'font[A|B|size|color|face][#|S]' +
bgneal@45 1987 'basefont[id|size|color|face][]' +
bgneal@45 1988 'em[E][#|S]' +
bgneal@45 1989 'strong[E][#|S]' +
bgneal@45 1990 'dfn[E][#|S]' +
bgneal@45 1991 'code[E][#|S]' +
bgneal@45 1992 'q[E|cite][#|S]' +
bgneal@45 1993 'samp[E][#|S]' +
bgneal@45 1994 'kbd[E][#|S]' +
bgneal@45 1995 'var[E][#|S]' +
bgneal@45 1996 'cite[E][#|S]' +
bgneal@45 1997 'abbr[E][#|S]' +
bgneal@45 1998 'acronym[E][#|S]' +
bgneal@45 1999 'sub[E][#|S]' +
bgneal@45 2000 'sup[E][#|S]' +
bgneal@45 2001 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
bgneal@45 2002 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
bgneal@45 2003 'optgroup[E|disabled|label][option]' +
bgneal@45 2004 'option[E|selected|disabled|label|value][]' +
bgneal@45 2005 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
bgneal@45 2006 'label[E|for|accesskey|onfocus|onblur][#|S]' +
bgneal@45 2007 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
bgneal@45 2008 'h4[E|align][#|S]' +
bgneal@45 2009 'ins[E|cite|datetime][#|Y]' +
bgneal@45 2010 'h5[E|align][#|S]' +
bgneal@45 2011 'del[E|cite|datetime][#|Y]' +
bgneal@45 2012 'h6[E|align][#|S]' +
bgneal@45 2013 'div[E|align][#|Y]' +
bgneal@45 2014 'ul[E|type|compact][li]' +
bgneal@45 2015 'li[E|type|value][#|Y]' +
bgneal@45 2016 'ol[E|type|compact|start][li]' +
bgneal@45 2017 'dl[E|compact][dt|dd]' +
bgneal@45 2018 'dt[E][#|S]' +
bgneal@45 2019 'dd[E][#|Y]' +
bgneal@45 2020 'menu[E|compact][li]' +
bgneal@45 2021 'dir[E|compact][li]' +
bgneal@45 2022 'pre[E|width|xml:space][#|ZA]' +
bgneal@45 2023 'hr[E|align|noshade|size|width][]' +
bgneal@45 2024 'blockquote[E|cite][#|Y]' +
bgneal@45 2025 'address[E][#|S|p]' +
bgneal@45 2026 'center[E][#|Y]' +
bgneal@45 2027 'noframes[E][#|Y]' +
bgneal@45 2028 'isindex[A|B|prompt][]' +
bgneal@45 2029 'fieldset[E][#|legend|Y]' +
bgneal@45 2030 'legend[E|accesskey|align][#|S]' +
bgneal@45 2031 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
bgneal@45 2032 'caption[E|align][#|S]' +
bgneal@45 2033 'col[ZG][]' +
bgneal@45 2034 'colgroup[ZG][col]' +
bgneal@45 2035 'thead[ZF][tr]' +
bgneal@45 2036 'tr[ZF|bgcolor][th|td]' +
bgneal@45 2037 'th[E|ZE][#|Y]' +
bgneal@45 2038 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
bgneal@45 2039 'noscript[E][#|Y]' +
bgneal@45 2040 'td[E|ZE][#|Y]' +
bgneal@45 2041 'tfoot[ZF][tr]' +
bgneal@45 2042 'tbody[ZF][tr]' +
bgneal@45 2043 'area[E|D|shape|coords|href|nohref|alt|target][]' +
bgneal@45 2044 'base[id|href|target][]' +
bgneal@45 2045 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
bgneal@45 2046 );
bgneal@45 2047 }
bgneal@45 2048
bgneal@45 2049 return html4;
bgneal@45 2050 };
bgneal@45 2051
bgneal@45 2052 tinymce.html.Schema = function(settings) {
bgneal@45 2053 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
bgneal@45 2054 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
bgneal@45 2055
bgneal@45 2056 // Creates an lookup table map object for the specified option or the default value
bgneal@45 2057 function createLookupTable(option, default_value, extend) {
bgneal@45 2058 var value = settings[option];
bgneal@45 2059
bgneal@45 2060 if (!value) {
bgneal@45 2061 // Get cached default map or make it if needed
bgneal@45 2062 value = mapCache[option];
bgneal@45 2063
bgneal@45 2064 if (!value) {
bgneal@45 2065 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
bgneal@45 2066 value = tinymce.extend(value, extend);
bgneal@45 2067
bgneal@45 2068 mapCache[option] = value;
bgneal@45 2069 }
bgneal@45 2070 } else {
bgneal@45 2071 // Create custom map
bgneal@45 2072 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
bgneal@45 2073 }
bgneal@45 2074
bgneal@45 2075 return value;
bgneal@45 2076 };
bgneal@45 2077
bgneal@45 2078 settings = settings || {};
bgneal@45 2079 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
bgneal@45 2080
bgneal@45 2081 // Allow all elements and attributes if verify_html is set to false
bgneal@45 2082 if (settings.verify_html === false)
bgneal@45 2083 settings.valid_elements = '*[*]';
bgneal@45 2084
bgneal@45 2085 // Build styles list
bgneal@45 2086 if (settings.valid_styles) {
bgneal@45 2087 validStyles = {};
bgneal@45 2088
bgneal@45 2089 // Convert styles into a rule list
bgneal@45 2090 each(settings.valid_styles, function(value, key) {
bgneal@45 2091 validStyles[key] = tinymce.explode(value);
bgneal@45 2092 });
bgneal@45 2093 }
bgneal@45 2094
bgneal@45 2095 // Setup map objects
bgneal@45 2096 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
bgneal@45 2097 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options p td tfoot th thead tr');
bgneal@45 2098 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source');
bgneal@45 2099 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
bgneal@45 2100 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
bgneal@45 2101 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' +
bgneal@45 2102 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' +
bgneal@45 2103 'noscript menu isindex samp header footer article section hgroup aside nav');
bgneal@45 2104
bgneal@45 2105 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
bgneal@45 2106 function patternToRegExp(str) {
bgneal@45 2107 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
bgneal@45 2108 };
bgneal@45 2109
bgneal@45 2110 // Parses the specified valid_elements string and adds to the current rules
bgneal@45 2111 // This function is a bit hard to read since it's heavily optimized for speed
bgneal@45 2112 function addValidElements(valid_elements) {
bgneal@45 2113 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
bgneal@45 2114 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
bgneal@45 2115 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
bgneal@45 2116 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
bgneal@45 2117 hasPatternsRegExp = /[*?+]/;
bgneal@45 2118
bgneal@45 2119 if (valid_elements) {
bgneal@45 2120 // Split valid elements into an array with rules
bgneal@45 2121 valid_elements = split(valid_elements);
bgneal@45 2122
bgneal@45 2123 if (elements['@']) {
bgneal@45 2124 globalAttributes = elements['@'].attributes;
bgneal@45 2125 globalAttributesOrder = elements['@'].attributesOrder;
bgneal@45 2126 }
bgneal@45 2127
bgneal@45 2128 // Loop all rules
bgneal@45 2129 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
bgneal@45 2130 // Parse element rule
bgneal@45 2131 matches = elementRuleRegExp.exec(valid_elements[ei]);
bgneal@45 2132 if (matches) {
bgneal@45 2133 // Setup local names for matches
bgneal@45 2134 prefix = matches[1];
bgneal@45 2135 elementName = matches[2];
bgneal@45 2136 outputName = matches[3];
bgneal@45 2137 attrData = matches[4];
bgneal@45 2138
bgneal@45 2139 // Create new attributes and attributesOrder
bgneal@45 2140 attributes = {};
bgneal@45 2141 attributesOrder = [];
bgneal@45 2142
bgneal@45 2143 // Create the new element
bgneal@45 2144 element = {
bgneal@45 2145 attributes : attributes,
bgneal@45 2146 attributesOrder : attributesOrder
bgneal@45 2147 };
bgneal@45 2148
bgneal@45 2149 // Padd empty elements prefix
bgneal@45 2150 if (prefix === '#')
bgneal@45 2151 element.paddEmpty = true;
bgneal@45 2152
bgneal@45 2153 // Remove empty elements prefix
bgneal@45 2154 if (prefix === '-')
bgneal@45 2155 element.removeEmpty = true;
bgneal@45 2156
bgneal@45 2157 // Copy attributes from global rule into current rule
bgneal@45 2158 if (globalAttributes) {
bgneal@45 2159 for (key in globalAttributes)
bgneal@45 2160 attributes[key] = globalAttributes[key];
bgneal@45 2161
bgneal@45 2162 attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
bgneal@45 2163 }
bgneal@45 2164
bgneal@45 2165 // Attributes defined
bgneal@45 2166 if (attrData) {
bgneal@45 2167 attrData = split(attrData, '|');
bgneal@45 2168 for (ai = 0, al = attrData.length; ai < al; ai++) {
bgneal@45 2169 matches = attrRuleRegExp.exec(attrData[ai]);
bgneal@45 2170 if (matches) {
bgneal@45 2171 attr = {};
bgneal@45 2172 attrType = matches[1];
bgneal@45 2173 attrName = matches[2].replace(/::/g, ':');
bgneal@45 2174 prefix = matches[3];
bgneal@45 2175 value = matches[4];
bgneal@45 2176
bgneal@45 2177 // Required
bgneal@45 2178 if (attrType === '!') {
bgneal@45 2179 element.attributesRequired = element.attributesRequired || [];
bgneal@45 2180 element.attributesRequired.push(attrName);
bgneal@45 2181 attr.required = true;
bgneal@45 2182 }
bgneal@45 2183
bgneal@45 2184 // Denied from global
bgneal@45 2185 if (attrType === '-') {
bgneal@45 2186 delete attributes[attrName];
bgneal@45 2187 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
bgneal@45 2188 continue;
bgneal@45 2189 }
bgneal@45 2190
bgneal@45 2191 // Default value
bgneal@45 2192 if (prefix) {
bgneal@45 2193 // Default value
bgneal@45 2194 if (prefix === '=') {
bgneal@45 2195 element.attributesDefault = element.attributesDefault || [];
bgneal@45 2196 element.attributesDefault.push({name: attrName, value: value});
bgneal@45 2197 attr.defaultValue = value;
bgneal@45 2198 }
bgneal@45 2199
bgneal@45 2200 // Forced value
bgneal@45 2201 if (prefix === ':') {
bgneal@45 2202 element.attributesForced = element.attributesForced || [];
bgneal@45 2203 element.attributesForced.push({name: attrName, value: value});
bgneal@45 2204 attr.forcedValue = value;
bgneal@45 2205 }
bgneal@45 2206
bgneal@45 2207 // Required values
bgneal@45 2208 if (prefix === '<')
bgneal@45 2209 attr.validValues = makeMap(value, '?');
bgneal@45 2210 }
bgneal@45 2211
bgneal@45 2212 // Check for attribute patterns
bgneal@45 2213 if (hasPatternsRegExp.test(attrName)) {
bgneal@45 2214 element.attributePatterns = element.attributePatterns || [];
bgneal@45 2215 attr.pattern = patternToRegExp(attrName);
bgneal@45 2216 element.attributePatterns.push(attr);
bgneal@45 2217 } else {
bgneal@45 2218 // Add attribute to order list if it doesn't already exist
bgneal@45 2219 if (!attributes[attrName])
bgneal@45 2220 attributesOrder.push(attrName);
bgneal@45 2221
bgneal@45 2222 attributes[attrName] = attr;
bgneal@45 2223 }
bgneal@45 2224 }
bgneal@45 2225 }
bgneal@45 2226 }
bgneal@45 2227
bgneal@45 2228 // Global rule, store away these for later usage
bgneal@45 2229 if (!globalAttributes && elementName == '@') {
bgneal@45 2230 globalAttributes = attributes;
bgneal@45 2231 globalAttributesOrder = attributesOrder;
bgneal@45 2232 }
bgneal@45 2233
bgneal@45 2234 // Handle substitute elements such as b/strong
bgneal@45 2235 if (outputName) {
bgneal@45 2236 element.outputName = elementName;
bgneal@45 2237 elements[outputName] = element;
bgneal@45 2238 }
bgneal@45 2239
bgneal@45 2240 // Add pattern or exact element
bgneal@45 2241 if (hasPatternsRegExp.test(elementName)) {
bgneal@45 2242 element.pattern = patternToRegExp(elementName);
bgneal@45 2243 patternElements.push(element);
bgneal@45 2244 } else
bgneal@45 2245 elements[elementName] = element;
bgneal@45 2246 }
bgneal@45 2247 }
bgneal@45 2248 }
bgneal@45 2249 };
bgneal@45 2250
bgneal@45 2251 function setValidElements(valid_elements) {
bgneal@45 2252 elements = {};
bgneal@45 2253 patternElements = [];
bgneal@45 2254
bgneal@45 2255 addValidElements(valid_elements);
bgneal@45 2256
bgneal@45 2257 each(schemaItems, function(element, name) {
bgneal@45 2258 children[name] = element.children;
bgneal@45 2259 });
bgneal@45 2260 };
bgneal@45 2261
bgneal@45 2262 // Adds custom non HTML elements to the schema
bgneal@45 2263 function addCustomElements(custom_elements) {
bgneal@45 2264 var customElementRegExp = /^(~)?(.+)$/;
bgneal@45 2265
bgneal@45 2266 if (custom_elements) {
bgneal@45 2267 each(split(custom_elements), function(rule) {
bgneal@45 2268 var matches = customElementRegExp.exec(rule),
bgneal@45 2269 inline = matches[1] === '~',
bgneal@45 2270 cloneName = inline ? 'span' : 'div',
bgneal@45 2271 name = matches[2];
bgneal@45 2272
bgneal@45 2273 children[name] = children[cloneName];
bgneal@45 2274 customElementsMap[name] = cloneName;
bgneal@45 2275
bgneal@45 2276 // If it's not marked as inline then add it to valid block elements
bgneal@45 2277 if (!inline)
bgneal@45 2278 blockElementsMap[name] = {};
bgneal@45 2279
bgneal@45 2280 // Add custom elements at span/div positions
bgneal@45 2281 each(children, function(element, child) {
bgneal@45 2282 if (element[cloneName])
bgneal@45 2283 element[name] = element[cloneName];
bgneal@45 2284 });
bgneal@45 2285 });
bgneal@45 2286 }
bgneal@45 2287 };
bgneal@45 2288
bgneal@45 2289 // Adds valid children to the schema object
bgneal@45 2290 function addValidChildren(valid_children) {
bgneal@45 2291 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
bgneal@45 2292
bgneal@45 2293 if (valid_children) {
bgneal@45 2294 each(split(valid_children), function(rule) {
bgneal@45 2295 var matches = childRuleRegExp.exec(rule), parent, prefix;
bgneal@45 2296
bgneal@45 2297 if (matches) {
bgneal@45 2298 prefix = matches[1];
bgneal@45 2299
bgneal@45 2300 // Add/remove items from default
bgneal@45 2301 if (prefix)
bgneal@45 2302 parent = children[matches[2]];
bgneal@45 2303 else
bgneal@45 2304 parent = children[matches[2]] = {'#comment' : {}};
bgneal@45 2305
bgneal@45 2306 parent = children[matches[2]];
bgneal@45 2307
bgneal@45 2308 each(split(matches[3], '|'), function(child) {
bgneal@45 2309 if (prefix === '-')
bgneal@45 2310 delete parent[child];
bgneal@45 2311 else
bgneal@45 2312 parent[child] = {};
bgneal@45 2313 });
bgneal@45 2314 }
bgneal@45 2315 });
bgneal@45 2316 }
bgneal@45 2317 };
bgneal@45 2318
bgneal@45 2319 function getElementRule(name) {
bgneal@45 2320 var element = elements[name], i;
bgneal@45 2321
bgneal@45 2322 // Exact match found
bgneal@45 2323 if (element)
bgneal@45 2324 return element;
bgneal@45 2325
bgneal@45 2326 // No exact match then try the patterns
bgneal@45 2327 i = patternElements.length;
bgneal@45 2328 while (i--) {
bgneal@45 2329 element = patternElements[i];
bgneal@45 2330
bgneal@45 2331 if (element.pattern.test(name))
bgneal@45 2332 return element;
bgneal@45 2333 }
bgneal@45 2334 };
bgneal@45 2335
bgneal@45 2336 if (!settings.valid_elements) {
bgneal@45 2337 // No valid elements defined then clone the elements from the schema spec
bgneal@45 2338 each(schemaItems, function(element, name) {
bgneal@45 2339 elements[name] = {
bgneal@45 2340 attributes : element.attributes,
bgneal@45 2341 attributesOrder : element.attributesOrder
bgneal@45 2342 };
bgneal@45 2343
bgneal@45 2344 children[name] = element.children;
bgneal@45 2345 });
bgneal@45 2346
bgneal@45 2347 // Switch these on HTML4
bgneal@45 2348 if (settings.schema != "html5") {
bgneal@45 2349 each(split('strong/b,em/i'), function(item) {
bgneal@45 2350 item = split(item, '/');
bgneal@45 2351 elements[item[1]].outputName = item[0];
bgneal@45 2352 });
bgneal@45 2353 }
bgneal@45 2354
bgneal@45 2355 // Add default alt attribute for images
bgneal@45 2356 elements.img.attributesDefault = [{name: 'alt', value: ''}];
bgneal@45 2357
bgneal@45 2358 // Remove these if they are empty by default
bgneal@45 2359 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
bgneal@45 2360 if (elements[name]) {
bgneal@45 2361 elements[name].removeEmpty = true;
bgneal@45 2362 }
bgneal@45 2363 });
bgneal@45 2364
bgneal@45 2365 // Padd these by default
bgneal@45 2366 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
bgneal@45 2367 elements[name].paddEmpty = true;
bgneal@45 2368 });
bgneal@45 2369 } else
bgneal@45 2370 setValidElements(settings.valid_elements);
bgneal@45 2371
bgneal@45 2372 addCustomElements(settings.custom_elements);
bgneal@45 2373 addValidChildren(settings.valid_children);
bgneal@45 2374 addValidElements(settings.extended_valid_elements);
bgneal@45 2375
bgneal@45 2376 // Todo: Remove this when we fix list handling to be valid
bgneal@45 2377 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
bgneal@45 2378
bgneal@45 2379 // Delete invalid elements
bgneal@45 2380 if (settings.invalid_elements) {
bgneal@45 2381 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
bgneal@45 2382 if (elements[item])
bgneal@45 2383 delete elements[item];
bgneal@45 2384 });
bgneal@45 2385 }
bgneal@45 2386
bgneal@45 2387 // If the user didn't allow span only allow internal spans
bgneal@45 2388 if (!getElementRule('span'))
bgneal@45 2389 addValidElements('span[!data-mce-type|*]');
bgneal@45 2390
bgneal@45 2391 self.children = children;
bgneal@45 2392
bgneal@45 2393 self.styles = validStyles;
bgneal@45 2394
bgneal@45 2395 self.getBoolAttrs = function() {
bgneal@45 2396 return boolAttrMap;
bgneal@45 2397 };
bgneal@45 2398
bgneal@45 2399 self.getBlockElements = function() {
bgneal@45 2400 return blockElementsMap;
bgneal@45 2401 };
bgneal@45 2402
bgneal@45 2403 self.getShortEndedElements = function() {
bgneal@45 2404 return shortEndedElementsMap;
bgneal@45 2405 };
bgneal@45 2406
bgneal@45 2407 self.getSelfClosingElements = function() {
bgneal@45 2408 return selfClosingElementsMap;
bgneal@45 2409 };
bgneal@45 2410
bgneal@45 2411 self.getNonEmptyElements = function() {
bgneal@45 2412 return nonEmptyElementsMap;
bgneal@45 2413 };
bgneal@45 2414
bgneal@45 2415 self.getWhiteSpaceElements = function() {
bgneal@45 2416 return whiteSpaceElementsMap;
bgneal@45 2417 };
bgneal@45 2418
bgneal@45 2419 self.isValidChild = function(name, child) {
bgneal@45 2420 var parent = children[name];
bgneal@45 2421
bgneal@45 2422 return !!(parent && parent[child]);
bgneal@45 2423 };
bgneal@45 2424
bgneal@45 2425 self.getElementRule = getElementRule;
bgneal@45 2426
bgneal@45 2427 self.getCustomElements = function() {
bgneal@45 2428 return customElementsMap;
bgneal@45 2429 };
bgneal@45 2430
bgneal@45 2431 self.addValidElements = addValidElements;
bgneal@45 2432
bgneal@45 2433 self.setValidElements = setValidElements;
bgneal@45 2434
bgneal@45 2435 self.addCustomElements = addCustomElements;
bgneal@45 2436
bgneal@45 2437 self.addValidChildren = addValidChildren;
bgneal@45 2438 };
bgneal@45 2439 })(tinymce);
bgneal@45 2440
bgneal@45 2441 (function(tinymce) {
bgneal@45 2442 tinymce.html.SaxParser = function(settings, schema) {
bgneal@45 2443 var self = this, noop = function() {};
bgneal@45 2444
bgneal@45 2445 settings = settings || {};
bgneal@45 2446 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@45 2447
bgneal@45 2448 if (settings.fix_self_closing !== false)
bgneal@45 2449 settings.fix_self_closing = true;
bgneal@45 2450
bgneal@45 2451 // Add handler functions from settings and setup default handlers
bgneal@45 2452 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
bgneal@45 2453 if (name)
bgneal@45 2454 self[name] = settings[name] || noop;
bgneal@45 2455 });
bgneal@45 2456
bgneal@45 2457 self.parse = function(html) {
bgneal@45 2458 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
bgneal@45 2459 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
bgneal@45 2460 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
bgneal@45 2461 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
bgneal@45 2462
bgneal@45 2463 function processEndTag(name) {
bgneal@45 2464 var pos, i;
bgneal@45 2465
bgneal@45 2466 // Find position of parent of the same type
bgneal@45 2467 pos = stack.length;
bgneal@45 2468 while (pos--) {
bgneal@45 2469 if (stack[pos].name === name)
bgneal@45 2470 break;
bgneal@45 2471 }
bgneal@45 2472
bgneal@45 2473 // Found parent
bgneal@45 2474 if (pos >= 0) {
bgneal@45 2475 // Close all the open elements
bgneal@45 2476 for (i = stack.length - 1; i >= pos; i--) {
bgneal@45 2477 name = stack[i];
bgneal@45 2478
bgneal@45 2479 if (name.valid)
bgneal@45 2480 self.end(name.name);
bgneal@45 2481 }
bgneal@45 2482
bgneal@45 2483 // Remove the open elements from the stack
bgneal@45 2484 stack.length = pos;
bgneal@45 2485 }
bgneal@45 2486 };
bgneal@45 2487
bgneal@45 2488 // Precompile RegExps and map objects
bgneal@45 2489 tokenRegExp = new RegExp('<(?:' +
bgneal@45 2490 '(?:!--([\\w\\W]*?)-->)|' + // Comment
bgneal@45 2491 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
bgneal@45 2492 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
bgneal@45 2493 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
bgneal@45 2494 '(?:\\/([^>]+)>)|' + // End element
bgneal@45 2495 '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
bgneal@45 2496 ')', 'g');
bgneal@45 2497
bgneal@45 2498 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
bgneal@45 2499 specialElements = {
bgneal@45 2500 'script' : /<\/script[^>]*>/gi,
bgneal@45 2501 'style' : /<\/style[^>]*>/gi,
bgneal@45 2502 'noscript' : /<\/noscript[^>]*>/gi
bgneal@45 2503 };
bgneal@45 2504
bgneal@45 2505 // Setup lookup tables for empty elements and boolean attributes
bgneal@45 2506 shortEndedElements = schema.getShortEndedElements();
bgneal@45 2507 selfClosing = schema.getSelfClosingElements();
bgneal@45 2508 fillAttrsMap = schema.getBoolAttrs();
bgneal@45 2509 validate = settings.validate;
bgneal@45 2510 removeInternalElements = settings.remove_internals;
bgneal@45 2511 fixSelfClosing = settings.fix_self_closing;
bgneal@45 2512 isIE = tinymce.isIE;
bgneal@45 2513 invalidPrefixRegExp = /^:/;
bgneal@45 2514
bgneal@45 2515 while (matches = tokenRegExp.exec(html)) {
bgneal@45 2516 // Text
bgneal@45 2517 if (index < matches.index)
bgneal@45 2518 self.text(decode(html.substr(index, matches.index - index)));
bgneal@45 2519
bgneal@45 2520 if (value = matches[6]) { // End element
bgneal@45 2521 value = value.toLowerCase();
bgneal@45 2522
bgneal@45 2523 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
bgneal@45 2524 if (isIE && invalidPrefixRegExp.test(value))
bgneal@45 2525 value = value.substr(1);
bgneal@45 2526
bgneal@45 2527 processEndTag(value);
bgneal@45 2528 } else if (value = matches[7]) { // Start element
bgneal@45 2529 value = value.toLowerCase();
bgneal@45 2530
bgneal@45 2531 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
bgneal@45 2532 if (isIE && invalidPrefixRegExp.test(value))
bgneal@45 2533 value = value.substr(1);
bgneal@45 2534
bgneal@45 2535 isShortEnded = value in shortEndedElements;
bgneal@45 2536
bgneal@45 2537 // Is self closing tag for example an <li> after an open <li>
bgneal@45 2538 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
bgneal@45 2539 processEndTag(value);
bgneal@45 2540
bgneal@45 2541 // Validate element
bgneal@45 2542 if (!validate || (elementRule = schema.getElementRule(value))) {
bgneal@45 2543 isValidElement = true;
bgneal@45 2544
bgneal@45 2545 // Grab attributes map and patters when validation is enabled
bgneal@45 2546 if (validate) {
bgneal@45 2547 validAttributesMap = elementRule.attributes;
bgneal@45 2548 validAttributePatterns = elementRule.attributePatterns;
bgneal@45 2549 }
bgneal@45 2550
bgneal@45 2551 // Parse attributes
bgneal@45 2552 if (attribsValue = matches[8]) {
bgneal@45 2553 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
bgneal@45 2554
bgneal@45 2555 // If the element has internal attributes then remove it if we are told to do so
bgneal@45 2556 if (isInternalElement && removeInternalElements)
bgneal@45 2557 isValidElement = false;
bgneal@45 2558
bgneal@45 2559 attrList = [];
bgneal@45 2560 attrList.map = {};
bgneal@45 2561
bgneal@45 2562 attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
bgneal@45 2563 var attrRule, i;
bgneal@45 2564
bgneal@45 2565 name = name.toLowerCase();
bgneal@45 2566 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
bgneal@45 2567
bgneal@45 2568 // Validate name and value
bgneal@45 2569 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
bgneal@45 2570 attrRule = validAttributesMap[name];
bgneal@45 2571
bgneal@45 2572 // Find rule by pattern matching
bgneal@45 2573 if (!attrRule && validAttributePatterns) {
bgneal@45 2574 i = validAttributePatterns.length;
bgneal@45 2575 while (i--) {
bgneal@45 2576 attrRule = validAttributePatterns[i];
bgneal@45 2577 if (attrRule.pattern.test(name))
bgneal@45 2578 break;
bgneal@45 2579 }
bgneal@45 2580
bgneal@45 2581 // No rule matched
bgneal@45 2582 if (i === -1)
bgneal@45 2583 attrRule = null;
bgneal@45 2584 }
bgneal@45 2585
bgneal@45 2586 // No attribute rule found
bgneal@45 2587 if (!attrRule)
bgneal@45 2588 return;
bgneal@45 2589
bgneal@45 2590 // Validate value
bgneal@45 2591 if (attrRule.validValues && !(value in attrRule.validValues))
bgneal@45 2592 return;
bgneal@45 2593 }
bgneal@45 2594
bgneal@45 2595 // Add attribute to list and map
bgneal@45 2596 attrList.map[name] = value;
bgneal@45 2597 attrList.push({
bgneal@45 2598 name: name,
bgneal@45 2599 value: value
bgneal@45 2600 });
bgneal@45 2601 });
bgneal@45 2602 } else {
bgneal@45 2603 attrList = [];
bgneal@45 2604 attrList.map = {};
bgneal@45 2605 }
bgneal@45 2606
bgneal@45 2607 // Process attributes if validation is enabled
bgneal@45 2608 if (validate && !isInternalElement) {
bgneal@45 2609 attributesRequired = elementRule.attributesRequired;
bgneal@45 2610 attributesDefault = elementRule.attributesDefault;
bgneal@45 2611 attributesForced = elementRule.attributesForced;
bgneal@45 2612
bgneal@45 2613 // Handle forced attributes
bgneal@45 2614 if (attributesForced) {
bgneal@45 2615 i = attributesForced.length;
bgneal@45 2616 while (i--) {
bgneal@45 2617 attr = attributesForced[i];
bgneal@45 2618 name = attr.name;
bgneal@45 2619 attrValue = attr.value;
bgneal@45 2620
bgneal@45 2621 if (attrValue === '{$uid}')
bgneal@45 2622 attrValue = 'mce_' + idCount++;
bgneal@45 2623
bgneal@45 2624 attrList.map[name] = attrValue;
bgneal@45 2625 attrList.push({name: name, value: attrValue});
bgneal@45 2626 }
bgneal@45 2627 }
bgneal@45 2628
bgneal@45 2629 // Handle default attributes
bgneal@45 2630 if (attributesDefault) {
bgneal@45 2631 i = attributesDefault.length;
bgneal@45 2632 while (i--) {
bgneal@45 2633 attr = attributesDefault[i];
bgneal@45 2634 name = attr.name;
bgneal@45 2635
bgneal@45 2636 if (!(name in attrList.map)) {
bgneal@45 2637 attrValue = attr.value;
bgneal@45 2638
bgneal@45 2639 if (attrValue === '{$uid}')
bgneal@45 2640 attrValue = 'mce_' + idCount++;
bgneal@45 2641
bgneal@45 2642 attrList.map[name] = attrValue;
bgneal@45 2643 attrList.push({name: name, value: attrValue});
bgneal@45 2644 }
bgneal@45 2645 }
bgneal@45 2646 }
bgneal@45 2647
bgneal@45 2648 // Handle required attributes
bgneal@45 2649 if (attributesRequired) {
bgneal@45 2650 i = attributesRequired.length;
bgneal@45 2651 while (i--) {
bgneal@45 2652 if (attributesRequired[i] in attrList.map)
bgneal@45 2653 break;
bgneal@45 2654 }
bgneal@45 2655
bgneal@45 2656 // None of the required attributes where found
bgneal@45 2657 if (i === -1)
bgneal@45 2658 isValidElement = false;
bgneal@45 2659 }
bgneal@45 2660
bgneal@45 2661 // Invalidate element if it's marked as bogus
bgneal@45 2662 if (attrList.map['data-mce-bogus'])
bgneal@45 2663 isValidElement = false;
bgneal@45 2664 }
bgneal@45 2665
bgneal@45 2666 if (isValidElement)
bgneal@45 2667 self.start(value, attrList, isShortEnded);
bgneal@45 2668 } else
bgneal@45 2669 isValidElement = false;
bgneal@45 2670
bgneal@45 2671 // Treat script, noscript and style a bit different since they may include code that looks like elements
bgneal@45 2672 if (endRegExp = specialElements[value]) {
bgneal@45 2673 endRegExp.lastIndex = index = matches.index + matches[0].length;
bgneal@45 2674
bgneal@45 2675 if (matches = endRegExp.exec(html)) {
bgneal@45 2676 if (isValidElement)
bgneal@45 2677 text = html.substr(index, matches.index - index);
bgneal@45 2678
bgneal@45 2679 index = matches.index + matches[0].length;
bgneal@45 2680 } else {
bgneal@45 2681 text = html.substr(index);
bgneal@45 2682 index = html.length;
bgneal@45 2683 }
bgneal@45 2684
bgneal@45 2685 if (isValidElement && text.length > 0)
bgneal@45 2686 self.text(text, true);
bgneal@45 2687
bgneal@45 2688 if (isValidElement)
bgneal@45 2689 self.end(value);
bgneal@45 2690
bgneal@45 2691 tokenRegExp.lastIndex = index;
bgneal@45 2692 continue;
bgneal@45 2693 }
bgneal@45 2694
bgneal@45 2695 // Push value on to stack
bgneal@45 2696 if (!isShortEnded) {
bgneal@45 2697 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
bgneal@45 2698 stack.push({name: value, valid: isValidElement});
bgneal@45 2699 else if (isValidElement)
bgneal@45 2700 self.end(value);
bgneal@45 2701 }
bgneal@45 2702 } else if (value = matches[1]) { // Comment
bgneal@45 2703 self.comment(value);
bgneal@45 2704 } else if (value = matches[2]) { // CDATA
bgneal@45 2705 self.cdata(value);
bgneal@45 2706 } else if (value = matches[3]) { // DOCTYPE
bgneal@45 2707 self.doctype(value);
bgneal@45 2708 } else if (value = matches[4]) { // PI
bgneal@45 2709 self.pi(value, matches[5]);
bgneal@45 2710 }
bgneal@45 2711
bgneal@45 2712 index = matches.index + matches[0].length;
bgneal@45 2713 }
bgneal@45 2714
bgneal@45 2715 // Text
bgneal@45 2716 if (index < html.length)
bgneal@45 2717 self.text(decode(html.substr(index)));
bgneal@45 2718
bgneal@45 2719 // Close any open elements
bgneal@45 2720 for (i = stack.length - 1; i >= 0; i--) {
bgneal@45 2721 value = stack[i];
bgneal@45 2722
bgneal@45 2723 if (value.valid)
bgneal@45 2724 self.end(value.name);
bgneal@45 2725 }
bgneal@45 2726 };
bgneal@45 2727 }
bgneal@45 2728 })(tinymce);
bgneal@45 2729
bgneal@45 2730 (function(tinymce) {
bgneal@45 2731 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
bgneal@45 2732 '#text' : 3,
bgneal@45 2733 '#comment' : 8,
bgneal@45 2734 '#cdata' : 4,
bgneal@45 2735 '#pi' : 7,
bgneal@45 2736 '#doctype' : 10,
bgneal@45 2737 '#document-fragment' : 11
bgneal@45 2738 };
bgneal@45 2739
bgneal@45 2740 // Walks the tree left/right
bgneal@45 2741 function walk(node, root_node, prev) {
bgneal@45 2742 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
bgneal@45 2743
bgneal@45 2744 // Walk into nodes if it has a start
bgneal@45 2745 if (node[startName])
bgneal@45 2746 return node[startName];
bgneal@45 2747
bgneal@45 2748 // Return the sibling if it has one
bgneal@45 2749 if (node !== root_node) {
bgneal@45 2750 sibling = node[siblingName];
bgneal@45 2751
bgneal@45 2752 if (sibling)
bgneal@45 2753 return sibling;
bgneal@45 2754
bgneal@45 2755 // Walk up the parents to look for siblings
bgneal@45 2756 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
bgneal@45 2757 sibling = parent[siblingName];
bgneal@45 2758
bgneal@45 2759 if (sibling)
bgneal@45 2760 return sibling;
bgneal@45 2761 }
bgneal@45 2762 }
bgneal@45 2763 };
bgneal@45 2764
bgneal@45 2765 function Node(name, type) {
bgneal@45 2766 this.name = name;
bgneal@45 2767 this.type = type;
bgneal@45 2768
bgneal@45 2769 if (type === 1) {
bgneal@45 2770 this.attributes = [];
bgneal@45 2771 this.attributes.map = {};
bgneal@45 2772 }
bgneal@45 2773 }
bgneal@45 2774
bgneal@45 2775 tinymce.extend(Node.prototype, {
bgneal@45 2776 replace : function(node) {
bgneal@45 2777 var self = this;
bgneal@45 2778
bgneal@45 2779 if (node.parent)
bgneal@45 2780 node.remove();
bgneal@45 2781
bgneal@45 2782 self.insert(node, self);
bgneal@45 2783 self.remove();
bgneal@45 2784
bgneal@45 2785 return self;
bgneal@45 2786 },
bgneal@45 2787
bgneal@45 2788 attr : function(name, value) {
bgneal@45 2789 var self = this, attrs, i, undef;
bgneal@45 2790
bgneal@45 2791 if (typeof name !== "string") {
bgneal@45 2792 for (i in name)
bgneal@45 2793 self.attr(i, name[i]);
bgneal@45 2794
bgneal@45 2795 return self;
bgneal@45 2796 }
bgneal@45 2797
bgneal@45 2798 if (attrs = self.attributes) {
bgneal@45 2799 if (value !== undef) {
bgneal@45 2800 // Remove attribute
bgneal@45 2801 if (value === null) {
bgneal@45 2802 if (name in attrs.map) {
bgneal@45 2803 delete attrs.map[name];
bgneal@45 2804
bgneal@45 2805 i = attrs.length;
bgneal@45 2806 while (i--) {
bgneal@45 2807 if (attrs[i].name === name) {
bgneal@45 2808 attrs = attrs.splice(i, 1);
bgneal@45 2809 return self;
bgneal@45 2810 }
bgneal@45 2811 }
bgneal@45 2812 }
bgneal@45 2813
bgneal@45 2814 return self;
bgneal@45 2815 }
bgneal@45 2816
bgneal@45 2817 // Set attribute
bgneal@45 2818 if (name in attrs.map) {
bgneal@45 2819 // Set attribute
bgneal@45 2820 i = attrs.length;
bgneal@45 2821 while (i--) {
bgneal@45 2822 if (attrs[i].name === name) {
bgneal@45 2823 attrs[i].value = value;
bgneal@45 2824 break;
bgneal@45 2825 }
bgneal@45 2826 }
bgneal@45 2827 } else
bgneal@45 2828 attrs.push({name: name, value: value});
bgneal@45 2829
bgneal@45 2830 attrs.map[name] = value;
bgneal@45 2831
bgneal@45 2832 return self;
bgneal@45 2833 } else {
bgneal@45 2834 return attrs.map[name];
bgneal@45 2835 }
bgneal@45 2836 }
bgneal@45 2837 },
bgneal@45 2838
bgneal@45 2839 clone : function() {
bgneal@45 2840 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
bgneal@45 2841
bgneal@45 2842 // Clone element attributes
bgneal@45 2843 if (selfAttrs = self.attributes) {
bgneal@45 2844 cloneAttrs = [];
bgneal@45 2845 cloneAttrs.map = {};
bgneal@45 2846
bgneal@45 2847 for (i = 0, l = selfAttrs.length; i < l; i++) {
bgneal@45 2848 selfAttr = selfAttrs[i];
bgneal@45 2849
bgneal@45 2850 // Clone everything except id
bgneal@45 2851 if (selfAttr.name !== 'id') {
bgneal@45 2852 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
bgneal@45 2853 cloneAttrs.map[selfAttr.name] = selfAttr.value;
bgneal@45 2854 }
bgneal@45 2855 }
bgneal@45 2856
bgneal@45 2857 clone.attributes = cloneAttrs;
bgneal@45 2858 }
bgneal@45 2859
bgneal@45 2860 clone.value = self.value;
bgneal@45 2861 clone.shortEnded = self.shortEnded;
bgneal@45 2862
bgneal@45 2863 return clone;
bgneal@45 2864 },
bgneal@45 2865
bgneal@45 2866 wrap : function(wrapper) {
bgneal@45 2867 var self = this;
bgneal@45 2868
bgneal@45 2869 self.parent.insert(wrapper, self);
bgneal@45 2870 wrapper.append(self);
bgneal@45 2871
bgneal@45 2872 return self;
bgneal@45 2873 },
bgneal@45 2874
bgneal@45 2875 unwrap : function() {
bgneal@45 2876 var self = this, node, next;
bgneal@45 2877
bgneal@45 2878 for (node = self.firstChild; node; ) {
bgneal@45 2879 next = node.next;
bgneal@45 2880 self.insert(node, self, true);
bgneal@45 2881 node = next;
bgneal@45 2882 }
bgneal@45 2883
bgneal@45 2884 self.remove();
bgneal@45 2885 },
bgneal@45 2886
bgneal@45 2887 remove : function() {
bgneal@45 2888 var self = this, parent = self.parent, next = self.next, prev = self.prev;
bgneal@45 2889
bgneal@45 2890 if (parent) {
bgneal@45 2891 if (parent.firstChild === self) {
bgneal@45 2892 parent.firstChild = next;
bgneal@45 2893
bgneal@45 2894 if (next)
bgneal@45 2895 next.prev = null;
bgneal@45 2896 } else {
bgneal@45 2897 prev.next = next;
bgneal@45 2898 }
bgneal@45 2899
bgneal@45 2900 if (parent.lastChild === self) {
bgneal@45 2901 parent.lastChild = prev;
bgneal@45 2902
bgneal@45 2903 if (prev)
bgneal@45 2904 prev.next = null;
bgneal@45 2905 } else {
bgneal@45 2906 next.prev = prev;
bgneal@45 2907 }
bgneal@45 2908
bgneal@45 2909 self.parent = self.next = self.prev = null;
bgneal@45 2910 }
bgneal@45 2911
bgneal@45 2912 return self;
bgneal@45 2913 },
bgneal@45 2914
bgneal@45 2915 append : function(node) {
bgneal@45 2916 var self = this, last;
bgneal@45 2917
bgneal@45 2918 if (node.parent)
bgneal@45 2919 node.remove();
bgneal@45 2920
bgneal@45 2921 last = self.lastChild;
bgneal@45 2922 if (last) {
bgneal@45 2923 last.next = node;
bgneal@45 2924 node.prev = last;
bgneal@45 2925 self.lastChild = node;
bgneal@45 2926 } else
bgneal@45 2927 self.lastChild = self.firstChild = node;
bgneal@45 2928
bgneal@45 2929 node.parent = self;
bgneal@45 2930
bgneal@45 2931 return node;
bgneal@45 2932 },
bgneal@45 2933
bgneal@45 2934 insert : function(node, ref_node, before) {
bgneal@45 2935 var parent;
bgneal@45 2936
bgneal@45 2937 if (node.parent)
bgneal@45 2938 node.remove();
bgneal@45 2939
bgneal@45 2940 parent = ref_node.parent || this;
bgneal@45 2941
bgneal@45 2942 if (before) {
bgneal@45 2943 if (ref_node === parent.firstChild)
bgneal@45 2944 parent.firstChild = node;
bgneal@45 2945 else
bgneal@45 2946 ref_node.prev.next = node;
bgneal@45 2947
bgneal@45 2948 node.prev = ref_node.prev;
bgneal@45 2949 node.next = ref_node;
bgneal@45 2950 ref_node.prev = node;
bgneal@45 2951 } else {
bgneal@45 2952 if (ref_node === parent.lastChild)
bgneal@45 2953 parent.lastChild = node;
bgneal@45 2954 else
bgneal@45 2955 ref_node.next.prev = node;
bgneal@45 2956
bgneal@45 2957 node.next = ref_node.next;
bgneal@45 2958 node.prev = ref_node;
bgneal@45 2959 ref_node.next = node;
bgneal@45 2960 }
bgneal@45 2961
bgneal@45 2962 node.parent = parent;
bgneal@45 2963
bgneal@45 2964 return node;
bgneal@45 2965 },
bgneal@45 2966
bgneal@45 2967 getAll : function(name) {
bgneal@45 2968 var self = this, node, collection = [];
bgneal@45 2969
bgneal@45 2970 for (node = self.firstChild; node; node = walk(node, self)) {
bgneal@45 2971 if (node.name === name)
bgneal@45 2972 collection.push(node);
bgneal@45 2973 }
bgneal@45 2974
bgneal@45 2975 return collection;
bgneal@45 2976 },
bgneal@45 2977
bgneal@45 2978 empty : function() {
bgneal@45 2979 var self = this, nodes, i, node;
bgneal@45 2980
bgneal@45 2981 // Remove all children
bgneal@45 2982 if (self.firstChild) {
bgneal@45 2983 nodes = [];
bgneal@45 2984
bgneal@45 2985 // Collect the children
bgneal@45 2986 for (node = self.firstChild; node; node = walk(node, self))
bgneal@45 2987 nodes.push(node);
bgneal@45 2988
bgneal@45 2989 // Remove the children
bgneal@45 2990 i = nodes.length;
bgneal@45 2991 while (i--) {
bgneal@45 2992 node = nodes[i];
bgneal@45 2993 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
bgneal@45 2994 }
bgneal@45 2995 }
bgneal@45 2996
bgneal@45 2997 self.firstChild = self.lastChild = null;
bgneal@45 2998
bgneal@45 2999 return self;
bgneal@45 3000 },
bgneal@45 3001
bgneal@45 3002 isEmpty : function(elements) {
bgneal@45 3003 var self = this, node = self.firstChild, i, name;
bgneal@45 3004
bgneal@45 3005 if (node) {
bgneal@45 3006 do {
bgneal@45 3007 if (node.type === 1) {
bgneal@45 3008 // Ignore bogus elements
bgneal@45 3009 if (node.attributes.map['data-mce-bogus'])
bgneal@45 3010 continue;
bgneal@45 3011
bgneal@45 3012 // Keep empty elements like <img />
bgneal@45 3013 if (elements[node.name])
bgneal@45 3014 return false;
bgneal@45 3015
bgneal@45 3016 // Keep elements with data attributes or name attribute like <a name="1"></a>
bgneal@45 3017 i = node.attributes.length;
bgneal@45 3018 while (i--) {
bgneal@45 3019 name = node.attributes[i].name;
bgneal@45 3020 if (name === "name" || name.indexOf('data-') === 0)
bgneal@45 3021 return false;
bgneal@45 3022 }
bgneal@45 3023 }
bgneal@45 3024
bgneal@45 3025 // Keep comments
bgneal@45 3026 if (node.type === 8)
bgneal@45 3027 return false;
bgneal@45 3028
bgneal@45 3029 // Keep non whitespace text nodes
bgneal@45 3030 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
bgneal@45 3031 return false;
bgneal@45 3032 } while (node = walk(node, self));
bgneal@45 3033 }
bgneal@45 3034
bgneal@45 3035 return true;
bgneal@45 3036 },
bgneal@45 3037
bgneal@45 3038 walk : function(prev) {
bgneal@45 3039 return walk(this, null, prev);
bgneal@45 3040 }
bgneal@45 3041 });
bgneal@45 3042
bgneal@45 3043 tinymce.extend(Node, {
bgneal@45 3044 create : function(name, attrs) {
bgneal@45 3045 var node, attrName;
bgneal@45 3046
bgneal@45 3047 // Create node
bgneal@45 3048 node = new Node(name, typeLookup[name] || 1);
bgneal@45 3049
bgneal@45 3050 // Add attributes if needed
bgneal@45 3051 if (attrs) {
bgneal@45 3052 for (attrName in attrs)
bgneal@45 3053 node.attr(attrName, attrs[attrName]);
bgneal@45 3054 }
bgneal@45 3055
bgneal@45 3056 return node;
bgneal@45 3057 }
bgneal@45 3058 });
bgneal@45 3059
bgneal@45 3060 tinymce.html.Node = Node;
bgneal@45 3061 })(tinymce);
bgneal@45 3062
bgneal@45 3063 (function(tinymce) {
bgneal@45 3064 var Node = tinymce.html.Node;
bgneal@45 3065
bgneal@45 3066 tinymce.html.DomParser = function(settings, schema) {
bgneal@45 3067 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
bgneal@45 3068
bgneal@45 3069 settings = settings || {};
bgneal@45 3070 settings.validate = "validate" in settings ? settings.validate : true;
bgneal@45 3071 settings.root_name = settings.root_name || 'body';
bgneal@45 3072 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@45 3073
bgneal@45 3074 function fixInvalidChildren(nodes) {
bgneal@45 3075 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
bgneal@45 3076 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
bgneal@45 3077
bgneal@45 3078 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
bgneal@45 3079 nonEmptyElements = schema.getNonEmptyElements();
bgneal@45 3080
bgneal@45 3081 for (ni = 0; ni < nodes.length; ni++) {
bgneal@45 3082 node = nodes[ni];
bgneal@45 3083
bgneal@45 3084 // Already removed
bgneal@45 3085 if (!node.parent)
bgneal@45 3086 continue;
bgneal@45 3087
bgneal@45 3088 // Get list of all parent nodes until we find a valid parent to stick the child into
bgneal@45 3089 parents = [node];
bgneal@45 3090 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
bgneal@45 3091 parents.push(parent);
bgneal@45 3092
bgneal@45 3093 // Found a suitable parent
bgneal@45 3094 if (parent && parents.length > 1) {
bgneal@45 3095 // Reverse the array since it makes looping easier
bgneal@45 3096 parents.reverse();
bgneal@45 3097
bgneal@45 3098 // Clone the related parent and insert that after the moved node
bgneal@45 3099 newParent = currentNode = self.filterNode(parents[0].clone());
bgneal@45 3100
bgneal@45 3101 // Start cloning and moving children on the left side of the target node
bgneal@45 3102 for (i = 0; i < parents.length - 1; i++) {
bgneal@45 3103 if (schema.isValidChild(currentNode.name, parents[i].name)) {
bgneal@45 3104 tempNode = self.filterNode(parents[i].clone());
bgneal@45 3105 currentNode.append(tempNode);
bgneal@45 3106 } else
bgneal@45 3107 tempNode = currentNode;
bgneal@45 3108
bgneal@45 3109 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
bgneal@45 3110 nextNode = childNode.next;
bgneal@45 3111 tempNode.append(childNode);
bgneal@45 3112 childNode = nextNode;
bgneal@45 3113 }
bgneal@45 3114
bgneal@45 3115 currentNode = tempNode;
bgneal@45 3116 }
bgneal@45 3117
bgneal@45 3118 if (!newParent.isEmpty(nonEmptyElements)) {
bgneal@45 3119 parent.insert(newParent, parents[0], true);
bgneal@45 3120 parent.insert(node, newParent);
bgneal@45 3121 } else {
bgneal@45 3122 parent.insert(node, parents[0], true);
bgneal@45 3123 }
bgneal@45 3124
bgneal@45 3125 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
bgneal@45 3126 parent = parents[0];
bgneal@45 3127 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
bgneal@45 3128 parent.empty().remove();
bgneal@45 3129 }
bgneal@45 3130 } else if (node.parent) {
bgneal@45 3131 // If it's an LI try to find a UL/OL for it or wrap it
bgneal@45 3132 if (node.name === 'li') {
bgneal@45 3133 sibling = node.prev;
bgneal@45 3134 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
bgneal@45 3135 sibling.append(node);
bgneal@45 3136 continue;
bgneal@45 3137 }
bgneal@45 3138
bgneal@45 3139 sibling = node.next;
bgneal@45 3140 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
bgneal@45 3141 sibling.insert(node, sibling.firstChild, true);
bgneal@45 3142 continue;
bgneal@45 3143 }
bgneal@45 3144
bgneal@45 3145 node.wrap(self.filterNode(new Node('ul', 1)));
bgneal@45 3146 continue;
bgneal@45 3147 }
bgneal@45 3148
bgneal@45 3149 // Try wrapping the element in a DIV
bgneal@45 3150 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
bgneal@45 3151 node.wrap(self.filterNode(new Node('div', 1)));
bgneal@45 3152 } else {
bgneal@45 3153 // We failed wrapping it, then remove or unwrap it
bgneal@45 3154 if (node.name === 'style' || node.name === 'script')
bgneal@45 3155 node.empty().remove();
bgneal@45 3156 else
bgneal@45 3157 node.unwrap();
bgneal@45 3158 }
bgneal@45 3159 }
bgneal@45 3160 }
bgneal@45 3161 };
bgneal@45 3162
bgneal@45 3163 self.filterNode = function(node) {
bgneal@45 3164 var i, name, list;
bgneal@45 3165
bgneal@45 3166 // Run element filters
bgneal@45 3167 if (name in nodeFilters) {
bgneal@45 3168 list = matchedNodes[name];
bgneal@45 3169
bgneal@45 3170 if (list)
bgneal@45 3171 list.push(node);
bgneal@45 3172 else
bgneal@45 3173 matchedNodes[name] = [node];
bgneal@45 3174 }
bgneal@45 3175
bgneal@45 3176 // Run attribute filters
bgneal@45 3177 i = attributeFilters.length;
bgneal@45 3178 while (i--) {
bgneal@45 3179 name = attributeFilters[i].name;
bgneal@45 3180
bgneal@45 3181 if (name in node.attributes.map) {
bgneal@45 3182 list = matchedAttributes[name];
bgneal@45 3183
bgneal@45 3184 if (list)
bgneal@45 3185 list.push(node);
bgneal@45 3186 else
bgneal@45 3187 matchedAttributes[name] = [node];
bgneal@45 3188 }
bgneal@45 3189 }
bgneal@45 3190
bgneal@45 3191 return node;
bgneal@45 3192 };
bgneal@45 3193
bgneal@45 3194 self.addNodeFilter = function(name, callback) {
bgneal@45 3195 tinymce.each(tinymce.explode(name), function(name) {
bgneal@45 3196 var list = nodeFilters[name];
bgneal@45 3197
bgneal@45 3198 if (!list)
bgneal@45 3199 nodeFilters[name] = list = [];
bgneal@45 3200
bgneal@45 3201 list.push(callback);
bgneal@45 3202 });
bgneal@45 3203 };
bgneal@45 3204
bgneal@45 3205 self.addAttributeFilter = function(name, callback) {
bgneal@45 3206 tinymce.each(tinymce.explode(name), function(name) {
bgneal@45 3207 var i;
bgneal@45 3208
bgneal@45 3209 for (i = 0; i < attributeFilters.length; i++) {
bgneal@45 3210 if (attributeFilters[i].name === name) {
bgneal@45 3211 attributeFilters[i].callbacks.push(callback);
bgneal@45 3212 return;
bgneal@45 3213 }
bgneal@45 3214 }
bgneal@45 3215
bgneal@45 3216 attributeFilters.push({name: name, callbacks: [callback]});
bgneal@45 3217 });
bgneal@45 3218 };
bgneal@45 3219
bgneal@45 3220 self.parse = function(html, args) {
bgneal@45 3221 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
bgneal@45 3222 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
bgneal@45 3223 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
bgneal@45 3224
bgneal@45 3225 args = args || {};
bgneal@45 3226 matchedNodes = {};
bgneal@45 3227 matchedAttributes = {};
bgneal@45 3228 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
bgneal@45 3229 nonEmptyElements = schema.getNonEmptyElements();
bgneal@45 3230 children = schema.children;
bgneal@45 3231 validate = settings.validate;
bgneal@45 3232 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
bgneal@45 3233
bgneal@45 3234 whiteSpaceElements = schema.getWhiteSpaceElements();
bgneal@45 3235 startWhiteSpaceRegExp = /^[ \t\r\n]+/;
bgneal@45 3236 endWhiteSpaceRegExp = /[ \t\r\n]+$/;
bgneal@45 3237 allWhiteSpaceRegExp = /[ \t\r\n]+/g;
bgneal@45 3238
bgneal@45 3239 function addRootBlocks() {
bgneal@45 3240 var node = rootNode.firstChild, next, rootBlockNode;
bgneal@45 3241
bgneal@45 3242 while (node) {
bgneal@45 3243 next = node.next;
bgneal@45 3244
bgneal@45 3245 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
bgneal@45 3246 if (!rootBlockNode) {
bgneal@45 3247 // Create a new root block element
bgneal@45 3248 rootBlockNode = createNode(rootBlockName, 1);
bgneal@45 3249 rootNode.insert(rootBlockNode, node);
bgneal@45 3250 rootBlockNode.append(node);
bgneal@45 3251 } else
bgneal@45 3252 rootBlockNode.append(node);
bgneal@45 3253 } else {
bgneal@45 3254 rootBlockNode = null;
bgneal@45 3255 }
bgneal@45 3256
bgneal@45 3257 node = next;
bgneal@45 3258 };
bgneal@45 3259 };
bgneal@45 3260
bgneal@45 3261 function createNode(name, type) {
bgneal@45 3262 var node = new Node(name, type), list;
bgneal@45 3263
bgneal@45 3264 if (name in nodeFilters) {
bgneal@45 3265 list = matchedNodes[name];
bgneal@45 3266
bgneal@45 3267 if (list)
bgneal@45 3268 list.push(node);
bgneal@45 3269 else
bgneal@45 3270 matchedNodes[name] = [node];
bgneal@45 3271 }
bgneal@45 3272
bgneal@45 3273 return node;
bgneal@45 3274 };
bgneal@45 3275
bgneal@45 3276 function removeWhitespaceBefore(node) {
bgneal@45 3277 var textNode, textVal, sibling;
bgneal@45 3278
bgneal@45 3279 for (textNode = node.prev; textNode && textNode.type === 3; ) {
bgneal@45 3280 textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
bgneal@45 3281
bgneal@45 3282 if (textVal.length > 0) {
bgneal@45 3283 textNode.value = textVal;
bgneal@45 3284 textNode = textNode.prev;
bgneal@45 3285 } else {
bgneal@45 3286 sibling = textNode.prev;
bgneal@45 3287 textNode.remove();
bgneal@45 3288 textNode = sibling;
bgneal@45 3289 }
bgneal@45 3290 }
bgneal@45 3291 };
bgneal@45 3292
bgneal@45 3293 parser = new tinymce.html.SaxParser({
bgneal@45 3294 validate : validate,
bgneal@45 3295 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
bgneal@45 3296
bgneal@45 3297 cdata: function(text) {
bgneal@45 3298 node.append(createNode('#cdata', 4)).value = text;
bgneal@45 3299 },
bgneal@45 3300
bgneal@45 3301 text: function(text, raw) {
bgneal@45 3302 var textNode;
bgneal@45 3303
bgneal@45 3304 // Trim all redundant whitespace on non white space elements
bgneal@45 3305 if (!isInWhiteSpacePreservedElement) {
bgneal@45 3306 text = text.replace(allWhiteSpaceRegExp, ' ');
bgneal@45 3307
bgneal@45 3308 if (node.lastChild && blockElements[node.lastChild.name])
bgneal@45 3309 text = text.replace(startWhiteSpaceRegExp, '');
bgneal@45 3310 }
bgneal@45 3311
bgneal@45 3312 // Do we need to create the node
bgneal@45 3313 if (text.length !== 0) {
bgneal@45 3314 textNode = createNode('#text', 3);
bgneal@45 3315 textNode.raw = !!raw;
bgneal@45 3316 node.append(textNode).value = text;
bgneal@45 3317 }
bgneal@45 3318 },
bgneal@45 3319
bgneal@45 3320 comment: function(text) {
bgneal@45 3321 node.append(createNode('#comment', 8)).value = text;
bgneal@45 3322 },
bgneal@45 3323
bgneal@45 3324 pi: function(name, text) {
bgneal@45 3325 node.append(createNode(name, 7)).value = text;
bgneal@45 3326 removeWhitespaceBefore(node);
bgneal@45 3327 },
bgneal@45 3328
bgneal@45 3329 doctype: function(text) {
bgneal@45 3330 var newNode;
bgneal@45 3331
bgneal@45 3332 newNode = node.append(createNode('#doctype', 10));
bgneal@45 3333 newNode.value = text;
bgneal@45 3334 removeWhitespaceBefore(node);
bgneal@45 3335 },
bgneal@45 3336
bgneal@45 3337 start: function(name, attrs, empty) {
bgneal@45 3338 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
bgneal@45 3339
bgneal@45 3340 elementRule = validate ? schema.getElementRule(name) : {};
bgneal@45 3341 if (elementRule) {
bgneal@45 3342 newNode = createNode(elementRule.outputName || name, 1);
bgneal@45 3343 newNode.attributes = attrs;
bgneal@45 3344 newNode.shortEnded = empty;
bgneal@45 3345
bgneal@45 3346 node.append(newNode);
bgneal@45 3347
bgneal@45 3348 // Check if node is valid child of the parent node is the child is
bgneal@45 3349 // unknown we don't collect it since it's probably a custom element
bgneal@45 3350 parent = children[node.name];
bgneal@45 3351 if (parent && children[newNode.name] && !parent[newNode.name])
bgneal@45 3352 invalidChildren.push(newNode);
bgneal@45 3353
bgneal@45 3354 attrFiltersLen = attributeFilters.length;
bgneal@45 3355 while (attrFiltersLen--) {
bgneal@45 3356 attrName = attributeFilters[attrFiltersLen].name;
bgneal@45 3357
bgneal@45 3358 if (attrName in attrs.map) {
bgneal@45 3359 list = matchedAttributes[attrName];
bgneal@45 3360
bgneal@45 3361 if (list)
bgneal@45 3362 list.push(newNode);
bgneal@45 3363 else
bgneal@45 3364 matchedAttributes[attrName] = [newNode];
bgneal@45 3365 }
bgneal@45 3366 }
bgneal@45 3367
bgneal@45 3368 // Trim whitespace before block
bgneal@45 3369 if (blockElements[name])
bgneal@45 3370 removeWhitespaceBefore(newNode);
bgneal@45 3371
bgneal@45 3372 // Change current node if the element wasn't empty i.e not <br /> or <img />
bgneal@45 3373 if (!empty)
bgneal@45 3374 node = newNode;
bgneal@45 3375
bgneal@45 3376 // Check if we are inside a whitespace preserved element
bgneal@45 3377 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
bgneal@45 3378 isInWhiteSpacePreservedElement = true;
bgneal@45 3379 }
bgneal@45 3380 }
bgneal@45 3381 },
bgneal@45 3382
bgneal@45 3383 end: function(name) {
bgneal@45 3384 var textNode, elementRule, text, sibling, tempNode;
bgneal@45 3385
bgneal@45 3386 elementRule = validate ? schema.getElementRule(name) : {};
bgneal@45 3387 if (elementRule) {
bgneal@45 3388 if (blockElements[name]) {
bgneal@45 3389 if (!isInWhiteSpacePreservedElement) {
bgneal@45 3390 // Trim whitespace at beginning of block
bgneal@45 3391 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
bgneal@45 3392 text = textNode.value.replace(startWhiteSpaceRegExp, '');
bgneal@45 3393
bgneal@45 3394 if (text.length > 0) {
bgneal@45 3395 textNode.value = text;
bgneal@45 3396 textNode = textNode.next;
bgneal@45 3397 } else {
bgneal@45 3398 sibling = textNode.next;
bgneal@45 3399 textNode.remove();
bgneal@45 3400 textNode = sibling;
bgneal@45 3401 }
bgneal@45 3402 }
bgneal@45 3403
bgneal@45 3404 // Trim whitespace at end of block
bgneal@45 3405 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
bgneal@45 3406 text = textNode.value.replace(endWhiteSpaceRegExp, '');
bgneal@45 3407
bgneal@45 3408 if (text.length > 0) {
bgneal@45 3409 textNode.value = text;
bgneal@45 3410 textNode = textNode.prev;
bgneal@45 3411 } else {
bgneal@45 3412 sibling = textNode.prev;
bgneal@45 3413 textNode.remove();
bgneal@45 3414 textNode = sibling;
bgneal@45 3415 }
bgneal@45 3416 }
bgneal@45 3417 }
bgneal@45 3418
bgneal@45 3419 // Trim start white space
bgneal@45 3420 textNode = node.prev;
bgneal@45 3421 if (textNode && textNode.type === 3) {
bgneal@45 3422 text = textNode.value.replace(startWhiteSpaceRegExp, '');
bgneal@45 3423
bgneal@45 3424 if (text.length > 0)
bgneal@45 3425 textNode.value = text;
bgneal@45 3426 else
bgneal@45 3427 textNode.remove();
bgneal@45 3428 }
bgneal@45 3429 }
bgneal@45 3430
bgneal@45 3431 // Check if we exited a whitespace preserved element
bgneal@45 3432 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
bgneal@45 3433 isInWhiteSpacePreservedElement = false;
bgneal@45 3434 }
bgneal@45 3435
bgneal@45 3436 // Handle empty nodes
bgneal@45 3437 if (elementRule.removeEmpty || elementRule.paddEmpty) {
bgneal@45 3438 if (node.isEmpty(nonEmptyElements)) {
bgneal@45 3439 if (elementRule.paddEmpty)
bgneal@45 3440 node.empty().append(new Node('#text', '3')).value = '\u00a0';
bgneal@45 3441 else {
bgneal@45 3442 // Leave nodes that have a name like <a name="name">
bgneal@45 3443 if (!node.attributes.map.name) {
bgneal@45 3444 tempNode = node.parent;
bgneal@45 3445 node.empty().remove();
bgneal@45 3446 node = tempNode;
bgneal@45 3447 return;
bgneal@45 3448 }
bgneal@45 3449 }
bgneal@45 3450 }
bgneal@45 3451 }
bgneal@45 3452
bgneal@45 3453 node = node.parent;
bgneal@45 3454 }
bgneal@45 3455 }
bgneal@45 3456 }, schema);
bgneal@45 3457
bgneal@45 3458 rootNode = node = new Node(args.context || settings.root_name, 11);
bgneal@45 3459
bgneal@45 3460 parser.parse(html);
bgneal@45 3461
bgneal@45 3462 // Fix invalid children or report invalid children in a contextual parsing
bgneal@45 3463 if (validate && invalidChildren.length) {
bgneal@45 3464 if (!args.context)
bgneal@45 3465 fixInvalidChildren(invalidChildren);
bgneal@45 3466 else
bgneal@45 3467 args.invalid = true;
bgneal@45 3468 }
bgneal@45 3469
bgneal@45 3470 // Wrap nodes in the root into block elements if the root is body
bgneal@45 3471 if (rootBlockName && rootNode.name == 'body')
bgneal@45 3472 addRootBlocks();
bgneal@45 3473
bgneal@45 3474 // Run filters only when the contents is valid
bgneal@45 3475 if (!args.invalid) {
bgneal@45 3476 // Run node filters
bgneal@45 3477 for (name in matchedNodes) {
bgneal@45 3478 list = nodeFilters[name];
bgneal@45 3479 nodes = matchedNodes[name];
bgneal@45 3480
bgneal@45 3481 // Remove already removed children
bgneal@45 3482 fi = nodes.length;
bgneal@45 3483 while (fi--) {
bgneal@45 3484 if (!nodes[fi].parent)
bgneal@45 3485 nodes.splice(fi, 1);
bgneal@45 3486 }
bgneal@45 3487
bgneal@45 3488 for (i = 0, l = list.length; i < l; i++)
bgneal@45 3489 list[i](nodes, name, args);
bgneal@45 3490 }
bgneal@45 3491
bgneal@45 3492 // Run attribute filters
bgneal@45 3493 for (i = 0, l = attributeFilters.length; i < l; i++) {
bgneal@45 3494 list = attributeFilters[i];
bgneal@45 3495
bgneal@45 3496 if (list.name in matchedAttributes) {
bgneal@45 3497 nodes = matchedAttributes[list.name];
bgneal@45 3498
bgneal@45 3499 // Remove already removed children
bgneal@45 3500 fi = nodes.length;
bgneal@45 3501 while (fi--) {
bgneal@45 3502 if (!nodes[fi].parent)
bgneal@45 3503 nodes.splice(fi, 1);
bgneal@45 3504 }
bgneal@45 3505
bgneal@45 3506 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
bgneal@45 3507 list.callbacks[fi](nodes, list.name, args);
bgneal@45 3508 }
bgneal@45 3509 }
bgneal@45 3510 }
bgneal@45 3511
bgneal@45 3512 return rootNode;
bgneal@45 3513 };
bgneal@45 3514
bgneal@45 3515 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
bgneal@45 3516 // make it possible to place the caret inside empty blocks. This logic tries to remove
bgneal@45 3517 // these elements and keep br elements that where intended to be there intact
bgneal@45 3518 if (settings.remove_trailing_brs) {
bgneal@45 3519 self.addNodeFilter('br', function(nodes, name) {
bgneal@45 3520 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
bgneal@45 3521 nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
bgneal@45 3522
bgneal@45 3523 // Remove brs from body element as well
bgneal@45 3524 blockElements.body = 1;
bgneal@45 3525
bgneal@45 3526 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
bgneal@45 3527 for (i = 0; i < l; i++) {
bgneal@45 3528 node = nodes[i];
bgneal@45 3529 parent = node.parent;
bgneal@45 3530
bgneal@45 3531 if (blockElements[node.parent.name] && node === parent.lastChild) {
bgneal@45 3532 // Loop all nodes to the right of the current node and check for other BR elements
bgneal@45 3533 // excluding bookmarks since they are invisible
bgneal@45 3534 prev = node.prev;
bgneal@45 3535 while (prev) {
bgneal@45 3536 prevName = prev.name;
bgneal@45 3537
bgneal@45 3538 // Ignore bookmarks
bgneal@45 3539 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
bgneal@45 3540 // Found a non BR element
bgneal@45 3541 if (prevName !== "br")
bgneal@45 3542 break;
bgneal@45 3543
bgneal@45 3544 // Found another br it's a <br><br> structure then don't remove anything
bgneal@45 3545 if (prevName === 'br') {
bgneal@45 3546 node = null;
bgneal@45 3547 break;
bgneal@45 3548 }
bgneal@45 3549 }
bgneal@45 3550
bgneal@45 3551 prev = prev.prev;
bgneal@45 3552 }
bgneal@45 3553
bgneal@45 3554 if (node) {
bgneal@45 3555 node.remove();
bgneal@45 3556
bgneal@45 3557 // Is the parent to be considered empty after we removed the BR
bgneal@45 3558 if (parent.isEmpty(nonEmptyElements)) {
bgneal@45 3559 elementRule = schema.getElementRule(parent.name);
bgneal@45 3560
bgneal@45 3561 // Remove or padd the element depending on schema rule
bgneal@45 3562 if (elementRule) {
bgneal@45 3563 if (elementRule.removeEmpty)
bgneal@45 3564 parent.remove();
bgneal@45 3565 else if (elementRule.paddEmpty)
bgneal@45 3566 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
bgneal@45 3567 }
bgneal@45 3568 }
bgneal@45 3569 }
bgneal@45 3570 }
bgneal@45 3571 }
bgneal@45 3572 });
bgneal@45 3573 }
bgneal@45 3574 }
bgneal@45 3575 })(tinymce);
bgneal@45 3576
bgneal@45 3577 tinymce.html.Writer = function(settings) {
bgneal@45 3578 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
bgneal@45 3579
bgneal@45 3580 settings = settings || {};
bgneal@45 3581 indent = settings.indent;
bgneal@45 3582 indentBefore = tinymce.makeMap(settings.indent_before || '');
bgneal@45 3583 indentAfter = tinymce.makeMap(settings.indent_after || '');
bgneal@45 3584 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
bgneal@45 3585 htmlOutput = settings.element_format == "html";
bgneal@45 3586
bgneal@45 3587 return {
bgneal@45 3588 start: function(name, attrs, empty) {
bgneal@45 3589 var i, l, attr, value;
bgneal@45 3590
bgneal@45 3591 if (indent && indentBefore[name] && html.length > 0) {
bgneal@45 3592 value = html[html.length - 1];
bgneal@45 3593
bgneal@45 3594 if (value.length > 0 && value !== '\n')
bgneal@45 3595 html.push('\n');
bgneal@45 3596 }
bgneal@45 3597
bgneal@45 3598 html.push('<', name);
bgneal@45 3599
bgneal@45 3600 if (attrs) {
bgneal@45 3601 for (i = 0, l = attrs.length; i < l; i++) {
bgneal@45 3602 attr = attrs[i];
bgneal@45 3603 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
bgneal@45 3604 }
bgneal@45 3605 }
bgneal@45 3606
bgneal@45 3607 if (!empty || htmlOutput)
bgneal@45 3608 html[html.length] = '>';
bgneal@45 3609 else
bgneal@45 3610 html[html.length] = ' />';
bgneal@45 3611
bgneal@45 3612 if (empty && indent && indentAfter[name] && html.length > 0) {
bgneal@45 3613 value = html[html.length - 1];
bgneal@45 3614
bgneal@45 3615 if (value.length > 0 && value !== '\n')
bgneal@45 3616 html.push('\n');
bgneal@45 3617 }
bgneal@45 3618 },
bgneal@45 3619
bgneal@45 3620 end: function(name) {
bgneal@45 3621 var value;
bgneal@45 3622
bgneal@45 3623 /*if (indent && indentBefore[name] && html.length > 0) {
bgneal@45 3624 value = html[html.length - 1];
bgneal@45 3625
bgneal@45 3626 if (value.length > 0 && value !== '\n')
bgneal@45 3627 html.push('\n');
bgneal@45 3628 }*/
bgneal@45 3629
bgneal@45 3630 html.push('</', name, '>');
bgneal@45 3631
bgneal@45 3632 if (indent && indentAfter[name] && html.length > 0) {
bgneal@45 3633 value = html[html.length - 1];
bgneal@45 3634
bgneal@45 3635 if (value.length > 0 && value !== '\n')
bgneal@45 3636 html.push('\n');
bgneal@45 3637 }
bgneal@45 3638 },
bgneal@45 3639
bgneal@45 3640 text: function(text, raw) {
bgneal@45 3641 if (text.length > 0)
bgneal@45 3642 html[html.length] = raw ? text : encode(text);
bgneal@45 3643 },
bgneal@45 3644
bgneal@45 3645 cdata: function(text) {
bgneal@45 3646 html.push('<![CDATA[', text, ']]>');
bgneal@45 3647 },
bgneal@45 3648
bgneal@45 3649 comment: function(text) {
bgneal@45 3650 html.push('<!--', text, '-->');
bgneal@45 3651 },
bgneal@45 3652
bgneal@45 3653 pi: function(name, text) {
bgneal@45 3654 if (text)
bgneal@45 3655 html.push('<?', name, ' ', text, '?>');
bgneal@45 3656 else
bgneal@45 3657 html.push('<?', name, '?>');
bgneal@45 3658
bgneal@45 3659 if (indent)
bgneal@45 3660 html.push('\n');
bgneal@45 3661 },
bgneal@45 3662
bgneal@45 3663 doctype: function(text) {
bgneal@45 3664 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
bgneal@45 3665 },
bgneal@45 3666
bgneal@45 3667 reset: function() {
bgneal@45 3668 html.length = 0;
bgneal@45 3669 },
bgneal@45 3670
bgneal@45 3671 getContent: function() {
bgneal@45 3672 return html.join('').replace(/\n$/, '');
bgneal@45 3673 }
bgneal@45 3674 };
bgneal@45 3675 };
bgneal@45 3676
bgneal@45 3677 (function(tinymce) {
bgneal@45 3678 tinymce.html.Serializer = function(settings, schema) {
bgneal@45 3679 var self = this, writer = new tinymce.html.Writer(settings);
bgneal@45 3680
bgneal@45 3681 settings = settings || {};
bgneal@45 3682 settings.validate = "validate" in settings ? settings.validate : true;
bgneal@45 3683
bgneal@45 3684 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@45 3685 self.writer = writer;
bgneal@45 3686
bgneal@45 3687 self.serialize = function(node) {
bgneal@45 3688 var handlers, validate;
bgneal@45 3689
bgneal@45 3690 validate = settings.validate;
bgneal@45 3691
bgneal@45 3692 handlers = {
bgneal@45 3693 // #text
bgneal@45 3694 3: function(node, raw) {
bgneal@45 3695 writer.text(node.value, node.raw);
bgneal@45 3696 },
bgneal@45 3697
bgneal@45 3698 // #comment
bgneal@45 3699 8: function(node) {
bgneal@45 3700 writer.comment(node.value);
bgneal@45 3701 },
bgneal@45 3702
bgneal@45 3703 // Processing instruction
bgneal@45 3704 7: function(node) {
bgneal@45 3705 writer.pi(node.name, node.value);
bgneal@45 3706 },
bgneal@45 3707
bgneal@45 3708 // Doctype
bgneal@45 3709 10: function(node) {
bgneal@45 3710 writer.doctype(node.value);
bgneal@45 3711 },
bgneal@45 3712
bgneal@45 3713 // CDATA
bgneal@45 3714 4: function(node) {
bgneal@45 3715 writer.cdata(node.value);
bgneal@45 3716 },
bgneal@45 3717
bgneal@45 3718 // Document fragment
bgneal@45 3719 11: function(node) {
bgneal@45 3720 if ((node = node.firstChild)) {
bgneal@45 3721 do {
bgneal@45 3722 walk(node);
bgneal@45 3723 } while (node = node.next);
bgneal@45 3724 }
bgneal@45 3725 }
bgneal@45 3726 };
bgneal@45 3727
bgneal@45 3728 writer.reset();
bgneal@45 3729
bgneal@45 3730 function walk(node) {
bgneal@45 3731 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
bgneal@45 3732
bgneal@45 3733 if (!handler) {
bgneal@45 3734 name = node.name;
bgneal@45 3735 isEmpty = node.shortEnded;
bgneal@45 3736 attrs = node.attributes;
bgneal@45 3737
bgneal@45 3738 // Sort attributes
bgneal@45 3739 if (validate && attrs && attrs.length > 1) {
bgneal@45 3740 sortedAttrs = [];
bgneal@45 3741 sortedAttrs.map = {};
bgneal@45 3742
bgneal@45 3743 elementRule = schema.getElementRule(node.name);
bgneal@45 3744 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
bgneal@45 3745 attrName = elementRule.attributesOrder[i];
bgneal@45 3746
bgneal@45 3747 if (attrName in attrs.map) {
bgneal@45 3748 attrValue = attrs.map[attrName];
bgneal@45 3749 sortedAttrs.map[attrName] = attrValue;
bgneal@45 3750 sortedAttrs.push({name: attrName, value: attrValue});
bgneal@45 3751 }
bgneal@45 3752 }
bgneal@45 3753
bgneal@45 3754 for (i = 0, l = attrs.length; i < l; i++) {
bgneal@45 3755 attrName = attrs[i].name;
bgneal@45 3756
bgneal@45 3757 if (!(attrName in sortedAttrs.map)) {
bgneal@45 3758 attrValue = attrs.map[attrName];
bgneal@45 3759 sortedAttrs.map[attrName] = attrValue;
bgneal@45 3760 sortedAttrs.push({name: attrName, value: attrValue});
bgneal@45 3761 }
bgneal@45 3762 }
bgneal@45 3763
bgneal@45 3764 attrs = sortedAttrs;
bgneal@45 3765 }
bgneal@45 3766
bgneal@45 3767 writer.start(node.name, attrs, isEmpty);
bgneal@45 3768
bgneal@45 3769 if (!isEmpty) {
bgneal@45 3770 if ((node = node.firstChild)) {
bgneal@45 3771 do {
bgneal@45 3772 walk(node);
bgneal@45 3773 } while (node = node.next);
bgneal@45 3774 }
bgneal@45 3775
bgneal@45 3776 writer.end(name);
bgneal@45 3777 }
bgneal@45 3778 } else
bgneal@45 3779 handler(node);
bgneal@45 3780 }
bgneal@45 3781
bgneal@45 3782 // Serialize element and treat all non elements as fragments
bgneal@45 3783 if (node.type == 1 && !settings.inner)
bgneal@45 3784 walk(node);
bgneal@45 3785 else
bgneal@45 3786 handlers[11](node);
bgneal@45 3787
bgneal@45 3788 return writer.getContent();
bgneal@45 3789 };
bgneal@45 3790 }
bgneal@45 3791 })(tinymce);
bgneal@45 3792
bgneal@45 3793 // JSLint defined globals
bgneal@45 3794 /*global tinymce:false, window:false */
bgneal@45 3795
bgneal@45 3796 tinymce.dom = {};
bgneal@45 3797
bgneal@45 3798 (function(namespace, expando) {
bgneal@45 3799 var w3cEventModel = !!document.addEventListener;
bgneal@45 3800
bgneal@45 3801 function addEvent(target, name, callback, capture) {
bgneal@45 3802 if (target.addEventListener) {
bgneal@45 3803 target.addEventListener(name, callback, capture || false);
bgneal@45 3804 } else if (target.attachEvent) {
bgneal@45 3805 target.attachEvent('on' + name, callback);
bgneal@45 3806 }
bgneal@45 3807 }
bgneal@45 3808
bgneal@45 3809 function removeEvent(target, name, callback, capture) {
bgneal@45 3810 if (target.removeEventListener) {
bgneal@45 3811 target.removeEventListener(name, callback, capture || false);
bgneal@45 3812 } else if (target.detachEvent) {
bgneal@45 3813 target.detachEvent('on' + name, callback);
bgneal@45 3814 }
bgneal@45 3815 }
bgneal@45 3816
bgneal@45 3817 function fix(original_event, data) {
bgneal@45 3818 var name, event = data || {};
bgneal@45 3819
bgneal@45 3820 // Dummy function that gets replaced on the delegation state functions
bgneal@45 3821 function returnFalse() {
bgneal@45 3822 return false;
bgneal@45 3823 }
bgneal@45 3824
bgneal@45 3825 // Dummy function that gets replaced on the delegation state functions
bgneal@45 3826 function returnTrue() {
bgneal@45 3827 return true;
bgneal@45 3828 }
bgneal@45 3829
bgneal@45 3830 // Copy all properties from the original event
bgneal@45 3831 for (name in original_event) {
bgneal@45 3832 // layerX/layerY is deprecated in Chrome and produces a warning
bgneal@45 3833 if (name !== "layerX" && name !== "layerY") {
bgneal@45 3834 event[name] = original_event[name];
bgneal@45 3835 }
bgneal@45 3836 }
bgneal@45 3837
bgneal@45 3838 // Normalize target IE uses srcElement
bgneal@45 3839 if (!event.target) {
bgneal@45 3840 event.target = event.srcElement || document;
bgneal@45 3841 }
bgneal@45 3842
bgneal@45 3843 // Add preventDefault method
bgneal@45 3844 event.preventDefault = function() {
bgneal@45 3845 event.isDefaultPrevented = returnTrue;
bgneal@45 3846
bgneal@45 3847 // Execute preventDefault on the original event object
bgneal@45 3848 if (original_event) {
bgneal@45 3849 if (original_event.preventDefault) {
bgneal@45 3850 original_event.preventDefault();
bgneal@45 3851 } else {
bgneal@45 3852 original_event.returnValue = false; // IE
bgneal@45 3853 }
bgneal@45 3854 }
bgneal@45 3855 };
bgneal@45 3856
bgneal@45 3857 // Add stopPropagation
bgneal@45 3858 event.stopPropagation = function() {
bgneal@45 3859 event.isPropagationStopped = returnTrue;
bgneal@45 3860
bgneal@45 3861 // Execute stopPropagation on the original event object
bgneal@45 3862 if (original_event) {
bgneal@45 3863 if (original_event.stopPropagation) {
bgneal@45 3864 original_event.stopPropagation();
bgneal@45 3865 } else {
bgneal@45 3866 original_event.cancelBubble = true; // IE
bgneal@45 3867 }
bgneal@45 3868 }
bgneal@45 3869 };
bgneal@45 3870
bgneal@45 3871 // Add stopImmediatePropagation
bgneal@45 3872 event.stopImmediatePropagation = function() {
bgneal@45 3873 event.isImmediatePropagationStopped = returnTrue;
bgneal@45 3874 event.stopPropagation();
bgneal@45 3875 };
bgneal@45 3876
bgneal@45 3877 // Add event delegation states
bgneal@45 3878 if (!event.isDefaultPrevented) {
bgneal@45 3879 event.isDefaultPrevented = returnFalse;
bgneal@45 3880 event.isPropagationStopped = returnFalse;
bgneal@45 3881 event.isImmediatePropagationStopped = returnFalse;
bgneal@45 3882 }
bgneal@45 3883
bgneal@45 3884 return event;
bgneal@45 3885 }
bgneal@45 3886
bgneal@45 3887 function bindOnReady(win, callback, event_utils) {
bgneal@45 3888 var doc = win.document, event = {type: 'ready'};
bgneal@45 3889
bgneal@45 3890 // Gets called when the DOM is ready
bgneal@45 3891 function readyHandler() {
bgneal@45 3892 if (!event_utils.domLoaded) {
bgneal@45 3893 event_utils.domLoaded = true;
bgneal@45 3894 callback(event);
bgneal@45 3895 }
bgneal@45 3896 }
bgneal@45 3897
bgneal@45 3898 // Use W3C method
bgneal@45 3899 if (w3cEventModel) {
bgneal@45 3900 addEvent(win, 'DOMContentLoaded', readyHandler);
bgneal@45 3901 } else {
bgneal@45 3902 // Use IE method
bgneal@45 3903 addEvent(doc, "readystatechange", function() {
bgneal@45 3904 if (doc.readyState === "complete") {
bgneal@45 3905 removeEvent(doc, "readystatechange", arguments.callee);
bgneal@45 3906 readyHandler();
bgneal@45 3907 }
bgneal@45 3908 });
bgneal@45 3909
bgneal@45 3910 // Wait until we can scroll, when we can the DOM is initialized
bgneal@45 3911 if (doc.documentElement.doScroll && win === win.top) {
bgneal@45 3912 (function() {
bgneal@45 3913 try {
bgneal@45 3914 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
bgneal@45 3915 // http://javascript.nwbox.com/IEContentLoaded/
bgneal@45 3916 doc.documentElement.doScroll("left");
bgneal@45 3917 } catch (ex) {
bgneal@45 3918 setTimeout(arguments.callee, 0);
bgneal@45 3919 return;
bgneal@45 3920 }
bgneal@45 3921
bgneal@45 3922 readyHandler();
bgneal@45 3923 })();
bgneal@45 3924 }
bgneal@45 3925 }
bgneal@45 3926
bgneal@45 3927 // Fallback if any of the above methods should fail for some odd reason
bgneal@45 3928 addEvent(win, 'load', readyHandler);
bgneal@45 3929 }
bgneal@45 3930
bgneal@45 3931 function EventUtils(proxy) {
bgneal@45 3932 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
bgneal@45 3933
bgneal@45 3934 hasMouseEnterLeave = "onmouseenter" in document.documentElement;
bgneal@45 3935 hasFocusIn = "onfocusin" in document.documentElement;
bgneal@45 3936 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
bgneal@45 3937 count = 1;
bgneal@45 3938
bgneal@45 3939 // State if the DOMContentLoaded was executed or not
bgneal@45 3940 self.domLoaded = false;
bgneal@45 3941 self.events = events;
bgneal@45 3942
bgneal@45 3943 function executeHandlers(evt, id) {
bgneal@45 3944 var callbackList, i, l, callback;
bgneal@45 3945
bgneal@45 3946 callbackList = events[id][evt.type];
bgneal@45 3947 if (callbackList) {
bgneal@45 3948 for (i = 0, l = callbackList.length; i < l; i++) {
bgneal@45 3949 callback = callbackList[i];
bgneal@45 3950
bgneal@45 3951 // Check if callback exists might be removed if a unbind is called inside the callback
bgneal@45 3952 if (callback && callback.func.call(callback.scope, evt) === false) {
bgneal@45 3953 evt.preventDefault();
bgneal@45 3954 }
bgneal@45 3955
bgneal@45 3956 // Should we stop propagation to immediate listeners
bgneal@45 3957 if (evt.isImmediatePropagationStopped()) {
bgneal@45 3958 return;
bgneal@45 3959 }
bgneal@45 3960 }
bgneal@45 3961 }
bgneal@45 3962 }
bgneal@45 3963
bgneal@45 3964 self.bind = function(target, names, callback, scope) {
bgneal@45 3965 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
bgneal@45 3966
bgneal@45 3967 // Native event handler function patches the event and executes the callbacks for the expando
bgneal@45 3968 function defaultNativeHandler(evt) {
bgneal@45 3969 executeHandlers(fix(evt || win.event), id);
bgneal@45 3970 }
bgneal@45 3971
bgneal@45 3972 // Don't bind to text nodes or comments
bgneal@45 3973 if (!target || target.nodeType === 3 || target.nodeType === 8) {
bgneal@45 3974 return;
bgneal@45 3975 }
bgneal@45 3976
bgneal@45 3977 // Create or get events id for the target
bgneal@45 3978 if (!target[expando]) {
bgneal@45 3979 id = count++;
bgneal@45 3980 target[expando] = id;
bgneal@45 3981 events[id] = {};
bgneal@45 3982 } else {
bgneal@45 3983 id = target[expando];
bgneal@45 3984
bgneal@45 3985 if (!events[id]) {
bgneal@45 3986 events[id] = {};
bgneal@45 3987 }
bgneal@45 3988 }
bgneal@45 3989
bgneal@45 3990 // Setup the specified scope or use the target as a default
bgneal@45 3991 scope = scope || target;
bgneal@45 3992
bgneal@45 3993 // Split names and bind each event, enables you to bind multiple events with one call
bgneal@45 3994 names = names.split(' ');
bgneal@45 3995 i = names.length;
bgneal@45 3996 while (i--) {
bgneal@45 3997 name = names[i];
bgneal@45 3998 nativeHandler = defaultNativeHandler;
bgneal@45 3999 fakeName = capture = false;
bgneal@45 4000
bgneal@45 4001 // Use ready instead of DOMContentLoaded
bgneal@45 4002 if (name === "DOMContentLoaded") {
bgneal@45 4003 name = "ready";
bgneal@45 4004 }
bgneal@45 4005
bgneal@45 4006 // DOM is already ready
bgneal@45 4007 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
bgneal@45 4008 self.domLoaded = true;
bgneal@45 4009 callback.call(scope, fix({type: name}));
bgneal@45 4010 continue;
bgneal@45 4011 }
bgneal@45 4012
bgneal@45 4013 // Handle mouseenter/mouseleaver
bgneal@45 4014 if (!hasMouseEnterLeave) {
bgneal@45 4015 fakeName = mouseEnterLeave[name];
bgneal@45 4016
bgneal@45 4017 if (fakeName) {
bgneal@45 4018 nativeHandler = function(evt) {
bgneal@45 4019 var current, related;
bgneal@45 4020
bgneal@45 4021 current = evt.currentTarget;
bgneal@45 4022 related = evt.relatedTarget;
bgneal@45 4023
bgneal@45 4024 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
bgneal@45 4025 if (related && current.contains) {
bgneal@45 4026 // Use contains for performance
bgneal@45 4027 related = current.contains(related);
bgneal@45 4028 } else {
bgneal@45 4029 while (related && related !== current) {
bgneal@45 4030 related = related.parentNode;
bgneal@45 4031 }
bgneal@45 4032 }
bgneal@45 4033
bgneal@45 4034 // Fire fake event
bgneal@45 4035 if (!related) {
bgneal@45 4036 evt = fix(evt || win.event);
bgneal@45 4037 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
bgneal@45 4038 evt.target = current;
bgneal@45 4039 executeHandlers(evt, id);
bgneal@45 4040 }
bgneal@45 4041 };
bgneal@45 4042 }
bgneal@45 4043 }
bgneal@45 4044
bgneal@45 4045 // Fake bubbeling of focusin/focusout
bgneal@45 4046 if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
bgneal@45 4047 capture = true;
bgneal@45 4048 fakeName = name === "focusin" ? "focus" : "blur";
bgneal@45 4049 nativeHandler = function(evt) {
bgneal@45 4050 evt = fix(evt || win.event);
bgneal@45 4051 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
bgneal@45 4052 executeHandlers(evt, id);
bgneal@45 4053 };
bgneal@45 4054 }
bgneal@45 4055
bgneal@45 4056 // Setup callback list and bind native event
bgneal@45 4057 callbackList = events[id][name];
bgneal@45 4058 if (!callbackList) {
bgneal@45 4059 events[id][name] = callbackList = [{func: callback, scope: scope}];
bgneal@45 4060 callbackList.fakeName = fakeName;
bgneal@45 4061 callbackList.capture = capture;
bgneal@45 4062
bgneal@45 4063 // Add the nativeHandler to the callback list so that we can later unbind it
bgneal@45 4064 callbackList.nativeHandler = nativeHandler;
bgneal@45 4065 if (!w3cEventModel) {
bgneal@45 4066 callbackList.proxyHandler = proxy(id);
bgneal@45 4067 }
bgneal@45 4068
bgneal@45 4069 // Check if the target has native events support
bgneal@45 4070 if (name === "ready") {
bgneal@45 4071 bindOnReady(target, nativeHandler, self);
bgneal@45 4072 } else {
bgneal@45 4073 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
bgneal@45 4074 }
bgneal@45 4075 } else {
bgneal@45 4076 // If it already has an native handler then just push the callback
bgneal@45 4077 callbackList.push({func: callback, scope: scope});
bgneal@45 4078 }
bgneal@45 4079 }
bgneal@45 4080
bgneal@45 4081 target = callbackList = 0; // Clean memory for IE
bgneal@45 4082
bgneal@45 4083 return callback;
bgneal@45 4084 };
bgneal@45 4085
bgneal@45 4086 self.unbind = function(target, names, callback) {
bgneal@45 4087 var id, callbackList, i, ci, name, eventMap;
bgneal@45 4088
bgneal@45 4089 // Don't bind to text nodes or comments
bgneal@45 4090 if (!target || target.nodeType === 3 || target.nodeType === 8) {
bgneal@45 4091 return self;
bgneal@45 4092 }
bgneal@45 4093
bgneal@45 4094 // Unbind event or events if the target has the expando
bgneal@45 4095 id = target[expando];
bgneal@45 4096 if (id) {
bgneal@45 4097 eventMap = events[id];
bgneal@45 4098
bgneal@45 4099 // Specific callback
bgneal@45 4100 if (names) {
bgneal@45 4101 names = names.split(' ');
bgneal@45 4102 i = names.length;
bgneal@45 4103 while (i--) {
bgneal@45 4104 name = names[i];
bgneal@45 4105 callbackList = eventMap[name];
bgneal@45 4106
bgneal@45 4107 // Unbind the event if it exists in the map
bgneal@45 4108 if (callbackList) {
bgneal@45 4109 // Remove specified callback
bgneal@45 4110 if (callback) {
bgneal@45 4111 ci = callbackList.length;
bgneal@45 4112 while (ci--) {
bgneal@45 4113 if (callbackList[ci].func === callback) {
bgneal@45 4114 callbackList.splice(ci, 1);
bgneal@45 4115 }
bgneal@45 4116 }
bgneal@45 4117 }
bgneal@45 4118
bgneal@45 4119 // Remove all callbacks if there isn't a specified callback or there is no callbacks left
bgneal@45 4120 if (!callback || callbackList.length === 0) {
bgneal@45 4121 delete eventMap[name];
bgneal@45 4122 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
bgneal@45 4123 }
bgneal@45 4124 }
bgneal@45 4125 }
bgneal@45 4126 } else {
bgneal@45 4127 // All events for a specific element
bgneal@45 4128 for (name in eventMap) {
bgneal@45 4129 callbackList = eventMap[name];
bgneal@45 4130 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
bgneal@45 4131 }
bgneal@45 4132
bgneal@45 4133 eventMap = {};
bgneal@45 4134 }
bgneal@45 4135
bgneal@45 4136 // Check if object is empty, if it isn't then we won't remove the expando map
bgneal@45 4137 for (name in eventMap) {
bgneal@45 4138 return self;
bgneal@45 4139 }
bgneal@45 4140
bgneal@45 4141 // Delete event object
bgneal@45 4142 delete events[id];
bgneal@45 4143
bgneal@45 4144 // Remove expando from target
bgneal@45 4145 try {
bgneal@45 4146 // IE will fail here since it can't delete properties from window
bgneal@45 4147 delete target[expando];
bgneal@45 4148 } catch (ex) {
bgneal@45 4149 // IE will set it to null
bgneal@45 4150 target[expando] = null;
bgneal@45 4151 }
bgneal@45 4152 }
bgneal@45 4153
bgneal@45 4154 return self;
bgneal@45 4155 };
bgneal@45 4156
bgneal@45 4157 self.fire = function(target, name, args) {
bgneal@45 4158 var id, event;
bgneal@45 4159
bgneal@45 4160 // Don't bind to text nodes or comments
bgneal@45 4161 if (!target || target.nodeType === 3 || target.nodeType === 8) {
bgneal@45 4162 return self;
bgneal@45 4163 }
bgneal@45 4164
bgneal@45 4165 // Build event object by patching the args
bgneal@45 4166 event = fix(null, args);
bgneal@45 4167 event.type = name;
bgneal@45 4168
bgneal@45 4169 do {
bgneal@45 4170 // Found an expando that means there is listeners to execute
bgneal@45 4171 id = target[expando];
bgneal@45 4172 if (id) {
bgneal@45 4173 executeHandlers(event, id);
bgneal@45 4174 }
bgneal@45 4175
bgneal@45 4176 // Walk up the DOM
bgneal@45 4177 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
bgneal@45 4178 } while (target && !event.isPropagationStopped());
bgneal@45 4179
bgneal@45 4180 return self;
bgneal@45 4181 };
bgneal@45 4182
bgneal@45 4183 self.clean = function(target) {
bgneal@45 4184 var i, children, unbind = self.unbind;
bgneal@45 4185
bgneal@45 4186 // Don't bind to text nodes or comments
bgneal@45 4187 if (!target || target.nodeType === 3 || target.nodeType === 8) {
bgneal@45 4188 return self;
bgneal@45 4189 }
bgneal@45 4190
bgneal@45 4191 // Unbind any element on the specificed target
bgneal@45 4192 if (target[expando]) {
bgneal@45 4193 unbind(target);
bgneal@45 4194 }
bgneal@45 4195
bgneal@45 4196 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
bgneal@45 4197 if (!target.getElementsByTagName) {
bgneal@45 4198 target = target.document;
bgneal@45 4199 }
bgneal@45 4200
bgneal@45 4201 // Remove events from each child element
bgneal@45 4202 if (target && target.getElementsByTagName) {
bgneal@45 4203 unbind(target);
bgneal@45 4204
bgneal@45 4205 children = target.getElementsByTagName('*');
bgneal@45 4206 i = children.length;
bgneal@45 4207 while (i--) {
bgneal@45 4208 target = children[i];
bgneal@45 4209
bgneal@45 4210 if (target[expando]) {
bgneal@45 4211 unbind(target);
bgneal@45 4212 }
bgneal@45 4213 }
bgneal@45 4214 }
bgneal@45 4215
bgneal@45 4216 return self;
bgneal@45 4217 };
bgneal@45 4218
bgneal@45 4219 self.callNativeHandler = function(id, evt) {
bgneal@45 4220 if (events) {
bgneal@45 4221 events[id][evt.type].nativeHandler(evt);
bgneal@45 4222 }
bgneal@45 4223 };
bgneal@45 4224
bgneal@45 4225 self.destory = function() {
bgneal@45 4226 events = {};
bgneal@45 4227 };
bgneal@45 4228
bgneal@45 4229 // Legacy function calls
bgneal@45 4230
bgneal@45 4231 self.add = function(target, events, func, scope) {
bgneal@45 4232 // Old API supported direct ID assignment
bgneal@45 4233 if (typeof(target) === "string") {
bgneal@45 4234 target = document.getElementById(target);
bgneal@45 4235 }
bgneal@45 4236
bgneal@45 4237 // Old API supported multiple targets
bgneal@45 4238 if (target && target instanceof Array) {
bgneal@45 4239 var i = target;
bgneal@45 4240
bgneal@45 4241 while (i--) {
bgneal@45 4242 self.add(target[i], events, func, scope);
bgneal@45 4243 }
bgneal@45 4244
bgneal@45 4245 return;
bgneal@45 4246 }
bgneal@45 4247
bgneal@45 4248 // Old API called ready init
bgneal@45 4249 if (events === "init") {
bgneal@45 4250 events = "ready";
bgneal@45 4251 }
bgneal@45 4252
bgneal@45 4253 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
bgneal@45 4254 };
bgneal@45 4255
bgneal@45 4256 self.remove = function(target, events, func) {
bgneal@45 4257 // Old API supported direct ID assignment
bgneal@45 4258 if (typeof(target) === "string") {
bgneal@45 4259 target = document.getElementById(target);
bgneal@45 4260 }
bgneal@45 4261
bgneal@45 4262 // Old API supported multiple targets
bgneal@45 4263 if (target instanceof Array) {
bgneal@45 4264 var i = target;
bgneal@45 4265
bgneal@45 4266 while (i--) {
bgneal@45 4267 self.remove(target[i], events, func, scope);
bgneal@45 4268 }
bgneal@45 4269
bgneal@45 4270 return self;
bgneal@45 4271 }
bgneal@45 4272
bgneal@45 4273 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
bgneal@45 4274 };
bgneal@45 4275
bgneal@45 4276 self.clear = function(target) {
bgneal@45 4277 // Old API supported direct ID assignment
bgneal@45 4278 if (typeof(target) === "string") {
bgneal@45 4279 target = document.getElementById(target);
bgneal@45 4280 }
bgneal@45 4281
bgneal@45 4282 return self.clean(target);
bgneal@45 4283 };
bgneal@45 4284
bgneal@45 4285 self.cancel = function(e) {
bgneal@45 4286 if (e) {
bgneal@45 4287 self.prevent(e);
bgneal@45 4288 self.stop(e);
bgneal@45 4289 }
bgneal@45 4290
bgneal@45 4291 return false;
bgneal@45 4292 };
bgneal@45 4293
bgneal@45 4294 self.prevent = function(e) {
bgneal@45 4295 e.preventDefault();
bgneal@45 4296
bgneal@45 4297 return false;
bgneal@45 4298 };
bgneal@45 4299
bgneal@45 4300 self.stop = function(e) {
bgneal@45 4301 e.stopPropagation();
bgneal@45 4302
bgneal@45 4303 return false;
bgneal@45 4304 };
bgneal@45 4305 }
bgneal@45 4306
bgneal@45 4307 namespace.EventUtils = EventUtils;
bgneal@45 4308
bgneal@45 4309 namespace.Event = new EventUtils(function(id) {
bgneal@45 4310 return function(evt) {
bgneal@45 4311 tinymce.dom.Event.callNativeHandler(id, evt);
bgneal@45 4312 };
bgneal@45 4313 });
bgneal@45 4314
bgneal@45 4315 // Bind ready event when tinymce script is loaded
bgneal@45 4316 namespace.Event.bind(window, 'ready', function() {});
bgneal@45 4317
bgneal@45 4318 namespace = 0;
bgneal@45 4319 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
bgneal@45 4320
bgneal@45 4321 (function(tinymce) {
bgneal@45 4322 // Shorten names
bgneal@45 4323 var each = tinymce.each,
bgneal@45 4324 is = tinymce.is,
bgneal@45 4325 isWebKit = tinymce.isWebKit,
bgneal@45 4326 isIE = tinymce.isIE,
bgneal@45 4327 Entities = tinymce.html.Entities,
bgneal@45 4328 simpleSelectorRe = /^([a-z0-9],?)+$/i,
bgneal@45 4329 whiteSpaceRegExp = /^[ \t\r\n]*$/;
bgneal@45 4330
bgneal@45 4331 tinymce.create('tinymce.dom.DOMUtils', {
bgneal@45 4332 doc : null,
bgneal@45 4333 root : null,
bgneal@45 4334 files : null,
bgneal@45 4335 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
bgneal@45 4336 props : {
bgneal@45 4337 "for" : "htmlFor",
bgneal@45 4338 "class" : "className",
bgneal@45 4339 className : "className",
bgneal@45 4340 checked : "checked",
bgneal@45 4341 disabled : "disabled",
bgneal@45 4342 maxlength : "maxLength",
bgneal@45 4343 readonly : "readOnly",
bgneal@45 4344 selected : "selected",
bgneal@45 4345 value : "value",
bgneal@45 4346 id : "id",
bgneal@45 4347 name : "name",
bgneal@45 4348 type : "type"
bgneal@45 4349 },
bgneal@45 4350
bgneal@45 4351 DOMUtils : function(d, s) {
bgneal@45 4352 var t = this, globalStyle, name, blockElementsMap;
bgneal@45 4353
bgneal@45 4354 t.doc = d;
bgneal@45 4355 t.win = window;
bgneal@45 4356 t.files = {};
bgneal@45 4357 t.cssFlicker = false;
bgneal@45 4358 t.counter = 0;
bgneal@45 4359 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
bgneal@45 4360 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
bgneal@45 4361 t.hasOuterHTML = "outerHTML" in d.createElement("a");
bgneal@45 4362
bgneal@45 4363 t.settings = s = tinymce.extend({
bgneal@45 4364 keep_values : false,
bgneal@45 4365 hex_colors : 1
bgneal@45 4366 }, s);
bgneal@45 4367
bgneal@45 4368 t.schema = s.schema;
bgneal@45 4369 t.styles = new tinymce.html.Styles({
bgneal@45 4370 url_converter : s.url_converter,
bgneal@45 4371 url_converter_scope : s.url_converter_scope
bgneal@45 4372 }, s.schema);
bgneal@45 4373
bgneal@45 4374 // Fix IE6SP2 flicker and check it failed for pre SP2
bgneal@45 4375 if (tinymce.isIE6) {
bgneal@45 4376 try {
bgneal@45 4377 d.execCommand('BackgroundImageCache', false, true);
bgneal@45 4378 } catch (e) {
bgneal@45 4379 t.cssFlicker = true;
bgneal@45 4380 }
bgneal@45 4381 }
bgneal@45 4382
bgneal@45 4383 t.fixDoc(d);
bgneal@45 4384 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
bgneal@45 4385 tinymce.addUnload(t.destroy, t);
bgneal@45 4386 blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
bgneal@45 4387
bgneal@45 4388 t.isBlock = function(node) {
bgneal@45 4389 // This function is called in module pattern style since it might be executed with the wrong this scope
bgneal@45 4390 var type = node.nodeType;
bgneal@45 4391
bgneal@45 4392 // If it's a node then check the type and use the nodeName
bgneal@45 4393 if (type)
bgneal@45 4394 return !!(type === 1 && blockElementsMap[node.nodeName]);
bgneal@45 4395
bgneal@45 4396 return !!blockElementsMap[node];
bgneal@45 4397 };
bgneal@45 4398 },
bgneal@45 4399
bgneal@45 4400 fixDoc: function(doc) {
bgneal@45 4401 var settings = this.settings, name;
bgneal@45 4402
bgneal@45 4403 if (isIE && settings.schema) {
bgneal@45 4404 // Add missing HTML 4/5 elements to IE
bgneal@45 4405 ('abbr article aside audio canvas ' +
bgneal@45 4406 'details figcaption figure footer ' +
bgneal@45 4407 'header hgroup mark menu meter nav ' +
bgneal@45 4408 'output progress section summary ' +
bgneal@45 4409 'time video').replace(/\w+/g, function(name) {
bgneal@45 4410 doc.createElement(name);
bgneal@45 4411 });
bgneal@45 4412
bgneal@45 4413 // Create all custom elements
bgneal@45 4414 for (name in settings.schema.getCustomElements()) {
bgneal@45 4415 doc.createElement(name);
bgneal@45 4416 }
bgneal@45 4417 }
bgneal@45 4418 },
bgneal@45 4419
bgneal@45 4420 clone: function(node, deep) {
bgneal@45 4421 var self = this, clone, doc;
bgneal@45 4422
bgneal@45 4423 // TODO: Add feature detection here in the future
bgneal@45 4424 if (!isIE || node.nodeType !== 1 || deep) {
bgneal@45 4425 return node.cloneNode(deep);
bgneal@45 4426 }
bgneal@45 4427
bgneal@45 4428 doc = self.doc;
bgneal@45 4429
bgneal@45 4430 // Make a HTML5 safe shallow copy
bgneal@45 4431 if (!deep) {
bgneal@45 4432 clone = doc.createElement(node.nodeName);
bgneal@45 4433
bgneal@45 4434 // Copy attribs
bgneal@45 4435 each(self.getAttribs(node), function(attr) {
bgneal@45 4436 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
bgneal@45 4437 });
bgneal@45 4438
bgneal@45 4439 return clone;
bgneal@45 4440 }
bgneal@45 4441 /*
bgneal@45 4442 // Setup HTML5 patched document fragment
bgneal@45 4443 if (!self.frag) {
bgneal@45 4444 self.frag = doc.createDocumentFragment();
bgneal@45 4445 self.fixDoc(self.frag);
bgneal@45 4446 }
bgneal@45 4447
bgneal@45 4448 // Make a deep copy by adding it to the document fragment then removing it this removed the :section
bgneal@45 4449 clone = doc.createElement('div');
bgneal@45 4450 self.frag.appendChild(clone);
bgneal@45 4451 clone.innerHTML = node.outerHTML;
bgneal@45 4452 self.frag.removeChild(clone);
bgneal@45 4453 */
bgneal@45 4454 return clone.firstChild;
bgneal@45 4455 },
bgneal@45 4456
bgneal@45 4457 getRoot : function() {
bgneal@45 4458 var t = this, s = t.settings;
bgneal@45 4459
bgneal@45 4460 return (s && t.get(s.root_element)) || t.doc.body;
bgneal@45 4461 },
bgneal@45 4462
bgneal@45 4463 getViewPort : function(w) {
bgneal@45 4464 var d, b;
bgneal@45 4465
bgneal@45 4466 w = !w ? this.win : w;
bgneal@45 4467 d = w.document;
bgneal@45 4468 b = this.boxModel ? d.documentElement : d.body;
bgneal@45 4469
bgneal@45 4470 // Returns viewport size excluding scrollbars
bgneal@45 4471 return {
bgneal@45 4472 x : w.pageXOffset || b.scrollLeft,
bgneal@45 4473 y : w.pageYOffset || b.scrollTop,
bgneal@45 4474 w : w.innerWidth || b.clientWidth,
bgneal@45 4475 h : w.innerHeight || b.clientHeight
bgneal@45 4476 };
bgneal@45 4477 },
bgneal@45 4478
bgneal@45 4479 getRect : function(e) {
bgneal@45 4480 var p, t = this, sr;
bgneal@45 4481
bgneal@45 4482 e = t.get(e);
bgneal@45 4483 p = t.getPos(e);
bgneal@45 4484 sr = t.getSize(e);
bgneal@45 4485
bgneal@45 4486 return {
bgneal@45 4487 x : p.x,
bgneal@45 4488 y : p.y,
bgneal@45 4489 w : sr.w,
bgneal@45 4490 h : sr.h
bgneal@45 4491 };
bgneal@45 4492 },
bgneal@45 4493
bgneal@45 4494 getSize : function(e) {
bgneal@45 4495 var t = this, w, h;
bgneal@45 4496
bgneal@45 4497 e = t.get(e);
bgneal@45 4498 w = t.getStyle(e, 'width');
bgneal@45 4499 h = t.getStyle(e, 'height');
bgneal@45 4500
bgneal@45 4501 // Non pixel value, then force offset/clientWidth
bgneal@45 4502 if (w.indexOf('px') === -1)
bgneal@45 4503 w = 0;
bgneal@45 4504
bgneal@45 4505 // Non pixel value, then force offset/clientWidth
bgneal@45 4506 if (h.indexOf('px') === -1)
bgneal@45 4507 h = 0;
bgneal@45 4508
bgneal@45 4509 return {
bgneal@45 4510 w : parseInt(w) || e.offsetWidth || e.clientWidth,
bgneal@45 4511 h : parseInt(h) || e.offsetHeight || e.clientHeight
bgneal@45 4512 };
bgneal@45 4513 },
bgneal@45 4514
bgneal@45 4515 getParent : function(n, f, r) {
bgneal@45 4516 return this.getParents(n, f, r, false);
bgneal@45 4517 },
bgneal@45 4518
bgneal@45 4519 getParents : function(n, f, r, c) {
bgneal@45 4520 var t = this, na, se = t.settings, o = [];
bgneal@45 4521
bgneal@45 4522 n = t.get(n);
bgneal@45 4523 c = c === undefined;
bgneal@45 4524
bgneal@45 4525 if (se.strict_root)
bgneal@45 4526 r = r || t.getRoot();
bgneal@45 4527
bgneal@45 4528 // Wrap node name as func
bgneal@45 4529 if (is(f, 'string')) {
bgneal@45 4530 na = f;
bgneal@45 4531
bgneal@45 4532 if (f === '*') {
bgneal@45 4533 f = function(n) {return n.nodeType == 1;};
bgneal@45 4534 } else {
bgneal@45 4535 f = function(n) {
bgneal@45 4536 return t.is(n, na);
bgneal@45 4537 };
bgneal@45 4538 }
bgneal@45 4539 }
bgneal@45 4540
bgneal@45 4541 while (n) {
bgneal@45 4542 if (n == r || !n.nodeType || n.nodeType === 9)
bgneal@45 4543 break;
bgneal@45 4544
bgneal@45 4545 if (!f || f(n)) {
bgneal@45 4546 if (c)
bgneal@45 4547 o.push(n);
bgneal@45 4548 else
bgneal@45 4549 return n;
bgneal@45 4550 }
bgneal@45 4551
bgneal@45 4552 n = n.parentNode;
bgneal@45 4553 }
bgneal@45 4554
bgneal@45 4555 return c ? o : null;
bgneal@45 4556 },
bgneal@45 4557
bgneal@45 4558 get : function(e) {
bgneal@45 4559 var n;
bgneal@45 4560
bgneal@45 4561 if (e && this.doc && typeof(e) == 'string') {
bgneal@45 4562 n = e;
bgneal@45 4563 e = this.doc.getElementById(e);
bgneal@45 4564
bgneal@45 4565 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
bgneal@45 4566 if (e && e.id !== n)
bgneal@45 4567 return this.doc.getElementsByName(n)[1];
bgneal@45 4568 }
bgneal@45 4569
bgneal@45 4570 return e;
bgneal@45 4571 },
bgneal@45 4572
bgneal@45 4573 getNext : function(node, selector) {
bgneal@45 4574 return this._findSib(node, selector, 'nextSibling');
bgneal@45 4575 },
bgneal@45 4576
bgneal@45 4577 getPrev : function(node, selector) {
bgneal@45 4578 return this._findSib(node, selector, 'previousSibling');
bgneal@45 4579 },
bgneal@45 4580
bgneal@45 4581
bgneal@45 4582 select : function(pa, s) {
bgneal@45 4583 var t = this;
bgneal@45 4584
bgneal@45 4585 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
bgneal@45 4586 },
bgneal@45 4587
bgneal@45 4588 is : function(n, selector) {
bgneal@45 4589 var i;
bgneal@45 4590
bgneal@45 4591 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
bgneal@45 4592 if (n.length === undefined) {
bgneal@45 4593 // Simple all selector
bgneal@45 4594 if (selector === '*')
bgneal@45 4595 return n.nodeType == 1;
bgneal@45 4596
bgneal@45 4597 // Simple selector just elements
bgneal@45 4598 if (simpleSelectorRe.test(selector)) {
bgneal@45 4599 selector = selector.toLowerCase().split(/,/);
bgneal@45 4600 n = n.nodeName.toLowerCase();
bgneal@45 4601
bgneal@45 4602 for (i = selector.length - 1; i >= 0; i--) {
bgneal@45 4603 if (selector[i] == n)
bgneal@45 4604 return true;
bgneal@45 4605 }
bgneal@45 4606
bgneal@45 4607 return false;
bgneal@45 4608 }
bgneal@45 4609 }
bgneal@45 4610
bgneal@45 4611 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
bgneal@45 4612 },
bgneal@45 4613
bgneal@45 4614
bgneal@45 4615 add : function(p, n, a, h, c) {
bgneal@45 4616 var t = this;
bgneal@45 4617
bgneal@45 4618 return this.run(p, function(p) {
bgneal@45 4619 var e, k;
bgneal@45 4620
bgneal@45 4621 e = is(n, 'string') ? t.doc.createElement(n) : n;
bgneal@45 4622 t.setAttribs(e, a);
bgneal@45 4623
bgneal@45 4624 if (h) {
bgneal@45 4625 if (h.nodeType)
bgneal@45 4626 e.appendChild(h);
bgneal@45 4627 else
bgneal@45 4628 t.setHTML(e, h);
bgneal@45 4629 }
bgneal@45 4630
bgneal@45 4631 return !c ? p.appendChild(e) : e;
bgneal@45 4632 });
bgneal@45 4633 },
bgneal@45 4634
bgneal@45 4635 create : function(n, a, h) {
bgneal@45 4636 return this.add(this.doc.createElement(n), n, a, h, 1);
bgneal@45 4637 },
bgneal@45 4638
bgneal@45 4639 createHTML : function(n, a, h) {
bgneal@45 4640 var o = '', t = this, k;
bgneal@45 4641
bgneal@45 4642 o += '<' + n;
bgneal@45 4643
bgneal@45 4644 for (k in a) {
bgneal@45 4645 if (a.hasOwnProperty(k))
bgneal@45 4646 o += ' ' + k + '="' + t.encode(a[k]) + '"';
bgneal@45 4647 }
bgneal@45 4648
bgneal@45 4649 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
bgneal@45 4650 if (typeof(h) != "undefined")
bgneal@45 4651 return o + '>' + h + '</' + n + '>';
bgneal@45 4652
bgneal@45 4653 return o + ' />';
bgneal@45 4654 },
bgneal@45 4655
bgneal@45 4656 remove : function(node, keep_children) {
bgneal@45 4657 return this.run(node, function(node) {
bgneal@45 4658 var child, parent = node.parentNode;
bgneal@45 4659
bgneal@45 4660 if (!parent)
bgneal@45 4661 return null;
bgneal@45 4662
bgneal@45 4663 if (keep_children) {
bgneal@45 4664 while (child = node.firstChild) {
bgneal@45 4665 // IE 8 will crash if you don't remove completely empty text nodes
bgneal@45 4666 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
bgneal@45 4667 parent.insertBefore(child, node);
bgneal@45 4668 else
bgneal@45 4669 node.removeChild(child);
bgneal@45 4670 }
bgneal@45 4671 }
bgneal@45 4672
bgneal@45 4673 return parent.removeChild(node);
bgneal@45 4674 });
bgneal@45 4675 },
bgneal@45 4676
bgneal@45 4677 setStyle : function(n, na, v) {
bgneal@45 4678 var t = this;
bgneal@45 4679
bgneal@45 4680 return t.run(n, function(e) {
bgneal@45 4681 var s, i;
bgneal@45 4682
bgneal@45 4683 s = e.style;
bgneal@45 4684
bgneal@45 4685 // Camelcase it, if needed
bgneal@45 4686 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 4687 return b.toUpperCase();
bgneal@45 4688 });
bgneal@45 4689
bgneal@45 4690 // Default px suffix on these
bgneal@45 4691 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
bgneal@45 4692 v += 'px';
bgneal@45 4693
bgneal@45 4694 switch (na) {
bgneal@45 4695 case 'opacity':
bgneal@45 4696 // IE specific opacity
bgneal@45 4697 if (isIE) {
bgneal@45 4698 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
bgneal@45 4699
bgneal@45 4700 if (!n.currentStyle || !n.currentStyle.hasLayout)
bgneal@45 4701 s.display = 'inline-block';
bgneal@45 4702 }
bgneal@45 4703
bgneal@45 4704 // Fix for older browsers
bgneal@45 4705 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
bgneal@45 4706 break;
bgneal@45 4707
bgneal@45 4708 case 'float':
bgneal@45 4709 isIE ? s.styleFloat = v : s.cssFloat = v;
bgneal@45 4710 break;
bgneal@45 4711
bgneal@45 4712 default:
bgneal@45 4713 s[na] = v || '';
bgneal@45 4714 }
bgneal@45 4715
bgneal@45 4716 // Force update of the style data
bgneal@45 4717 if (t.settings.update_styles)
bgneal@45 4718 t.setAttrib(e, 'data-mce-style');
bgneal@45 4719 });
bgneal@45 4720 },
bgneal@45 4721
bgneal@45 4722 getStyle : function(n, na, c) {
bgneal@45 4723 n = this.get(n);
bgneal@45 4724
bgneal@45 4725 if (!n)
bgneal@45 4726 return;
bgneal@45 4727
bgneal@45 4728 // Gecko
bgneal@45 4729 if (this.doc.defaultView && c) {
bgneal@45 4730 // Remove camelcase
bgneal@45 4731 na = na.replace(/[A-Z]/g, function(a){
bgneal@45 4732 return '-' + a;
bgneal@45 4733 });
bgneal@45 4734
bgneal@45 4735 try {
bgneal@45 4736 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
bgneal@45 4737 } catch (ex) {
bgneal@45 4738 // Old safari might fail
bgneal@45 4739 return null;
bgneal@45 4740 }
bgneal@45 4741 }
bgneal@45 4742
bgneal@45 4743 // Camelcase it, if needed
bgneal@45 4744 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 4745 return b.toUpperCase();
bgneal@45 4746 });
bgneal@45 4747
bgneal@45 4748 if (na == 'float')
bgneal@45 4749 na = isIE ? 'styleFloat' : 'cssFloat';
bgneal@45 4750
bgneal@45 4751 // IE & Opera
bgneal@45 4752 if (n.currentStyle && c)
bgneal@45 4753 return n.currentStyle[na];
bgneal@45 4754
bgneal@45 4755 return n.style ? n.style[na] : undefined;
bgneal@45 4756 },
bgneal@45 4757
bgneal@45 4758 setStyles : function(e, o) {
bgneal@45 4759 var t = this, s = t.settings, ol;
bgneal@45 4760
bgneal@45 4761 ol = s.update_styles;
bgneal@45 4762 s.update_styles = 0;
bgneal@45 4763
bgneal@45 4764 each(o, function(v, n) {
bgneal@45 4765 t.setStyle(e, n, v);
bgneal@45 4766 });
bgneal@45 4767
bgneal@45 4768 // Update style info
bgneal@45 4769 s.update_styles = ol;
bgneal@45 4770 if (s.update_styles)
bgneal@45 4771 t.setAttrib(e, s.cssText);
bgneal@45 4772 },
bgneal@45 4773
bgneal@45 4774 removeAllAttribs: function(e) {
bgneal@45 4775 return this.run(e, function(e) {
bgneal@45 4776 var i, attrs = e.attributes;
bgneal@45 4777 for (i = attrs.length - 1; i >= 0; i--) {
bgneal@45 4778 e.removeAttributeNode(attrs.item(i));
bgneal@45 4779 }
bgneal@45 4780 });
bgneal@45 4781 },
bgneal@45 4782
bgneal@45 4783 setAttrib : function(e, n, v) {
bgneal@45 4784 var t = this;
bgneal@45 4785
bgneal@45 4786 // Whats the point
bgneal@45 4787 if (!e || !n)
bgneal@45 4788 return;
bgneal@45 4789
bgneal@45 4790 // Strict XML mode
bgneal@45 4791 if (t.settings.strict)
bgneal@45 4792 n = n.toLowerCase();
bgneal@45 4793
bgneal@45 4794 return this.run(e, function(e) {
bgneal@45 4795 var s = t.settings;
bgneal@45 4796 var originalValue = e.getAttribute(n);
bgneal@45 4797 if (v !== null) {
bgneal@45 4798 switch (n) {
bgneal@45 4799 case "style":
bgneal@45 4800 if (!is(v, 'string')) {
bgneal@45 4801 each(v, function(v, n) {
bgneal@45 4802 t.setStyle(e, n, v);
bgneal@45 4803 });
bgneal@45 4804
bgneal@45 4805 return;
bgneal@45 4806 }
bgneal@45 4807
bgneal@45 4808 // No mce_style for elements with these since they might get resized by the user
bgneal@45 4809 if (s.keep_values) {
bgneal@45 4810 if (v && !t._isRes(v))
bgneal@45 4811 e.setAttribute('data-mce-style', v, 2);
bgneal@45 4812 else
bgneal@45 4813 e.removeAttribute('data-mce-style', 2);
bgneal@45 4814 }
bgneal@45 4815
bgneal@45 4816 e.style.cssText = v;
bgneal@45 4817 break;
bgneal@45 4818
bgneal@45 4819 case "class":
bgneal@45 4820 e.className = v || ''; // Fix IE null bug
bgneal@45 4821 break;
bgneal@45 4822
bgneal@45 4823 case "src":
bgneal@45 4824 case "href":
bgneal@45 4825 if (s.keep_values) {
bgneal@45 4826 if (s.url_converter)
bgneal@45 4827 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
bgneal@45 4828
bgneal@45 4829 t.setAttrib(e, 'data-mce-' + n, v, 2);
bgneal@45 4830 }
bgneal@45 4831
bgneal@45 4832 break;
bgneal@45 4833
bgneal@45 4834 case "shape":
bgneal@45 4835 e.setAttribute('data-mce-style', v);
bgneal@45 4836 break;
bgneal@45 4837 }
bgneal@45 4838 }
bgneal@45 4839 if (is(v) && v !== null && v.length !== 0)
bgneal@45 4840 e.setAttribute(n, '' + v, 2);
bgneal@45 4841 else
bgneal@45 4842 e.removeAttribute(n, 2);
bgneal@45 4843
bgneal@45 4844 // fire onChangeAttrib event for attributes that have changed
bgneal@45 4845 if (tinyMCE.activeEditor && originalValue != v) {
bgneal@45 4846 var ed = tinyMCE.activeEditor;
bgneal@45 4847 ed.onSetAttrib.dispatch(ed, e, n, v);
bgneal@45 4848 }
bgneal@45 4849 });
bgneal@45 4850 },
bgneal@45 4851
bgneal@45 4852 setAttribs : function(e, o) {
bgneal@45 4853 var t = this;
bgneal@45 4854
bgneal@45 4855 return this.run(e, function(e) {
bgneal@45 4856 each(o, function(v, n) {
bgneal@45 4857 t.setAttrib(e, n, v);
bgneal@45 4858 });
bgneal@45 4859 });
bgneal@45 4860 },
bgneal@45 4861
bgneal@45 4862 getAttrib : function(e, n, dv) {
bgneal@45 4863 var v, t = this, undef;
bgneal@45 4864
bgneal@45 4865 e = t.get(e);
bgneal@45 4866
bgneal@45 4867 if (!e || e.nodeType !== 1)
bgneal@45 4868 return dv === undef ? false : dv;
bgneal@45 4869
bgneal@45 4870 if (!is(dv))
bgneal@45 4871 dv = '';
bgneal@45 4872
bgneal@45 4873 // Try the mce variant for these
bgneal@45 4874 if (/^(src|href|style|coords|shape)$/.test(n)) {
bgneal@45 4875 v = e.getAttribute("data-mce-" + n);
bgneal@45 4876
bgneal@45 4877 if (v)
bgneal@45 4878 return v;
bgneal@45 4879 }
bgneal@45 4880
bgneal@45 4881 if (isIE && t.props[n]) {
bgneal@45 4882 v = e[t.props[n]];
bgneal@45 4883 v = v && v.nodeValue ? v.nodeValue : v;
bgneal@45 4884 }
bgneal@45 4885
bgneal@45 4886 if (!v)
bgneal@45 4887 v = e.getAttribute(n, 2);
bgneal@45 4888
bgneal@45 4889 // Check boolean attribs
bgneal@45 4890 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
bgneal@45 4891 if (e[t.props[n]] === true && v === '')
bgneal@45 4892 return n;
bgneal@45 4893
bgneal@45 4894 return v ? n : '';
bgneal@45 4895 }
bgneal@45 4896
bgneal@45 4897 // Inner input elements will override attributes on form elements
bgneal@45 4898 if (e.nodeName === "FORM" && e.getAttributeNode(n))
bgneal@45 4899 return e.getAttributeNode(n).nodeValue;
bgneal@45 4900
bgneal@45 4901 if (n === 'style') {
bgneal@45 4902 v = v || e.style.cssText;
bgneal@45 4903
bgneal@45 4904 if (v) {
bgneal@45 4905 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
bgneal@45 4906
bgneal@45 4907 if (t.settings.keep_values && !t._isRes(v))
bgneal@45 4908 e.setAttribute('data-mce-style', v);
bgneal@45 4909 }
bgneal@45 4910 }
bgneal@45 4911
bgneal@45 4912 // Remove Apple and WebKit stuff
bgneal@45 4913 if (isWebKit && n === "class" && v)
bgneal@45 4914 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
bgneal@45 4915
bgneal@45 4916 // Handle IE issues
bgneal@45 4917 if (isIE) {
bgneal@45 4918 switch (n) {
bgneal@45 4919 case 'rowspan':
bgneal@45 4920 case 'colspan':
bgneal@45 4921 // IE returns 1 as default value
bgneal@45 4922 if (v === 1)
bgneal@45 4923 v = '';
bgneal@45 4924
bgneal@45 4925 break;
bgneal@45 4926
bgneal@45 4927 case 'size':
bgneal@45 4928 // IE returns +0 as default value for size
bgneal@45 4929 if (v === '+0' || v === 20 || v === 0)
bgneal@45 4930 v = '';
bgneal@45 4931
bgneal@45 4932 break;
bgneal@45 4933
bgneal@45 4934 case 'width':
bgneal@45 4935 case 'height':
bgneal@45 4936 case 'vspace':
bgneal@45 4937 case 'checked':
bgneal@45 4938 case 'disabled':
bgneal@45 4939 case 'readonly':
bgneal@45 4940 if (v === 0)
bgneal@45 4941 v = '';
bgneal@45 4942
bgneal@45 4943 break;
bgneal@45 4944
bgneal@45 4945 case 'hspace':
bgneal@45 4946 // IE returns -1 as default value
bgneal@45 4947 if (v === -1)
bgneal@45 4948 v = '';
bgneal@45 4949
bgneal@45 4950 break;
bgneal@45 4951
bgneal@45 4952 case 'maxlength':
bgneal@45 4953 case 'tabindex':
bgneal@45 4954 // IE returns default value
bgneal@45 4955 if (v === 32768 || v === 2147483647 || v === '32768')
bgneal@45 4956 v = '';
bgneal@45 4957
bgneal@45 4958 break;
bgneal@45 4959
bgneal@45 4960 case 'multiple':
bgneal@45 4961 case 'compact':
bgneal@45 4962 case 'noshade':
bgneal@45 4963 case 'nowrap':
bgneal@45 4964 if (v === 65535)
bgneal@45 4965 return n;
bgneal@45 4966
bgneal@45 4967 return dv;
bgneal@45 4968
bgneal@45 4969 case 'shape':
bgneal@45 4970 v = v.toLowerCase();
bgneal@45 4971 break;
bgneal@45 4972
bgneal@45 4973 default:
bgneal@45 4974 // IE has odd anonymous function for event attributes
bgneal@45 4975 if (n.indexOf('on') === 0 && v)
bgneal@45 4976 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
bgneal@45 4977 }
bgneal@45 4978 }
bgneal@45 4979
bgneal@45 4980 return (v !== undef && v !== null && v !== '') ? '' + v : dv;
bgneal@45 4981 },
bgneal@45 4982
bgneal@45 4983 getPos : function(n, ro) {
bgneal@45 4984 var t = this, x = 0, y = 0, e, d = t.doc, r;
bgneal@45 4985
bgneal@45 4986 n = t.get(n);
bgneal@45 4987 ro = ro || d.body;
bgneal@45 4988
bgneal@45 4989 if (n) {
bgneal@45 4990 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
bgneal@45 4991 if (n.getBoundingClientRect) {
bgneal@45 4992 n = n.getBoundingClientRect();
bgneal@45 4993 e = t.boxModel ? d.documentElement : d.body;
bgneal@45 4994
bgneal@45 4995 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
bgneal@45 4996 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
bgneal@45 4997 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
bgneal@45 4998 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
bgneal@45 4999
bgneal@45 5000 return {x : x, y : y};
bgneal@45 5001 }
bgneal@45 5002
bgneal@45 5003 r = n;
bgneal@45 5004 while (r && r != ro && r.nodeType) {
bgneal@45 5005 x += r.offsetLeft || 0;
bgneal@45 5006 y += r.offsetTop || 0;
bgneal@45 5007 r = r.offsetParent;
bgneal@45 5008 }
bgneal@45 5009
bgneal@45 5010 r = n.parentNode;
bgneal@45 5011 while (r && r != ro && r.nodeType) {
bgneal@45 5012 x -= r.scrollLeft || 0;
bgneal@45 5013 y -= r.scrollTop || 0;
bgneal@45 5014 r = r.parentNode;
bgneal@45 5015 }
bgneal@45 5016 }
bgneal@45 5017
bgneal@45 5018 return {x : x, y : y};
bgneal@45 5019 },
bgneal@45 5020
bgneal@45 5021 parseStyle : function(st) {
bgneal@45 5022 return this.styles.parse(st);
bgneal@45 5023 },
bgneal@45 5024
bgneal@45 5025 serializeStyle : function(o, name) {
bgneal@45 5026 return this.styles.serialize(o, name);
bgneal@45 5027 },
bgneal@45 5028
bgneal@45 5029 loadCSS : function(u) {
bgneal@45 5030 var t = this, d = t.doc, head;
bgneal@45 5031
bgneal@45 5032 if (!u)
bgneal@45 5033 u = '';
bgneal@45 5034
bgneal@45 5035 head = t.select('head')[0];
bgneal@45 5036
bgneal@45 5037 each(u.split(','), function(u) {
bgneal@45 5038 var link;
bgneal@45 5039
bgneal@45 5040 if (t.files[u])
bgneal@45 5041 return;
bgneal@45 5042
bgneal@45 5043 t.files[u] = true;
bgneal@45 5044 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
bgneal@45 5045
bgneal@45 5046 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
bgneal@45 5047 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
bgneal@45 5048 // It's ugly but it seems to work fine.
bgneal@45 5049 if (isIE && d.documentMode && d.recalc) {
bgneal@45 5050 link.onload = function() {
bgneal@45 5051 if (d.recalc)
bgneal@45 5052 d.recalc();
bgneal@45 5053
bgneal@45 5054 link.onload = null;
bgneal@45 5055 };
bgneal@45 5056 }
bgneal@45 5057
bgneal@45 5058 head.appendChild(link);
bgneal@45 5059 });
bgneal@45 5060 },
bgneal@45 5061
bgneal@45 5062 addClass : function(e, c) {
bgneal@45 5063 return this.run(e, function(e) {
bgneal@45 5064 var o;
bgneal@45 5065
bgneal@45 5066 if (!c)
bgneal@45 5067 return 0;
bgneal@45 5068
bgneal@45 5069 if (this.hasClass(e, c))
bgneal@45 5070 return e.className;
bgneal@45 5071
bgneal@45 5072 o = this.removeClass(e, c);
bgneal@45 5073
bgneal@45 5074 return e.className = (o != '' ? (o + ' ') : '') + c;
bgneal@45 5075 });
bgneal@45 5076 },
bgneal@45 5077
bgneal@45 5078 removeClass : function(e, c) {
bgneal@45 5079 var t = this, re;
bgneal@45 5080
bgneal@45 5081 return t.run(e, function(e) {
bgneal@45 5082 var v;
bgneal@45 5083
bgneal@45 5084 if (t.hasClass(e, c)) {
bgneal@45 5085 if (!re)
bgneal@45 5086 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
bgneal@45 5087
bgneal@45 5088 v = e.className.replace(re, ' ');
bgneal@45 5089 v = tinymce.trim(v != ' ' ? v : '');
bgneal@45 5090
bgneal@45 5091 e.className = v;
bgneal@45 5092
bgneal@45 5093 // Empty class attr
bgneal@45 5094 if (!v) {
bgneal@45 5095 e.removeAttribute('class');
bgneal@45 5096 e.removeAttribute('className');
bgneal@45 5097 }
bgneal@45 5098
bgneal@45 5099 return v;
bgneal@45 5100 }
bgneal@45 5101
bgneal@45 5102 return e.className;
bgneal@45 5103 });
bgneal@45 5104 },
bgneal@45 5105
bgneal@45 5106 hasClass : function(n, c) {
bgneal@45 5107 n = this.get(n);
bgneal@45 5108
bgneal@45 5109 if (!n || !c)
bgneal@45 5110 return false;
bgneal@45 5111
bgneal@45 5112 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
bgneal@45 5113 },
bgneal@45 5114
bgneal@45 5115 show : function(e) {
bgneal@45 5116 return this.setStyle(e, 'display', 'block');
bgneal@45 5117 },
bgneal@45 5118
bgneal@45 5119 hide : function(e) {
bgneal@45 5120 return this.setStyle(e, 'display', 'none');
bgneal@45 5121 },
bgneal@45 5122
bgneal@45 5123 isHidden : function(e) {
bgneal@45 5124 e = this.get(e);
bgneal@45 5125
bgneal@45 5126 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
bgneal@45 5127 },
bgneal@45 5128
bgneal@45 5129 uniqueId : function(p) {
bgneal@45 5130 return (!p ? 'mce_' : p) + (this.counter++);
bgneal@45 5131 },
bgneal@45 5132
bgneal@45 5133 setHTML : function(element, html) {
bgneal@45 5134 var self = this;
bgneal@45 5135
bgneal@45 5136 return self.run(element, function(element) {
bgneal@45 5137 if (isIE) {
bgneal@45 5138 // Remove all child nodes, IE keeps empty text nodes in DOM
bgneal@45 5139 while (element.firstChild)
bgneal@45 5140 element.removeChild(element.firstChild);
bgneal@45 5141
bgneal@45 5142 try {
bgneal@45 5143 // IE will remove comments from the beginning
bgneal@45 5144 // unless you padd the contents with something
bgneal@45 5145 element.innerHTML = '<br />' + html;
bgneal@45 5146 element.removeChild(element.firstChild);
bgneal@45 5147 } catch (ex) {
bgneal@45 5148 // 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
bgneal@45 5149 // This seems to fix this problem
bgneal@45 5150
bgneal@45 5151 // Create new div with HTML contents and a BR infront to keep comments
bgneal@45 5152 element = self.create('div');
bgneal@45 5153 element.innerHTML = '<br />' + html;
bgneal@45 5154
bgneal@45 5155 // Add all children from div to target
bgneal@45 5156 each (element.childNodes, function(node, i) {
bgneal@45 5157 // Skip br element
bgneal@45 5158 if (i)
bgneal@45 5159 element.appendChild(node);
bgneal@45 5160 });
bgneal@45 5161 }
bgneal@45 5162 } else
bgneal@45 5163 element.innerHTML = html;
bgneal@45 5164
bgneal@45 5165 return html;
bgneal@45 5166 });
bgneal@45 5167 },
bgneal@45 5168
bgneal@45 5169 getOuterHTML : function(elm) {
bgneal@45 5170 var doc, self = this;
bgneal@45 5171
bgneal@45 5172 elm = self.get(elm);
bgneal@45 5173
bgneal@45 5174 if (!elm)
bgneal@45 5175 return null;
bgneal@45 5176
bgneal@45 5177 if (elm.nodeType === 1 && self.hasOuterHTML)
bgneal@45 5178 return elm.outerHTML;
bgneal@45 5179
bgneal@45 5180 doc = (elm.ownerDocument || self.doc).createElement("body");
bgneal@45 5181 doc.appendChild(elm.cloneNode(true));
bgneal@45 5182
bgneal@45 5183 return doc.innerHTML;
bgneal@45 5184 },
bgneal@45 5185
bgneal@45 5186 setOuterHTML : function(e, h, d) {
bgneal@45 5187 var t = this;
bgneal@45 5188
bgneal@45 5189 function setHTML(e, h, d) {
bgneal@45 5190 var n, tp;
bgneal@45 5191
bgneal@45 5192 tp = d.createElement("body");
bgneal@45 5193 tp.innerHTML = h;
bgneal@45 5194
bgneal@45 5195 n = tp.lastChild;
bgneal@45 5196 while (n) {
bgneal@45 5197 t.insertAfter(n.cloneNode(true), e);
bgneal@45 5198 n = n.previousSibling;
bgneal@45 5199 }
bgneal@45 5200
bgneal@45 5201 t.remove(e);
bgneal@45 5202 };
bgneal@45 5203
bgneal@45 5204 return this.run(e, function(e) {
bgneal@45 5205 e = t.get(e);
bgneal@45 5206
bgneal@45 5207 // Only set HTML on elements
bgneal@45 5208 if (e.nodeType == 1) {
bgneal@45 5209 d = d || e.ownerDocument || t.doc;
bgneal@45 5210
bgneal@45 5211 if (isIE) {
bgneal@45 5212 try {
bgneal@45 5213 // Try outerHTML for IE it sometimes produces an unknown runtime error
bgneal@45 5214 if (isIE && e.nodeType == 1)
bgneal@45 5215 e.outerHTML = h;
bgneal@45 5216 else
bgneal@45 5217 setHTML(e, h, d);
bgneal@45 5218 } catch (ex) {
bgneal@45 5219 // Fix for unknown runtime error
bgneal@45 5220 setHTML(e, h, d);
bgneal@45 5221 }
bgneal@45 5222 } else
bgneal@45 5223 setHTML(e, h, d);
bgneal@45 5224 }
bgneal@45 5225 });
bgneal@45 5226 },
bgneal@45 5227
bgneal@45 5228 decode : Entities.decode,
bgneal@45 5229
bgneal@45 5230 encode : Entities.encodeAllRaw,
bgneal@45 5231
bgneal@45 5232 insertAfter : function(node, reference_node) {
bgneal@45 5233 reference_node = this.get(reference_node);
bgneal@45 5234
bgneal@45 5235 return this.run(node, function(node) {
bgneal@45 5236 var parent, nextSibling;
bgneal@45 5237
bgneal@45 5238 parent = reference_node.parentNode;
bgneal@45 5239 nextSibling = reference_node.nextSibling;
bgneal@45 5240
bgneal@45 5241 if (nextSibling)
bgneal@45 5242 parent.insertBefore(node, nextSibling);
bgneal@45 5243 else
bgneal@45 5244 parent.appendChild(node);
bgneal@45 5245
bgneal@45 5246 return node;
bgneal@45 5247 });
bgneal@45 5248 },
bgneal@45 5249
bgneal@45 5250 replace : function(n, o, k) {
bgneal@45 5251 var t = this;
bgneal@45 5252
bgneal@45 5253 if (is(o, 'array'))
bgneal@45 5254 n = n.cloneNode(true);
bgneal@45 5255
bgneal@45 5256 return t.run(o, function(o) {
bgneal@45 5257 if (k) {
bgneal@45 5258 each(tinymce.grep(o.childNodes), function(c) {
bgneal@45 5259 n.appendChild(c);
bgneal@45 5260 });
bgneal@45 5261 }
bgneal@45 5262
bgneal@45 5263 return o.parentNode.replaceChild(n, o);
bgneal@45 5264 });
bgneal@45 5265 },
bgneal@45 5266
bgneal@45 5267 rename : function(elm, name) {
bgneal@45 5268 var t = this, newElm;
bgneal@45 5269
bgneal@45 5270 if (elm.nodeName != name.toUpperCase()) {
bgneal@45 5271 // Rename block element
bgneal@45 5272 newElm = t.create(name);
bgneal@45 5273
bgneal@45 5274 // Copy attribs to new block
bgneal@45 5275 each(t.getAttribs(elm), function(attr_node) {
bgneal@45 5276 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
bgneal@45 5277 });
bgneal@45 5278
bgneal@45 5279 // Replace block
bgneal@45 5280 t.replace(newElm, elm, 1);
bgneal@45 5281 }
bgneal@45 5282
bgneal@45 5283 return newElm || elm;
bgneal@45 5284 },
bgneal@45 5285
bgneal@45 5286 findCommonAncestor : function(a, b) {
bgneal@45 5287 var ps = a, pe;
bgneal@45 5288
bgneal@45 5289 while (ps) {
bgneal@45 5290 pe = b;
bgneal@45 5291
bgneal@45 5292 while (pe && ps != pe)
bgneal@45 5293 pe = pe.parentNode;
bgneal@45 5294
bgneal@45 5295 if (ps == pe)
bgneal@45 5296 break;
bgneal@45 5297
bgneal@45 5298 ps = ps.parentNode;
bgneal@45 5299 }
bgneal@45 5300
bgneal@45 5301 if (!ps && a.ownerDocument)
bgneal@45 5302 return a.ownerDocument.documentElement;
bgneal@45 5303
bgneal@45 5304 return ps;
bgneal@45 5305 },
bgneal@45 5306
bgneal@45 5307 toHex : function(s) {
bgneal@45 5308 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
bgneal@45 5309
bgneal@45 5310 function hex(s) {
bgneal@45 5311 s = parseInt(s).toString(16);
bgneal@45 5312
bgneal@45 5313 return s.length > 1 ? s : '0' + s; // 0 -> 00
bgneal@45 5314 };
bgneal@45 5315
bgneal@45 5316 if (c) {
bgneal@45 5317 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
bgneal@45 5318
bgneal@45 5319 return s;
bgneal@45 5320 }
bgneal@45 5321
bgneal@45 5322 return s;
bgneal@45 5323 },
bgneal@45 5324
bgneal@45 5325 getClasses : function() {
bgneal@45 5326 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
bgneal@45 5327
bgneal@45 5328 if (t.classes)
bgneal@45 5329 return t.classes;
bgneal@45 5330
bgneal@45 5331 function addClasses(s) {
bgneal@45 5332 // IE style imports
bgneal@45 5333 each(s.imports, function(r) {
bgneal@45 5334 addClasses(r);
bgneal@45 5335 });
bgneal@45 5336
bgneal@45 5337 each(s.cssRules || s.rules, function(r) {
bgneal@45 5338 // Real type or fake it on IE
bgneal@45 5339 switch (r.type || 1) {
bgneal@45 5340 // Rule
bgneal@45 5341 case 1:
bgneal@45 5342 if (r.selectorText) {
bgneal@45 5343 each(r.selectorText.split(','), function(v) {
bgneal@45 5344 v = v.replace(/^\s*|\s*$|^\s\./g, "");
bgneal@45 5345
bgneal@45 5346 // Is internal or it doesn't contain a class
bgneal@45 5347 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
bgneal@45 5348 return;
bgneal@45 5349
bgneal@45 5350 // Remove everything but class name
bgneal@45 5351 ov = v;
bgneal@45 5352 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
bgneal@45 5353
bgneal@45 5354 // Filter classes
bgneal@45 5355 if (f && !(v = f(v, ov)))
bgneal@45 5356 return;
bgneal@45 5357
bgneal@45 5358 if (!lo[v]) {
bgneal@45 5359 cl.push({'class' : v});
bgneal@45 5360 lo[v] = 1;
bgneal@45 5361 }
bgneal@45 5362 });
bgneal@45 5363 }
bgneal@45 5364 break;
bgneal@45 5365
bgneal@45 5366 // Import
bgneal@45 5367 case 3:
bgneal@45 5368 addClasses(r.styleSheet);
bgneal@45 5369 break;
bgneal@45 5370 }
bgneal@45 5371 });
bgneal@45 5372 };
bgneal@45 5373
bgneal@45 5374 try {
bgneal@45 5375 each(t.doc.styleSheets, addClasses);
bgneal@45 5376 } catch (ex) {
bgneal@45 5377 // Ignore
bgneal@45 5378 }
bgneal@45 5379
bgneal@45 5380 if (cl.length > 0)
bgneal@45 5381 t.classes = cl;
bgneal@45 5382
bgneal@45 5383 return cl;
bgneal@45 5384 },
bgneal@45 5385
bgneal@45 5386 run : function(e, f, s) {
bgneal@45 5387 var t = this, o;
bgneal@45 5388
bgneal@45 5389 if (t.doc && typeof(e) === 'string')
bgneal@45 5390 e = t.get(e);
bgneal@45 5391
bgneal@45 5392 if (!e)
bgneal@45 5393 return false;
bgneal@45 5394
bgneal@45 5395 s = s || this;
bgneal@45 5396 if (!e.nodeType && (e.length || e.length === 0)) {
bgneal@45 5397 o = [];
bgneal@45 5398
bgneal@45 5399 each(e, function(e, i) {
bgneal@45 5400 if (e) {
bgneal@45 5401 if (typeof(e) == 'string')
bgneal@45 5402 e = t.doc.getElementById(e);
bgneal@45 5403
bgneal@45 5404 o.push(f.call(s, e, i));
bgneal@45 5405 }
bgneal@45 5406 });
bgneal@45 5407
bgneal@45 5408 return o;
bgneal@45 5409 }
bgneal@45 5410
bgneal@45 5411 return f.call(s, e);
bgneal@45 5412 },
bgneal@45 5413
bgneal@45 5414 getAttribs : function(n) {
bgneal@45 5415 var o;
bgneal@45 5416
bgneal@45 5417 n = this.get(n);
bgneal@45 5418
bgneal@45 5419 if (!n)
bgneal@45 5420 return [];
bgneal@45 5421
bgneal@45 5422 if (isIE) {
bgneal@45 5423 o = [];
bgneal@45 5424
bgneal@45 5425 // Object will throw exception in IE
bgneal@45 5426 if (n.nodeName == 'OBJECT')
bgneal@45 5427 return n.attributes;
bgneal@45 5428
bgneal@45 5429 // IE doesn't keep the selected attribute if you clone option elements
bgneal@45 5430 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
bgneal@45 5431 o.push({specified : 1, nodeName : 'selected'});
bgneal@45 5432
bgneal@45 5433 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
bgneal@45 5434 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
bgneal@45 5435 o.push({specified : 1, nodeName : a});
bgneal@45 5436 });
bgneal@45 5437
bgneal@45 5438 return o;
bgneal@45 5439 }
bgneal@45 5440
bgneal@45 5441 return n.attributes;
bgneal@45 5442 },
bgneal@45 5443
bgneal@45 5444 isEmpty : function(node, elements) {
bgneal@45 5445 var self = this, i, attributes, type, walker, name, parentNode;
bgneal@45 5446
bgneal@45 5447 node = node.firstChild;
bgneal@45 5448 if (node) {
bgneal@45 5449 walker = new tinymce.dom.TreeWalker(node, node.parentNode);
bgneal@45 5450 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
bgneal@45 5451
bgneal@45 5452 do {
bgneal@45 5453 type = node.nodeType;
bgneal@45 5454
bgneal@45 5455 if (type === 1) {
bgneal@45 5456 // Ignore bogus elements
bgneal@45 5457 if (node.getAttribute('data-mce-bogus'))
bgneal@45 5458 continue;
bgneal@45 5459
bgneal@45 5460 // Keep empty elements like <img />
bgneal@45 5461 name = node.nodeName.toLowerCase();
bgneal@45 5462 if (elements && elements[name]) {
bgneal@45 5463 // Ignore single BR elements in blocks like <p><br /></p>
bgneal@45 5464 parentNode = node.parentNode;
bgneal@45 5465 if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {
bgneal@45 5466 continue;
bgneal@45 5467 }
bgneal@45 5468
bgneal@45 5469 return false;
bgneal@45 5470 }
bgneal@45 5471
bgneal@45 5472 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
bgneal@45 5473 attributes = self.getAttribs(node);
bgneal@45 5474 i = node.attributes.length;
bgneal@45 5475 while (i--) {
bgneal@45 5476 name = node.attributes[i].nodeName;
bgneal@45 5477 if (name === "name" || name === 'data-mce-bookmark')
bgneal@45 5478 return false;
bgneal@45 5479 }
bgneal@45 5480 }
bgneal@45 5481
bgneal@45 5482 // Keep comment nodes
bgneal@45 5483 if (type == 8)
bgneal@45 5484 return false;
bgneal@45 5485
bgneal@45 5486 // Keep non whitespace text nodes
bgneal@45 5487 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
bgneal@45 5488 return false;
bgneal@45 5489 } while (node = walker.next());
bgneal@45 5490 }
bgneal@45 5491
bgneal@45 5492 return true;
bgneal@45 5493 },
bgneal@45 5494
bgneal@45 5495 destroy : function(s) {
bgneal@45 5496 var t = this;
bgneal@45 5497
bgneal@45 5498 t.win = t.doc = t.root = t.events = t.frag = null;
bgneal@45 5499
bgneal@45 5500 // Manual destroy then remove unload handler
bgneal@45 5501 if (!s)
bgneal@45 5502 tinymce.removeUnload(t.destroy);
bgneal@45 5503 },
bgneal@45 5504
bgneal@45 5505 createRng : function() {
bgneal@45 5506 var d = this.doc;
bgneal@45 5507
bgneal@45 5508 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
bgneal@45 5509 },
bgneal@45 5510
bgneal@45 5511 nodeIndex : function(node, normalized) {
bgneal@45 5512 var idx = 0, lastNodeType, lastNode, nodeType;
bgneal@45 5513
bgneal@45 5514 if (node) {
bgneal@45 5515 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
bgneal@45 5516 nodeType = node.nodeType;
bgneal@45 5517
bgneal@45 5518 // Normalize text nodes
bgneal@45 5519 if (normalized && nodeType == 3) {
bgneal@45 5520 if (nodeType == lastNodeType || !node.nodeValue.length)
bgneal@45 5521 continue;
bgneal@45 5522 }
bgneal@45 5523 idx++;
bgneal@45 5524 lastNodeType = nodeType;
bgneal@45 5525 }
bgneal@45 5526 }
bgneal@45 5527
bgneal@45 5528 return idx;
bgneal@45 5529 },
bgneal@45 5530
bgneal@45 5531 split : function(pe, e, re) {
bgneal@45 5532 var t = this, r = t.createRng(), bef, aft, pa;
bgneal@45 5533
bgneal@45 5534 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
bgneal@45 5535 // but we don't want that in our code since it serves no purpose for the end user
bgneal@45 5536 // For example if this is chopped:
bgneal@45 5537 // <p>text 1<span><b>CHOP</b></span>text 2</p>
bgneal@45 5538 // would produce:
bgneal@45 5539 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
bgneal@45 5540 // this function will then trim of empty edges and produce:
bgneal@45 5541 // <p>text 1</p><b>CHOP</b><p>text 2</p>
bgneal@45 5542 function trim(node) {
bgneal@45 5543 var i, children = node.childNodes, type = node.nodeType;
bgneal@45 5544
bgneal@45 5545 function surroundedBySpans(node) {
bgneal@45 5546 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
bgneal@45 5547 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
bgneal@45 5548 return previousIsSpan && nextIsSpan;
bgneal@45 5549 }
bgneal@45 5550
bgneal@45 5551 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
bgneal@45 5552 return;
bgneal@45 5553
bgneal@45 5554 for (i = children.length - 1; i >= 0; i--)
bgneal@45 5555 trim(children[i]);
bgneal@45 5556
bgneal@45 5557 if (type != 9) {
bgneal@45 5558 // Keep non whitespace text nodes
bgneal@45 5559 if (type == 3 && node.nodeValue.length > 0) {
bgneal@45 5560 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
bgneal@45 5561 // Also keep text nodes with only spaces if surrounded by spans.
bgneal@45 5562 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
bgneal@45 5563 var trimmedLength = tinymce.trim(node.nodeValue).length;
bgneal@45 5564 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node))
bgneal@45 5565 return;
bgneal@45 5566 } else if (type == 1) {
bgneal@45 5567 // If the only child is a bookmark then move it up
bgneal@45 5568 children = node.childNodes;
bgneal@45 5569 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
bgneal@45 5570 node.parentNode.insertBefore(children[0], node);
bgneal@45 5571
bgneal@45 5572 // Keep non empty elements or img, hr etc
bgneal@45 5573 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
bgneal@45 5574 return;
bgneal@45 5575 }
bgneal@45 5576
bgneal@45 5577 t.remove(node);
bgneal@45 5578 }
bgneal@45 5579
bgneal@45 5580 return node;
bgneal@45 5581 };
bgneal@45 5582
bgneal@45 5583 if (pe && e) {
bgneal@45 5584 // Get before chunk
bgneal@45 5585 r.setStart(pe.parentNode, t.nodeIndex(pe));
bgneal@45 5586 r.setEnd(e.parentNode, t.nodeIndex(e));
bgneal@45 5587 bef = r.extractContents();
bgneal@45 5588
bgneal@45 5589 // Get after chunk
bgneal@45 5590 r = t.createRng();
bgneal@45 5591 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
bgneal@45 5592 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
bgneal@45 5593 aft = r.extractContents();
bgneal@45 5594
bgneal@45 5595 // Insert before chunk
bgneal@45 5596 pa = pe.parentNode;
bgneal@45 5597 pa.insertBefore(trim(bef), pe);
bgneal@45 5598
bgneal@45 5599 // Insert middle chunk
bgneal@45 5600 if (re)
bgneal@45 5601 pa.replaceChild(re, e);
bgneal@45 5602 else
bgneal@45 5603 pa.insertBefore(e, pe);
bgneal@45 5604
bgneal@45 5605 // Insert after chunk
bgneal@45 5606 pa.insertBefore(trim(aft), pe);
bgneal@45 5607 t.remove(pe);
bgneal@45 5608
bgneal@45 5609 return re || e;
bgneal@45 5610 }
bgneal@45 5611 },
bgneal@45 5612
bgneal@45 5613 bind : function(target, name, func, scope) {
bgneal@45 5614 return this.events.add(target, name, func, scope || this);
bgneal@45 5615 },
bgneal@45 5616
bgneal@45 5617 unbind : function(target, name, func) {
bgneal@45 5618 return this.events.remove(target, name, func);
bgneal@45 5619 },
bgneal@45 5620
bgneal@45 5621 fire : function(target, name, evt) {
bgneal@45 5622 return this.events.fire(target, name, evt);
bgneal@45 5623 },
bgneal@45 5624
bgneal@45 5625
bgneal@45 5626 _findSib : function(node, selector, name) {
bgneal@45 5627 var t = this, f = selector;
bgneal@45 5628
bgneal@45 5629 if (node) {
bgneal@45 5630 // If expression make a function of it using is
bgneal@45 5631 if (is(f, 'string')) {
bgneal@45 5632 f = function(node) {
bgneal@45 5633 return t.is(node, selector);
bgneal@45 5634 };
bgneal@45 5635 }
bgneal@45 5636
bgneal@45 5637 // Loop all siblings
bgneal@45 5638 for (node = node[name]; node; node = node[name]) {
bgneal@45 5639 if (f(node))
bgneal@45 5640 return node;
bgneal@45 5641 }
bgneal@45 5642 }
bgneal@45 5643
bgneal@45 5644 return null;
bgneal@45 5645 },
bgneal@45 5646
bgneal@45 5647 _isRes : function(c) {
bgneal@45 5648 // Is live resizble element
bgneal@45 5649 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
bgneal@45 5650 }
bgneal@45 5651
bgneal@45 5652 /*
bgneal@45 5653 walk : function(n, f, s) {
bgneal@45 5654 var d = this.doc, w;
bgneal@45 5655
bgneal@45 5656 if (d.createTreeWalker) {
bgneal@45 5657 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
bgneal@45 5658
bgneal@45 5659 while ((n = w.nextNode()) != null)
bgneal@45 5660 f.call(s || this, n);
bgneal@45 5661 } else
bgneal@45 5662 tinymce.walk(n, f, 'childNodes', s);
bgneal@45 5663 }
bgneal@45 5664 */
bgneal@45 5665
bgneal@45 5666 /*
bgneal@45 5667 toRGB : function(s) {
bgneal@45 5668 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
bgneal@45 5669
bgneal@45 5670 if (c) {
bgneal@45 5671 // #FFF -> #FFFFFF
bgneal@45 5672 if (!is(c[3]))
bgneal@45 5673 c[3] = c[2] = c[1];
bgneal@45 5674
bgneal@45 5675 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
bgneal@45 5676 }
bgneal@45 5677
bgneal@45 5678 return s;
bgneal@45 5679 }
bgneal@45 5680 */
bgneal@45 5681 });
bgneal@45 5682
bgneal@45 5683 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
bgneal@45 5684 })(tinymce);
bgneal@45 5685
bgneal@45 5686 (function(ns) {
bgneal@45 5687 // Range constructor
bgneal@45 5688 function Range(dom) {
bgneal@45 5689 var t = this,
bgneal@45 5690 doc = dom.doc,
bgneal@45 5691 EXTRACT = 0,
bgneal@45 5692 CLONE = 1,
bgneal@45 5693 DELETE = 2,
bgneal@45 5694 TRUE = true,
bgneal@45 5695 FALSE = false,
bgneal@45 5696 START_OFFSET = 'startOffset',
bgneal@45 5697 START_CONTAINER = 'startContainer',
bgneal@45 5698 END_CONTAINER = 'endContainer',
bgneal@45 5699 END_OFFSET = 'endOffset',
bgneal@45 5700 extend = tinymce.extend,
bgneal@45 5701 nodeIndex = dom.nodeIndex;
bgneal@45 5702
bgneal@45 5703 extend(t, {
bgneal@45 5704 // Inital states
bgneal@45 5705 startContainer : doc,
bgneal@45 5706 startOffset : 0,
bgneal@45 5707 endContainer : doc,
bgneal@45 5708 endOffset : 0,
bgneal@45 5709 collapsed : TRUE,
bgneal@45 5710 commonAncestorContainer : doc,
bgneal@45 5711
bgneal@45 5712 // Range constants
bgneal@45 5713 START_TO_START : 0,
bgneal@45 5714 START_TO_END : 1,
bgneal@45 5715 END_TO_END : 2,
bgneal@45 5716 END_TO_START : 3,
bgneal@45 5717
bgneal@45 5718 // Public methods
bgneal@45 5719 setStart : setStart,
bgneal@45 5720 setEnd : setEnd,
bgneal@45 5721 setStartBefore : setStartBefore,
bgneal@45 5722 setStartAfter : setStartAfter,
bgneal@45 5723 setEndBefore : setEndBefore,
bgneal@45 5724 setEndAfter : setEndAfter,
bgneal@45 5725 collapse : collapse,
bgneal@45 5726 selectNode : selectNode,
bgneal@45 5727 selectNodeContents : selectNodeContents,
bgneal@45 5728 compareBoundaryPoints : compareBoundaryPoints,
bgneal@45 5729 deleteContents : deleteContents,
bgneal@45 5730 extractContents : extractContents,
bgneal@45 5731 cloneContents : cloneContents,
bgneal@45 5732 insertNode : insertNode,
bgneal@45 5733 surroundContents : surroundContents,
bgneal@45 5734 cloneRange : cloneRange
bgneal@45 5735 });
bgneal@45 5736
bgneal@45 5737 function createDocumentFragment() {
bgneal@45 5738 return doc.createDocumentFragment();
bgneal@45 5739 };
bgneal@45 5740
bgneal@45 5741 function setStart(n, o) {
bgneal@45 5742 _setEndPoint(TRUE, n, o);
bgneal@45 5743 };
bgneal@45 5744
bgneal@45 5745 function setEnd(n, o) {
bgneal@45 5746 _setEndPoint(FALSE, n, o);
bgneal@45 5747 };
bgneal@45 5748
bgneal@45 5749 function setStartBefore(n) {
bgneal@45 5750 setStart(n.parentNode, nodeIndex(n));
bgneal@45 5751 };
bgneal@45 5752
bgneal@45 5753 function setStartAfter(n) {
bgneal@45 5754 setStart(n.parentNode, nodeIndex(n) + 1);
bgneal@45 5755 };
bgneal@45 5756
bgneal@45 5757 function setEndBefore(n) {
bgneal@45 5758 setEnd(n.parentNode, nodeIndex(n));
bgneal@45 5759 };
bgneal@45 5760
bgneal@45 5761 function setEndAfter(n) {
bgneal@45 5762 setEnd(n.parentNode, nodeIndex(n) + 1);
bgneal@45 5763 };
bgneal@45 5764
bgneal@45 5765 function collapse(ts) {
bgneal@45 5766 if (ts) {
bgneal@45 5767 t[END_CONTAINER] = t[START_CONTAINER];
bgneal@45 5768 t[END_OFFSET] = t[START_OFFSET];
bgneal@45 5769 } else {
bgneal@45 5770 t[START_CONTAINER] = t[END_CONTAINER];
bgneal@45 5771 t[START_OFFSET] = t[END_OFFSET];
bgneal@45 5772 }
bgneal@45 5773
bgneal@45 5774 t.collapsed = TRUE;
bgneal@45 5775 };
bgneal@45 5776
bgneal@45 5777 function selectNode(n) {
bgneal@45 5778 setStartBefore(n);
bgneal@45 5779 setEndAfter(n);
bgneal@45 5780 };
bgneal@45 5781
bgneal@45 5782 function selectNodeContents(n) {
bgneal@45 5783 setStart(n, 0);
bgneal@45 5784 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
bgneal@45 5785 };
bgneal@45 5786
bgneal@45 5787 function compareBoundaryPoints(h, r) {
bgneal@45 5788 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
bgneal@45 5789 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
bgneal@45 5790
bgneal@45 5791 // Check START_TO_START
bgneal@45 5792 if (h === 0)
bgneal@45 5793 return _compareBoundaryPoints(sc, so, rsc, rso);
bgneal@45 5794
bgneal@45 5795 // Check START_TO_END
bgneal@45 5796 if (h === 1)
bgneal@45 5797 return _compareBoundaryPoints(ec, eo, rsc, rso);
bgneal@45 5798
bgneal@45 5799 // Check END_TO_END
bgneal@45 5800 if (h === 2)
bgneal@45 5801 return _compareBoundaryPoints(ec, eo, rec, reo);
bgneal@45 5802
bgneal@45 5803 // Check END_TO_START
bgneal@45 5804 if (h === 3)
bgneal@45 5805 return _compareBoundaryPoints(sc, so, rec, reo);
bgneal@45 5806 };
bgneal@45 5807
bgneal@45 5808 function deleteContents() {
bgneal@45 5809 _traverse(DELETE);
bgneal@45 5810 };
bgneal@45 5811
bgneal@45 5812 function extractContents() {
bgneal@45 5813 return _traverse(EXTRACT);
bgneal@45 5814 };
bgneal@45 5815
bgneal@45 5816 function cloneContents() {
bgneal@45 5817 return _traverse(CLONE);
bgneal@45 5818 };
bgneal@45 5819
bgneal@45 5820 function insertNode(n) {
bgneal@45 5821 var startContainer = this[START_CONTAINER],
bgneal@45 5822 startOffset = this[START_OFFSET], nn, o;
bgneal@45 5823
bgneal@45 5824 // Node is TEXT_NODE or CDATA
bgneal@45 5825 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
bgneal@45 5826 if (!startOffset) {
bgneal@45 5827 // At the start of text
bgneal@45 5828 startContainer.parentNode.insertBefore(n, startContainer);
bgneal@45 5829 } else if (startOffset >= startContainer.nodeValue.length) {
bgneal@45 5830 // At the end of text
bgneal@45 5831 dom.insertAfter(n, startContainer);
bgneal@45 5832 } else {
bgneal@45 5833 // Middle, need to split
bgneal@45 5834 nn = startContainer.splitText(startOffset);
bgneal@45 5835 startContainer.parentNode.insertBefore(n, nn);
bgneal@45 5836 }
bgneal@45 5837 } else {
bgneal@45 5838 // Insert element node
bgneal@45 5839 if (startContainer.childNodes.length > 0)
bgneal@45 5840 o = startContainer.childNodes[startOffset];
bgneal@45 5841
bgneal@45 5842 if (o)
bgneal@45 5843 startContainer.insertBefore(n, o);
bgneal@45 5844 else
bgneal@45 5845 startContainer.appendChild(n);
bgneal@45 5846 }
bgneal@45 5847 };
bgneal@45 5848
bgneal@45 5849 function surroundContents(n) {
bgneal@45 5850 var f = t.extractContents();
bgneal@45 5851
bgneal@45 5852 t.insertNode(n);
bgneal@45 5853 n.appendChild(f);
bgneal@45 5854 t.selectNode(n);
bgneal@45 5855 };
bgneal@45 5856
bgneal@45 5857 function cloneRange() {
bgneal@45 5858 return extend(new Range(dom), {
bgneal@45 5859 startContainer : t[START_CONTAINER],
bgneal@45 5860 startOffset : t[START_OFFSET],
bgneal@45 5861 endContainer : t[END_CONTAINER],
bgneal@45 5862 endOffset : t[END_OFFSET],
bgneal@45 5863 collapsed : t.collapsed,
bgneal@45 5864 commonAncestorContainer : t.commonAncestorContainer
bgneal@45 5865 });
bgneal@45 5866 };
bgneal@45 5867
bgneal@45 5868 // Private methods
bgneal@45 5869
bgneal@45 5870 function _getSelectedNode(container, offset) {
bgneal@45 5871 var child;
bgneal@45 5872
bgneal@45 5873 if (container.nodeType == 3 /* TEXT_NODE */)
bgneal@45 5874 return container;
bgneal@45 5875
bgneal@45 5876 if (offset < 0)
bgneal@45 5877 return container;
bgneal@45 5878
bgneal@45 5879 child = container.firstChild;
bgneal@45 5880 while (child && offset > 0) {
bgneal@45 5881 --offset;
bgneal@45 5882 child = child.nextSibling;
bgneal@45 5883 }
bgneal@45 5884
bgneal@45 5885 if (child)
bgneal@45 5886 return child;
bgneal@45 5887
bgneal@45 5888 return container;
bgneal@45 5889 };
bgneal@45 5890
bgneal@45 5891 function _isCollapsed() {
bgneal@45 5892 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
bgneal@45 5893 };
bgneal@45 5894
bgneal@45 5895 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
bgneal@45 5896 var c, offsetC, n, cmnRoot, childA, childB;
bgneal@45 5897
bgneal@45 5898 // In the first case the boundary-points have the same container. A is before B
bgneal@45 5899 // if its offset is less than the offset of B, A is equal to B if its offset is
bgneal@45 5900 // equal to the offset of B, and A is after B if its offset is greater than the
bgneal@45 5901 // offset of B.
bgneal@45 5902 if (containerA == containerB) {
bgneal@45 5903 if (offsetA == offsetB)
bgneal@45 5904 return 0; // equal
bgneal@45 5905
bgneal@45 5906 if (offsetA < offsetB)
bgneal@45 5907 return -1; // before
bgneal@45 5908
bgneal@45 5909 return 1; // after
bgneal@45 5910 }
bgneal@45 5911
bgneal@45 5912 // In the second case a child node C of the container of A is an ancestor
bgneal@45 5913 // container of B. In this case, A is before B if the offset of A is less than or
bgneal@45 5914 // equal to the index of the child node C and A is after B otherwise.
bgneal@45 5915 c = containerB;
bgneal@45 5916 while (c && c.parentNode != containerA)
bgneal@45 5917 c = c.parentNode;
bgneal@45 5918
bgneal@45 5919 if (c) {
bgneal@45 5920 offsetC = 0;
bgneal@45 5921 n = containerA.firstChild;
bgneal@45 5922
bgneal@45 5923 while (n != c && offsetC < offsetA) {
bgneal@45 5924 offsetC++;
bgneal@45 5925 n = n.nextSibling;
bgneal@45 5926 }
bgneal@45 5927
bgneal@45 5928 if (offsetA <= offsetC)
bgneal@45 5929 return -1; // before
bgneal@45 5930
bgneal@45 5931 return 1; // after
bgneal@45 5932 }
bgneal@45 5933
bgneal@45 5934 // In the third case a child node C of the container of B is an ancestor container
bgneal@45 5935 // of A. In this case, A is before B if the index of the child node C is less than
bgneal@45 5936 // the offset of B and A is after B otherwise.
bgneal@45 5937 c = containerA;
bgneal@45 5938 while (c && c.parentNode != containerB) {
bgneal@45 5939 c = c.parentNode;
bgneal@45 5940 }
bgneal@45 5941
bgneal@45 5942 if (c) {
bgneal@45 5943 offsetC = 0;
bgneal@45 5944 n = containerB.firstChild;
bgneal@45 5945
bgneal@45 5946 while (n != c && offsetC < offsetB) {
bgneal@45 5947 offsetC++;
bgneal@45 5948 n = n.nextSibling;
bgneal@45 5949 }
bgneal@45 5950
bgneal@45 5951 if (offsetC < offsetB)
bgneal@45 5952 return -1; // before
bgneal@45 5953
bgneal@45 5954 return 1; // after
bgneal@45 5955 }
bgneal@45 5956
bgneal@45 5957 // In the fourth case, none of three other cases hold: the containers of A and B
bgneal@45 5958 // are siblings or descendants of sibling nodes. In this case, A is before B if
bgneal@45 5959 // the container of A is before the container of B in a pre-order traversal of the
bgneal@45 5960 // Ranges' context tree and A is after B otherwise.
bgneal@45 5961 cmnRoot = dom.findCommonAncestor(containerA, containerB);
bgneal@45 5962 childA = containerA;
bgneal@45 5963
bgneal@45 5964 while (childA && childA.parentNode != cmnRoot)
bgneal@45 5965 childA = childA.parentNode;
bgneal@45 5966
bgneal@45 5967 if (!childA)
bgneal@45 5968 childA = cmnRoot;
bgneal@45 5969
bgneal@45 5970 childB = containerB;
bgneal@45 5971 while (childB && childB.parentNode != cmnRoot)
bgneal@45 5972 childB = childB.parentNode;
bgneal@45 5973
bgneal@45 5974 if (!childB)
bgneal@45 5975 childB = cmnRoot;
bgneal@45 5976
bgneal@45 5977 if (childA == childB)
bgneal@45 5978 return 0; // equal
bgneal@45 5979
bgneal@45 5980 n = cmnRoot.firstChild;
bgneal@45 5981 while (n) {
bgneal@45 5982 if (n == childA)
bgneal@45 5983 return -1; // before
bgneal@45 5984
bgneal@45 5985 if (n == childB)
bgneal@45 5986 return 1; // after
bgneal@45 5987
bgneal@45 5988 n = n.nextSibling;
bgneal@45 5989 }
bgneal@45 5990 };
bgneal@45 5991
bgneal@45 5992 function _setEndPoint(st, n, o) {
bgneal@45 5993 var ec, sc;
bgneal@45 5994
bgneal@45 5995 if (st) {
bgneal@45 5996 t[START_CONTAINER] = n;
bgneal@45 5997 t[START_OFFSET] = o;
bgneal@45 5998 } else {
bgneal@45 5999 t[END_CONTAINER] = n;
bgneal@45 6000 t[END_OFFSET] = o;
bgneal@45 6001 }
bgneal@45 6002
bgneal@45 6003 // If one boundary-point of a Range is set to have a root container
bgneal@45 6004 // other than the current one for the Range, the Range is collapsed to
bgneal@45 6005 // the new position. This enforces the restriction that both boundary-
bgneal@45 6006 // points of a Range must have the same root container.
bgneal@45 6007 ec = t[END_CONTAINER];
bgneal@45 6008 while (ec.parentNode)
bgneal@45 6009 ec = ec.parentNode;
bgneal@45 6010
bgneal@45 6011 sc = t[START_CONTAINER];
bgneal@45 6012 while (sc.parentNode)
bgneal@45 6013 sc = sc.parentNode;
bgneal@45 6014
bgneal@45 6015 if (sc == ec) {
bgneal@45 6016 // The start position of a Range is guaranteed to never be after the
bgneal@45 6017 // end position. To enforce this restriction, if the start is set to
bgneal@45 6018 // be at a position after the end, the Range is collapsed to that
bgneal@45 6019 // position.
bgneal@45 6020 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
bgneal@45 6021 t.collapse(st);
bgneal@45 6022 } else
bgneal@45 6023 t.collapse(st);
bgneal@45 6024
bgneal@45 6025 t.collapsed = _isCollapsed();
bgneal@45 6026 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
bgneal@45 6027 };
bgneal@45 6028
bgneal@45 6029 function _traverse(how) {
bgneal@45 6030 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
bgneal@45 6031
bgneal@45 6032 if (t[START_CONTAINER] == t[END_CONTAINER])
bgneal@45 6033 return _traverseSameContainer(how);
bgneal@45 6034
bgneal@45 6035 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@45 6036 if (p == t[START_CONTAINER])
bgneal@45 6037 return _traverseCommonStartContainer(c, how);
bgneal@45 6038
bgneal@45 6039 ++endContainerDepth;
bgneal@45 6040 }
bgneal@45 6041
bgneal@45 6042 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@45 6043 if (p == t[END_CONTAINER])
bgneal@45 6044 return _traverseCommonEndContainer(c, how);
bgneal@45 6045
bgneal@45 6046 ++startContainerDepth;
bgneal@45 6047 }
bgneal@45 6048
bgneal@45 6049 depthDiff = startContainerDepth - endContainerDepth;
bgneal@45 6050
bgneal@45 6051 startNode = t[START_CONTAINER];
bgneal@45 6052 while (depthDiff > 0) {
bgneal@45 6053 startNode = startNode.parentNode;
bgneal@45 6054 depthDiff--;
bgneal@45 6055 }
bgneal@45 6056
bgneal@45 6057 endNode = t[END_CONTAINER];
bgneal@45 6058 while (depthDiff < 0) {
bgneal@45 6059 endNode = endNode.parentNode;
bgneal@45 6060 depthDiff++;
bgneal@45 6061 }
bgneal@45 6062
bgneal@45 6063 // ascend the ancestor hierarchy until we have a common parent.
bgneal@45 6064 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
bgneal@45 6065 startNode = sp;
bgneal@45 6066 endNode = ep;
bgneal@45 6067 }
bgneal@45 6068
bgneal@45 6069 return _traverseCommonAncestors(startNode, endNode, how);
bgneal@45 6070 };
bgneal@45 6071
bgneal@45 6072 function _traverseSameContainer(how) {
bgneal@45 6073 var frag, s, sub, n, cnt, sibling, xferNode, start, len;
bgneal@45 6074
bgneal@45 6075 if (how != DELETE)
bgneal@45 6076 frag = createDocumentFragment();
bgneal@45 6077
bgneal@45 6078 // If selection is empty, just return the fragment
bgneal@45 6079 if (t[START_OFFSET] == t[END_OFFSET])
bgneal@45 6080 return frag;
bgneal@45 6081
bgneal@45 6082 // Text node needs special case handling
bgneal@45 6083 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
bgneal@45 6084 // get the substring
bgneal@45 6085 s = t[START_CONTAINER].nodeValue;
bgneal@45 6086 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
bgneal@45 6087
bgneal@45 6088 // set the original text node to its new value
bgneal@45 6089 if (how != CLONE) {
bgneal@45 6090 n = t[START_CONTAINER];
bgneal@45 6091 start = t[START_OFFSET];
bgneal@45 6092 len = t[END_OFFSET] - t[START_OFFSET];
bgneal@45 6093
bgneal@45 6094 if (start === 0 && len >= n.nodeValue.length - 1) {
bgneal@45 6095 n.parentNode.removeChild(n);
bgneal@45 6096 } else {
bgneal@45 6097 n.deleteData(start, len);
bgneal@45 6098 }
bgneal@45 6099
bgneal@45 6100 // Nothing is partially selected, so collapse to start point
bgneal@45 6101 t.collapse(TRUE);
bgneal@45 6102 }
bgneal@45 6103
bgneal@45 6104 if (how == DELETE)
bgneal@45 6105 return;
bgneal@45 6106
bgneal@45 6107 if (sub.length > 0) {
bgneal@45 6108 frag.appendChild(doc.createTextNode(sub));
bgneal@45 6109 }
bgneal@45 6110
bgneal@45 6111 return frag;
bgneal@45 6112 }
bgneal@45 6113
bgneal@45 6114 // Copy nodes between the start/end offsets.
bgneal@45 6115 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
bgneal@45 6116 cnt = t[END_OFFSET] - t[START_OFFSET];
bgneal@45 6117
bgneal@45 6118 while (n && cnt > 0) {
bgneal@45 6119 sibling = n.nextSibling;
bgneal@45 6120 xferNode = _traverseFullySelected(n, how);
bgneal@45 6121
bgneal@45 6122 if (frag)
bgneal@45 6123 frag.appendChild( xferNode );
bgneal@45 6124
bgneal@45 6125 --cnt;
bgneal@45 6126 n = sibling;
bgneal@45 6127 }
bgneal@45 6128
bgneal@45 6129 // Nothing is partially selected, so collapse to start point
bgneal@45 6130 if (how != CLONE)
bgneal@45 6131 t.collapse(TRUE);
bgneal@45 6132
bgneal@45 6133 return frag;
bgneal@45 6134 };
bgneal@45 6135
bgneal@45 6136 function _traverseCommonStartContainer(endAncestor, how) {
bgneal@45 6137 var frag, n, endIdx, cnt, sibling, xferNode;
bgneal@45 6138
bgneal@45 6139 if (how != DELETE)
bgneal@45 6140 frag = createDocumentFragment();
bgneal@45 6141
bgneal@45 6142 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 6143
bgneal@45 6144 if (frag)
bgneal@45 6145 frag.appendChild(n);
bgneal@45 6146
bgneal@45 6147 endIdx = nodeIndex(endAncestor);
bgneal@45 6148 cnt = endIdx - t[START_OFFSET];
bgneal@45 6149
bgneal@45 6150 if (cnt <= 0) {
bgneal@45 6151 // Collapse to just before the endAncestor, which
bgneal@45 6152 // is partially selected.
bgneal@45 6153 if (how != CLONE) {
bgneal@45 6154 t.setEndBefore(endAncestor);
bgneal@45 6155 t.collapse(FALSE);
bgneal@45 6156 }
bgneal@45 6157
bgneal@45 6158 return frag;
bgneal@45 6159 }
bgneal@45 6160
bgneal@45 6161 n = endAncestor.previousSibling;
bgneal@45 6162 while (cnt > 0) {
bgneal@45 6163 sibling = n.previousSibling;
bgneal@45 6164 xferNode = _traverseFullySelected(n, how);
bgneal@45 6165
bgneal@45 6166 if (frag)
bgneal@45 6167 frag.insertBefore(xferNode, frag.firstChild);
bgneal@45 6168
bgneal@45 6169 --cnt;
bgneal@45 6170 n = sibling;
bgneal@45 6171 }
bgneal@45 6172
bgneal@45 6173 // Collapse to just before the endAncestor, which
bgneal@45 6174 // is partially selected.
bgneal@45 6175 if (how != CLONE) {
bgneal@45 6176 t.setEndBefore(endAncestor);
bgneal@45 6177 t.collapse(FALSE);
bgneal@45 6178 }
bgneal@45 6179
bgneal@45 6180 return frag;
bgneal@45 6181 };
bgneal@45 6182
bgneal@45 6183 function _traverseCommonEndContainer(startAncestor, how) {
bgneal@45 6184 var frag, startIdx, n, cnt, sibling, xferNode;
bgneal@45 6185
bgneal@45 6186 if (how != DELETE)
bgneal@45 6187 frag = createDocumentFragment();
bgneal@45 6188
bgneal@45 6189 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 6190 if (frag)
bgneal@45 6191 frag.appendChild(n);
bgneal@45 6192
bgneal@45 6193 startIdx = nodeIndex(startAncestor);
bgneal@45 6194 ++startIdx; // Because we already traversed it
bgneal@45 6195
bgneal@45 6196 cnt = t[END_OFFSET] - startIdx;
bgneal@45 6197 n = startAncestor.nextSibling;
bgneal@45 6198 while (n && cnt > 0) {
bgneal@45 6199 sibling = n.nextSibling;
bgneal@45 6200 xferNode = _traverseFullySelected(n, how);
bgneal@45 6201
bgneal@45 6202 if (frag)
bgneal@45 6203 frag.appendChild(xferNode);
bgneal@45 6204
bgneal@45 6205 --cnt;
bgneal@45 6206 n = sibling;
bgneal@45 6207 }
bgneal@45 6208
bgneal@45 6209 if (how != CLONE) {
bgneal@45 6210 t.setStartAfter(startAncestor);
bgneal@45 6211 t.collapse(TRUE);
bgneal@45 6212 }
bgneal@45 6213
bgneal@45 6214 return frag;
bgneal@45 6215 };
bgneal@45 6216
bgneal@45 6217 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
bgneal@45 6218 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
bgneal@45 6219
bgneal@45 6220 if (how != DELETE)
bgneal@45 6221 frag = createDocumentFragment();
bgneal@45 6222
bgneal@45 6223 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 6224 if (frag)
bgneal@45 6225 frag.appendChild(n);
bgneal@45 6226
bgneal@45 6227 commonParent = startAncestor.parentNode;
bgneal@45 6228 startOffset = nodeIndex(startAncestor);
bgneal@45 6229 endOffset = nodeIndex(endAncestor);
bgneal@45 6230 ++startOffset;
bgneal@45 6231
bgneal@45 6232 cnt = endOffset - startOffset;
bgneal@45 6233 sibling = startAncestor.nextSibling;
bgneal@45 6234
bgneal@45 6235 while (cnt > 0) {
bgneal@45 6236 nextSibling = sibling.nextSibling;
bgneal@45 6237 n = _traverseFullySelected(sibling, how);
bgneal@45 6238
bgneal@45 6239 if (frag)
bgneal@45 6240 frag.appendChild(n);
bgneal@45 6241
bgneal@45 6242 sibling = nextSibling;
bgneal@45 6243 --cnt;
bgneal@45 6244 }
bgneal@45 6245
bgneal@45 6246 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 6247
bgneal@45 6248 if (frag)
bgneal@45 6249 frag.appendChild(n);
bgneal@45 6250
bgneal@45 6251 if (how != CLONE) {
bgneal@45 6252 t.setStartAfter(startAncestor);
bgneal@45 6253 t.collapse(TRUE);
bgneal@45 6254 }
bgneal@45 6255
bgneal@45 6256 return frag;
bgneal@45 6257 };
bgneal@45 6258
bgneal@45 6259 function _traverseRightBoundary(root, how) {
bgneal@45 6260 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
bgneal@45 6261
bgneal@45 6262 if (next == root)
bgneal@45 6263 return _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 6264
bgneal@45 6265 parent = next.parentNode;
bgneal@45 6266 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@45 6267
bgneal@45 6268 while (parent) {
bgneal@45 6269 while (next) {
bgneal@45 6270 prevSibling = next.previousSibling;
bgneal@45 6271 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 6272
bgneal@45 6273 if (how != DELETE)
bgneal@45 6274 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
bgneal@45 6275
bgneal@45 6276 isFullySelected = TRUE;
bgneal@45 6277 next = prevSibling;
bgneal@45 6278 }
bgneal@45 6279
bgneal@45 6280 if (parent == root)
bgneal@45 6281 return clonedParent;
bgneal@45 6282
bgneal@45 6283 next = parent.previousSibling;
bgneal@45 6284 parent = parent.parentNode;
bgneal@45 6285
bgneal@45 6286 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@45 6287
bgneal@45 6288 if (how != DELETE)
bgneal@45 6289 clonedGrandParent.appendChild(clonedParent);
bgneal@45 6290
bgneal@45 6291 clonedParent = clonedGrandParent;
bgneal@45 6292 }
bgneal@45 6293 };
bgneal@45 6294
bgneal@45 6295 function _traverseLeftBoundary(root, how) {
bgneal@45 6296 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
bgneal@45 6297
bgneal@45 6298 if (next == root)
bgneal@45 6299 return _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 6300
bgneal@45 6301 parent = next.parentNode;
bgneal@45 6302 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@45 6303
bgneal@45 6304 while (parent) {
bgneal@45 6305 while (next) {
bgneal@45 6306 nextSibling = next.nextSibling;
bgneal@45 6307 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 6308
bgneal@45 6309 if (how != DELETE)
bgneal@45 6310 clonedParent.appendChild(clonedChild);
bgneal@45 6311
bgneal@45 6312 isFullySelected = TRUE;
bgneal@45 6313 next = nextSibling;
bgneal@45 6314 }
bgneal@45 6315
bgneal@45 6316 if (parent == root)
bgneal@45 6317 return clonedParent;
bgneal@45 6318
bgneal@45 6319 next = parent.nextSibling;
bgneal@45 6320 parent = parent.parentNode;
bgneal@45 6321
bgneal@45 6322 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@45 6323
bgneal@45 6324 if (how != DELETE)
bgneal@45 6325 clonedGrandParent.appendChild(clonedParent);
bgneal@45 6326
bgneal@45 6327 clonedParent = clonedGrandParent;
bgneal@45 6328 }
bgneal@45 6329 };
bgneal@45 6330
bgneal@45 6331 function _traverseNode(n, isFullySelected, isLeft, how) {
bgneal@45 6332 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
bgneal@45 6333
bgneal@45 6334 if (isFullySelected)
bgneal@45 6335 return _traverseFullySelected(n, how);
bgneal@45 6336
bgneal@45 6337 if (n.nodeType == 3 /* TEXT_NODE */) {
bgneal@45 6338 txtValue = n.nodeValue;
bgneal@45 6339
bgneal@45 6340 if (isLeft) {
bgneal@45 6341 offset = t[START_OFFSET];
bgneal@45 6342 newNodeValue = txtValue.substring(offset);
bgneal@45 6343 oldNodeValue = txtValue.substring(0, offset);
bgneal@45 6344 } else {
bgneal@45 6345 offset = t[END_OFFSET];
bgneal@45 6346 newNodeValue = txtValue.substring(0, offset);
bgneal@45 6347 oldNodeValue = txtValue.substring(offset);
bgneal@45 6348 }
bgneal@45 6349
bgneal@45 6350 if (how != CLONE)
bgneal@45 6351 n.nodeValue = oldNodeValue;
bgneal@45 6352
bgneal@45 6353 if (how == DELETE)
bgneal@45 6354 return;
bgneal@45 6355
bgneal@45 6356 newNode = dom.clone(n, FALSE);
bgneal@45 6357 newNode.nodeValue = newNodeValue;
bgneal@45 6358
bgneal@45 6359 return newNode;
bgneal@45 6360 }
bgneal@45 6361
bgneal@45 6362 if (how == DELETE)
bgneal@45 6363 return;
bgneal@45 6364
bgneal@45 6365 return dom.clone(n, FALSE);
bgneal@45 6366 };
bgneal@45 6367
bgneal@45 6368 function _traverseFullySelected(n, how) {
bgneal@45 6369 if (how != DELETE)
bgneal@45 6370 return how == CLONE ? dom.clone(n, TRUE) : n;
bgneal@45 6371
bgneal@45 6372 n.parentNode.removeChild(n);
bgneal@45 6373 };
bgneal@45 6374 };
bgneal@45 6375
bgneal@45 6376 ns.Range = Range;
bgneal@45 6377 })(tinymce.dom);
bgneal@45 6378
bgneal@45 6379 (function() {
bgneal@45 6380 function Selection(selection) {
bgneal@45 6381 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
bgneal@45 6382
bgneal@45 6383 function getPosition(rng, start) {
bgneal@45 6384 var checkRng, startIndex = 0, endIndex, inside,
bgneal@45 6385 children, child, offset, index, position = -1, parent;
bgneal@45 6386
bgneal@45 6387 // Setup test range, collapse it and get the parent
bgneal@45 6388 checkRng = rng.duplicate();
bgneal@45 6389 checkRng.collapse(start);
bgneal@45 6390 parent = checkRng.parentElement();
bgneal@45 6391
bgneal@45 6392 // Check if the selection is within the right document
bgneal@45 6393 if (parent.ownerDocument !== selection.dom.doc)
bgneal@45 6394 return;
bgneal@45 6395
bgneal@45 6396 // IE will report non editable elements as it's parent so look for an editable one
bgneal@45 6397 while (parent.contentEditable === "false") {
bgneal@45 6398 parent = parent.parentNode;
bgneal@45 6399 }
bgneal@45 6400
bgneal@45 6401 // If parent doesn't have any children then return that we are inside the element
bgneal@45 6402 if (!parent.hasChildNodes()) {
bgneal@45 6403 return {node : parent, inside : 1};
bgneal@45 6404 }
bgneal@45 6405
bgneal@45 6406 // Setup node list and endIndex
bgneal@45 6407 children = parent.children;
bgneal@45 6408 endIndex = children.length - 1;
bgneal@45 6409
bgneal@45 6410 // Perform a binary search for the position
bgneal@45 6411 while (startIndex <= endIndex) {
bgneal@45 6412 index = Math.floor((startIndex + endIndex) / 2);
bgneal@45 6413
bgneal@45 6414 // Move selection to node and compare the ranges
bgneal@45 6415 child = children[index];
bgneal@45 6416 checkRng.moveToElementText(child);
bgneal@45 6417 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
bgneal@45 6418
bgneal@45 6419 // Before/after or an exact match
bgneal@45 6420 if (position > 0) {
bgneal@45 6421 endIndex = index - 1;
bgneal@45 6422 } else if (position < 0) {
bgneal@45 6423 startIndex = index + 1;
bgneal@45 6424 } else {
bgneal@45 6425 return {node : child};
bgneal@45 6426 }
bgneal@45 6427 }
bgneal@45 6428
bgneal@45 6429 // Check if child position is before or we didn't find a position
bgneal@45 6430 if (position < 0) {
bgneal@45 6431 // No element child was found use the parent element and the offset inside that
bgneal@45 6432 if (!child) {
bgneal@45 6433 checkRng.moveToElementText(parent);
bgneal@45 6434 checkRng.collapse(true);
bgneal@45 6435 child = parent;
bgneal@45 6436 inside = true;
bgneal@45 6437 } else
bgneal@45 6438 checkRng.collapse(false);
bgneal@45 6439
bgneal@45 6440 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
bgneal@45 6441
bgneal@45 6442 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
bgneal@45 6443 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
bgneal@45 6444 checkRng = rng.duplicate();
bgneal@45 6445 checkRng.collapse(start);
bgneal@45 6446
bgneal@45 6447 offset = -1;
bgneal@45 6448 while (parent == checkRng.parentElement()) {
bgneal@45 6449 if (checkRng.move('character', -1) == 0)
bgneal@45 6450 break;
bgneal@45 6451
bgneal@45 6452 offset++;
bgneal@45 6453 }
bgneal@45 6454 }
bgneal@45 6455
bgneal@45 6456 offset = offset || checkRng.text.replace('\r\n', ' ').length;
bgneal@45 6457 } else {
bgneal@45 6458 // Child position is after the selection endpoint
bgneal@45 6459 checkRng.collapse(true);
bgneal@45 6460 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
bgneal@45 6461
bgneal@45 6462 // Get the length of the text to find where the endpoint is relative to it's container
bgneal@45 6463 offset = checkRng.text.replace('\r\n', ' ').length;
bgneal@45 6464 }
bgneal@45 6465
bgneal@45 6466 return {node : child, position : position, offset : offset, inside : inside};
bgneal@45 6467 };
bgneal@45 6468
bgneal@45 6469 // Returns a W3C DOM compatible range object by using the IE Range API
bgneal@45 6470 function getRange() {
bgneal@45 6471 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
bgneal@45 6472
bgneal@45 6473 // If selection is outside the current document just return an empty range
bgneal@45 6474 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
bgneal@45 6475 if (element.ownerDocument != dom.doc)
bgneal@45 6476 return domRange;
bgneal@45 6477
bgneal@45 6478 collapsed = selection.isCollapsed();
bgneal@45 6479
bgneal@45 6480 // Handle control selection
bgneal@45 6481 if (ieRange.item) {
bgneal@45 6482 domRange.setStart(element.parentNode, dom.nodeIndex(element));
bgneal@45 6483 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
bgneal@45 6484
bgneal@45 6485 return domRange;
bgneal@45 6486 }
bgneal@45 6487
bgneal@45 6488 function findEndPoint(start) {
bgneal@45 6489 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
bgneal@45 6490
bgneal@45 6491 container = endPoint.node;
bgneal@45 6492 offset = endPoint.offset;
bgneal@45 6493
bgneal@45 6494 if (endPoint.inside && !container.hasChildNodes()) {
bgneal@45 6495 domRange[start ? 'setStart' : 'setEnd'](container, 0);
bgneal@45 6496 return;
bgneal@45 6497 }
bgneal@45 6498
bgneal@45 6499 if (offset === undef) {
bgneal@45 6500 domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
bgneal@45 6501 return;
bgneal@45 6502 }
bgneal@45 6503
bgneal@45 6504 if (endPoint.position < 0) {
bgneal@45 6505 sibling = endPoint.inside ? container.firstChild : container.nextSibling;
bgneal@45 6506
bgneal@45 6507 if (!sibling) {
bgneal@45 6508 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
bgneal@45 6509 return;
bgneal@45 6510 }
bgneal@45 6511
bgneal@45 6512 if (!offset) {
bgneal@45 6513 if (sibling.nodeType == 3)
bgneal@45 6514 domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
bgneal@45 6515 else
bgneal@45 6516 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
bgneal@45 6517
bgneal@45 6518 return;
bgneal@45 6519 }
bgneal@45 6520
bgneal@45 6521 // Find the text node and offset
bgneal@45 6522 while (sibling) {
bgneal@45 6523 nodeValue = sibling.nodeValue;
bgneal@45 6524 textNodeOffset += nodeValue.length;
bgneal@45 6525
bgneal@45 6526 // We are at or passed the position we where looking for
bgneal@45 6527 if (textNodeOffset >= offset) {
bgneal@45 6528 container = sibling;
bgneal@45 6529 textNodeOffset -= offset;
bgneal@45 6530 textNodeOffset = nodeValue.length - textNodeOffset;
bgneal@45 6531 break;
bgneal@45 6532 }
bgneal@45 6533
bgneal@45 6534 sibling = sibling.nextSibling;
bgneal@45 6535 }
bgneal@45 6536 } else {
bgneal@45 6537 // Find the text node and offset
bgneal@45 6538 sibling = container.previousSibling;
bgneal@45 6539
bgneal@45 6540 if (!sibling)
bgneal@45 6541 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
bgneal@45 6542
bgneal@45 6543 // If there isn't any text to loop then use the first position
bgneal@45 6544 if (!offset) {
bgneal@45 6545 if (container.nodeType == 3)
bgneal@45 6546 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
bgneal@45 6547 else
bgneal@45 6548 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
bgneal@45 6549
bgneal@45 6550 return;
bgneal@45 6551 }
bgneal@45 6552
bgneal@45 6553 while (sibling) {
bgneal@45 6554 textNodeOffset += sibling.nodeValue.length;
bgneal@45 6555
bgneal@45 6556 // We are at or passed the position we where looking for
bgneal@45 6557 if (textNodeOffset >= offset) {
bgneal@45 6558 container = sibling;
bgneal@45 6559 textNodeOffset -= offset;
bgneal@45 6560 break;
bgneal@45 6561 }
bgneal@45 6562
bgneal@45 6563 sibling = sibling.previousSibling;
bgneal@45 6564 }
bgneal@45 6565 }
bgneal@45 6566
bgneal@45 6567 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
bgneal@45 6568 };
bgneal@45 6569
bgneal@45 6570 try {
bgneal@45 6571 // Find start point
bgneal@45 6572 findEndPoint(true);
bgneal@45 6573
bgneal@45 6574 // Find end point if needed
bgneal@45 6575 if (!collapsed)
bgneal@45 6576 findEndPoint();
bgneal@45 6577 } catch (ex) {
bgneal@45 6578 // IE has a nasty bug where text nodes might throw "invalid argument" when you
bgneal@45 6579 // access the nodeValue or other properties of text nodes. This seems to happend when
bgneal@45 6580 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
bgneal@45 6581 if (ex.number == -2147024809) {
bgneal@45 6582 // Get the current selection
bgneal@45 6583 bookmark = self.getBookmark(2);
bgneal@45 6584
bgneal@45 6585 // Get start element
bgneal@45 6586 tmpRange = ieRange.duplicate();
bgneal@45 6587 tmpRange.collapse(true);
bgneal@45 6588 element = tmpRange.parentElement();
bgneal@45 6589
bgneal@45 6590 // Get end element
bgneal@45 6591 if (!collapsed) {
bgneal@45 6592 tmpRange = ieRange.duplicate();
bgneal@45 6593 tmpRange.collapse(false);
bgneal@45 6594 element2 = tmpRange.parentElement();
bgneal@45 6595 element2.innerHTML = element2.innerHTML;
bgneal@45 6596 }
bgneal@45 6597
bgneal@45 6598 // Remove the broken elements
bgneal@45 6599 element.innerHTML = element.innerHTML;
bgneal@45 6600
bgneal@45 6601 // Restore the selection
bgneal@45 6602 self.moveToBookmark(bookmark);
bgneal@45 6603
bgneal@45 6604 // Since the range has moved we need to re-get it
bgneal@45 6605 ieRange = selection.getRng();
bgneal@45 6606
bgneal@45 6607 // Find start point
bgneal@45 6608 findEndPoint(true);
bgneal@45 6609
bgneal@45 6610 // Find end point if needed
bgneal@45 6611 if (!collapsed)
bgneal@45 6612 findEndPoint();
bgneal@45 6613 } else
bgneal@45 6614 throw ex; // Throw other errors
bgneal@45 6615 }
bgneal@45 6616
bgneal@45 6617 return domRange;
bgneal@45 6618 };
bgneal@45 6619
bgneal@45 6620 this.getBookmark = function(type) {
bgneal@45 6621 var rng = selection.getRng(), start, end, bookmark = {};
bgneal@45 6622
bgneal@45 6623 function getIndexes(node) {
bgneal@45 6624 var node, parent, root, children, i, indexes = [];
bgneal@45 6625
bgneal@45 6626 parent = node.parentNode;
bgneal@45 6627 root = dom.getRoot().parentNode;
bgneal@45 6628
bgneal@45 6629 while (parent != root && parent.nodeType !== 9) {
bgneal@45 6630 children = parent.children;
bgneal@45 6631
bgneal@45 6632 i = children.length;
bgneal@45 6633 while (i--) {
bgneal@45 6634 if (node === children[i]) {
bgneal@45 6635 indexes.push(i);
bgneal@45 6636 break;
bgneal@45 6637 }
bgneal@45 6638 }
bgneal@45 6639
bgneal@45 6640 node = parent;
bgneal@45 6641 parent = parent.parentNode;
bgneal@45 6642 }
bgneal@45 6643
bgneal@45 6644 return indexes;
bgneal@45 6645 };
bgneal@45 6646
bgneal@45 6647 function getBookmarkEndPoint(start) {
bgneal@45 6648 var position;
bgneal@45 6649
bgneal@45 6650 position = getPosition(rng, start);
bgneal@45 6651 if (position) {
bgneal@45 6652 return {
bgneal@45 6653 position : position.position,
bgneal@45 6654 offset : position.offset,
bgneal@45 6655 indexes : getIndexes(position.node),
bgneal@45 6656 inside : position.inside
bgneal@45 6657 };
bgneal@45 6658 }
bgneal@45 6659 };
bgneal@45 6660
bgneal@45 6661 // Non ubstructive bookmark
bgneal@45 6662 if (type === 2) {
bgneal@45 6663 // Handle text selection
bgneal@45 6664 if (!rng.item) {
bgneal@45 6665 bookmark.start = getBookmarkEndPoint(true);
bgneal@45 6666
bgneal@45 6667 if (!selection.isCollapsed())
bgneal@45 6668 bookmark.end = getBookmarkEndPoint();
bgneal@45 6669 } else
bgneal@45 6670 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
bgneal@45 6671 }
bgneal@45 6672
bgneal@45 6673 return bookmark;
bgneal@45 6674 };
bgneal@45 6675
bgneal@45 6676 this.moveToBookmark = function(bookmark) {
bgneal@45 6677 var rng, body = dom.doc.body;
bgneal@45 6678
bgneal@45 6679 function resolveIndexes(indexes) {
bgneal@45 6680 var node, i, idx, children;
bgneal@45 6681
bgneal@45 6682 node = dom.getRoot();
bgneal@45 6683 for (i = indexes.length - 1; i >= 0; i--) {
bgneal@45 6684 children = node.children;
bgneal@45 6685 idx = indexes[i];
bgneal@45 6686
bgneal@45 6687 if (idx <= children.length - 1) {
bgneal@45 6688 node = children[idx];
bgneal@45 6689 }
bgneal@45 6690 }
bgneal@45 6691
bgneal@45 6692 return node;
bgneal@45 6693 };
bgneal@45 6694
bgneal@45 6695 function setBookmarkEndPoint(start) {
bgneal@45 6696 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
bgneal@45 6697
bgneal@45 6698 if (endPoint) {
bgneal@45 6699 moveLeft = endPoint.position > 0;
bgneal@45 6700
bgneal@45 6701 moveRng = body.createTextRange();
bgneal@45 6702 moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
bgneal@45 6703
bgneal@45 6704 offset = endPoint.offset;
bgneal@45 6705 if (offset !== undef) {
bgneal@45 6706 moveRng.collapse(endPoint.inside || moveLeft);
bgneal@45 6707 moveRng.moveStart('character', moveLeft ? -offset : offset);
bgneal@45 6708 } else
bgneal@45 6709 moveRng.collapse(start);
bgneal@45 6710
bgneal@45 6711 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
bgneal@45 6712
bgneal@45 6713 if (start)
bgneal@45 6714 rng.collapse(true);
bgneal@45 6715 }
bgneal@45 6716 };
bgneal@45 6717
bgneal@45 6718 if (bookmark.start) {
bgneal@45 6719 if (bookmark.start.ctrl) {
bgneal@45 6720 rng = body.createControlRange();
bgneal@45 6721 rng.addElement(resolveIndexes(bookmark.start.indexes));
bgneal@45 6722 rng.select();
bgneal@45 6723 } else {
bgneal@45 6724 rng = body.createTextRange();
bgneal@45 6725 setBookmarkEndPoint(true);
bgneal@45 6726 setBookmarkEndPoint();
bgneal@45 6727 rng.select();
bgneal@45 6728 }
bgneal@45 6729 }
bgneal@45 6730 };
bgneal@45 6731
bgneal@45 6732 this.addRange = function(rng) {
bgneal@45 6733 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
bgneal@45 6734
bgneal@45 6735 function setEndPoint(start) {
bgneal@45 6736 var container, offset, marker, tmpRng, nodes;
bgneal@45 6737
bgneal@45 6738 marker = dom.create('a');
bgneal@45 6739 container = start ? startContainer : endContainer;
bgneal@45 6740 offset = start ? startOffset : endOffset;
bgneal@45 6741 tmpRng = ieRng.duplicate();
bgneal@45 6742
bgneal@45 6743 if (container == doc || container == doc.documentElement) {
bgneal@45 6744 container = body;
bgneal@45 6745 offset = 0;
bgneal@45 6746 }
bgneal@45 6747
bgneal@45 6748 if (container.nodeType == 3) {
bgneal@45 6749 container.parentNode.insertBefore(marker, container);
bgneal@45 6750 tmpRng.moveToElementText(marker);
bgneal@45 6751 tmpRng.moveStart('character', offset);
bgneal@45 6752 dom.remove(marker);
bgneal@45 6753 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@45 6754 } else {
bgneal@45 6755 nodes = container.childNodes;
bgneal@45 6756
bgneal@45 6757 if (nodes.length) {
bgneal@45 6758 if (offset >= nodes.length) {
bgneal@45 6759 dom.insertAfter(marker, nodes[nodes.length - 1]);
bgneal@45 6760 } else {
bgneal@45 6761 container.insertBefore(marker, nodes[offset]);
bgneal@45 6762 }
bgneal@45 6763
bgneal@45 6764 tmpRng.moveToElementText(marker);
bgneal@45 6765 } else if (container.canHaveHTML) {
bgneal@45 6766 // Empty node selection for example <div>|</div>
bgneal@45 6767 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
bgneal@45 6768 container.innerHTML = '<span>\uFEFF</span>';
bgneal@45 6769 marker = container.firstChild;
bgneal@45 6770 tmpRng.moveToElementText(marker);
bgneal@45 6771 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
bgneal@45 6772 }
bgneal@45 6773
bgneal@45 6774 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@45 6775 dom.remove(marker);
bgneal@45 6776 }
bgneal@45 6777 }
bgneal@45 6778
bgneal@45 6779 // Setup some shorter versions
bgneal@45 6780 startContainer = rng.startContainer;
bgneal@45 6781 startOffset = rng.startOffset;
bgneal@45 6782 endContainer = rng.endContainer;
bgneal@45 6783 endOffset = rng.endOffset;
bgneal@45 6784 ieRng = body.createTextRange();
bgneal@45 6785
bgneal@45 6786 // If single element selection then try making a control selection out of it
bgneal@45 6787 if (startContainer == endContainer && startContainer.nodeType == 1) {
bgneal@45 6788 // Trick to place the caret inside an empty block element like <p></p>
bgneal@45 6789 if (!startContainer.hasChildNodes()) {
bgneal@45 6790 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
bgneal@45 6791 ieRng.moveToElementText(startContainer.lastChild);
bgneal@45 6792 ieRng.select();
bgneal@45 6793 dom.doc.selection.clear();
bgneal@45 6794 startContainer.innerHTML = '';
bgneal@45 6795 return;
bgneal@45 6796 }
bgneal@45 6797
bgneal@45 6798 if (startOffset == endOffset - 1) {
bgneal@45 6799 try {
bgneal@45 6800 ctrlRng = body.createControlRange();
bgneal@45 6801 ctrlRng.addElement(startContainer.childNodes[startOffset]);
bgneal@45 6802 ctrlRng.select();
bgneal@45 6803 return;
bgneal@45 6804 } catch (ex) {
bgneal@45 6805 // Ignore
bgneal@45 6806 }
bgneal@45 6807 }
bgneal@45 6808 }
bgneal@45 6809
bgneal@45 6810 // Set start/end point of selection
bgneal@45 6811 setEndPoint(true);
bgneal@45 6812 setEndPoint();
bgneal@45 6813
bgneal@45 6814 // Select the new range and scroll it into view
bgneal@45 6815 ieRng.select();
bgneal@45 6816 };
bgneal@45 6817
bgneal@45 6818 // Expose range method
bgneal@45 6819 this.getRangeAt = getRange;
bgneal@45 6820 };
bgneal@45 6821
bgneal@45 6822 // Expose the selection object
bgneal@45 6823 tinymce.dom.TridentSelection = Selection;
bgneal@45 6824 })();
bgneal@45 6825
bgneal@45 6826
bgneal@45 6827 /*
bgneal@45 6828 * Sizzle CSS Selector Engine - v1.0
bgneal@45 6829 * Copyright 2009, The Dojo Foundation
bgneal@45 6830 * Released under the MIT, BSD, and GPL Licenses.
bgneal@45 6831 * More information: http://sizzlejs.com/
bgneal@45 6832 */
bgneal@45 6833 (function(){
bgneal@45 6834
bgneal@45 6835 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
bgneal@45 6836 done = 0,
bgneal@45 6837 toString = Object.prototype.toString,
bgneal@45 6838 hasDuplicate = false,
bgneal@45 6839 baseHasDuplicate = true;
bgneal@45 6840
bgneal@45 6841 // Here we check if the JavaScript engine is using some sort of
bgneal@45 6842 // optimization where it does not always call our comparision
bgneal@45 6843 // function. If that is the case, discard the hasDuplicate value.
bgneal@45 6844 // Thus far that includes Google Chrome.
bgneal@45 6845 [0, 0].sort(function(){
bgneal@45 6846 baseHasDuplicate = false;
bgneal@45 6847 return 0;
bgneal@45 6848 });
bgneal@45 6849
bgneal@45 6850 var Sizzle = function(selector, context, results, seed) {
bgneal@45 6851 results = results || [];
bgneal@45 6852 context = context || document;
bgneal@45 6853
bgneal@45 6854 var origContext = context;
bgneal@45 6855
bgneal@45 6856 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
bgneal@45 6857 return [];
bgneal@45 6858 }
bgneal@45 6859
bgneal@45 6860 if ( !selector || typeof selector !== "string" ) {
bgneal@45 6861 return results;
bgneal@45 6862 }
bgneal@45 6863
bgneal@45 6864 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
bgneal@45 6865 soFar = selector, ret, cur, pop, i;
bgneal@45 6866
bgneal@45 6867 // Reset the position of the chunker regexp (start from head)
bgneal@45 6868 do {
bgneal@45 6869 chunker.exec("");
bgneal@45 6870 m = chunker.exec(soFar);
bgneal@45 6871
bgneal@45 6872 if ( m ) {
bgneal@45 6873 soFar = m[3];
bgneal@45 6874
bgneal@45 6875 parts.push( m[1] );
bgneal@45 6876
bgneal@45 6877 if ( m[2] ) {
bgneal@45 6878 extra = m[3];
bgneal@45 6879 break;
bgneal@45 6880 }
bgneal@45 6881 }
bgneal@45 6882 } while ( m );
bgneal@45 6883
bgneal@45 6884 if ( parts.length > 1 && origPOS.exec( selector ) ) {
bgneal@45 6885 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
bgneal@45 6886 set = posProcess( parts[0] + parts[1], context );
bgneal@45 6887 } else {
bgneal@45 6888 set = Expr.relative[ parts[0] ] ?
bgneal@45 6889 [ context ] :
bgneal@45 6890 Sizzle( parts.shift(), context );
bgneal@45 6891
bgneal@45 6892 while ( parts.length ) {
bgneal@45 6893 selector = parts.shift();
bgneal@45 6894
bgneal@45 6895 if ( Expr.relative[ selector ] ) {
bgneal@45 6896 selector += parts.shift();
bgneal@45 6897 }
bgneal@45 6898
bgneal@45 6899 set = posProcess( selector, set );
bgneal@45 6900 }
bgneal@45 6901 }
bgneal@45 6902 } else {
bgneal@45 6903 // Take a shortcut and set the context if the root selector is an ID
bgneal@45 6904 // (but not if it'll be faster if the inner selector is an ID)
bgneal@45 6905 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
bgneal@45 6906 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
bgneal@45 6907 ret = Sizzle.find( parts.shift(), context, contextXML );
bgneal@45 6908 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
bgneal@45 6909 }
bgneal@45 6910
bgneal@45 6911 if ( context ) {
bgneal@45 6912 ret = seed ?
bgneal@45 6913 { expr: parts.pop(), set: makeArray(seed) } :
bgneal@45 6914 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
bgneal@45 6915 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
bgneal@45 6916
bgneal@45 6917 if ( parts.length > 0 ) {
bgneal@45 6918 checkSet = makeArray(set);
bgneal@45 6919 } else {
bgneal@45 6920 prune = false;
bgneal@45 6921 }
bgneal@45 6922
bgneal@45 6923 while ( parts.length ) {
bgneal@45 6924 cur = parts.pop();
bgneal@45 6925 pop = cur;
bgneal@45 6926
bgneal@45 6927 if ( !Expr.relative[ cur ] ) {
bgneal@45 6928 cur = "";
bgneal@45 6929 } else {
bgneal@45 6930 pop = parts.pop();
bgneal@45 6931 }
bgneal@45 6932
bgneal@45 6933 if ( pop == null ) {
bgneal@45 6934 pop = context;
bgneal@45 6935 }
bgneal@45 6936
bgneal@45 6937 Expr.relative[ cur ]( checkSet, pop, contextXML );
bgneal@45 6938 }
bgneal@45 6939 } else {
bgneal@45 6940 checkSet = parts = [];
bgneal@45 6941 }
bgneal@45 6942 }
bgneal@45 6943
bgneal@45 6944 if ( !checkSet ) {
bgneal@45 6945 checkSet = set;
bgneal@45 6946 }
bgneal@45 6947
bgneal@45 6948 if ( !checkSet ) {
bgneal@45 6949 Sizzle.error( cur || selector );
bgneal@45 6950 }
bgneal@45 6951
bgneal@45 6952 if ( toString.call(checkSet) === "[object Array]" ) {
bgneal@45 6953 if ( !prune ) {
bgneal@45 6954 results.push.apply( results, checkSet );
bgneal@45 6955 } else if ( context && context.nodeType === 1 ) {
bgneal@45 6956 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@45 6957 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
bgneal@45 6958 results.push( set[i] );
bgneal@45 6959 }
bgneal@45 6960 }
bgneal@45 6961 } else {
bgneal@45 6962 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@45 6963 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
bgneal@45 6964 results.push( set[i] );
bgneal@45 6965 }
bgneal@45 6966 }
bgneal@45 6967 }
bgneal@45 6968 } else {
bgneal@45 6969 makeArray( checkSet, results );
bgneal@45 6970 }
bgneal@45 6971
bgneal@45 6972 if ( extra ) {
bgneal@45 6973 Sizzle( extra, origContext, results, seed );
bgneal@45 6974 Sizzle.uniqueSort( results );
bgneal@45 6975 }
bgneal@45 6976
bgneal@45 6977 return results;
bgneal@45 6978 };
bgneal@45 6979
bgneal@45 6980 Sizzle.uniqueSort = function(results){
bgneal@45 6981 if ( sortOrder ) {
bgneal@45 6982 hasDuplicate = baseHasDuplicate;
bgneal@45 6983 results.sort(sortOrder);
bgneal@45 6984
bgneal@45 6985 if ( hasDuplicate ) {
bgneal@45 6986 for ( var i = 1; i < results.length; i++ ) {
bgneal@45 6987 if ( results[i] === results[i-1] ) {
bgneal@45 6988 results.splice(i--, 1);
bgneal@45 6989 }
bgneal@45 6990 }
bgneal@45 6991 }
bgneal@45 6992 }
bgneal@45 6993
bgneal@45 6994 return results;
bgneal@45 6995 };
bgneal@45 6996
bgneal@45 6997 Sizzle.matches = function(expr, set){
bgneal@45 6998 return Sizzle(expr, null, null, set);
bgneal@45 6999 };
bgneal@45 7000
bgneal@45 7001 Sizzle.find = function(expr, context, isXML){
bgneal@45 7002 var set;
bgneal@45 7003
bgneal@45 7004 if ( !expr ) {
bgneal@45 7005 return [];
bgneal@45 7006 }
bgneal@45 7007
bgneal@45 7008 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
bgneal@45 7009 var type = Expr.order[i], match;
bgneal@45 7010
bgneal@45 7011 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
bgneal@45 7012 var left = match[1];
bgneal@45 7013 match.splice(1,1);
bgneal@45 7014
bgneal@45 7015 if ( left.substr( left.length - 1 ) !== "\\" ) {
bgneal@45 7016 match[1] = (match[1] || "").replace(/\\/g, "");
bgneal@45 7017 set = Expr.find[ type ]( match, context, isXML );
bgneal@45 7018 if ( set != null ) {
bgneal@45 7019 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 7020 break;
bgneal@45 7021 }
bgneal@45 7022 }
bgneal@45 7023 }
bgneal@45 7024 }
bgneal@45 7025
bgneal@45 7026 if ( !set ) {
bgneal@45 7027 set = context.getElementsByTagName("*");
bgneal@45 7028 }
bgneal@45 7029
bgneal@45 7030 return {set: set, expr: expr};
bgneal@45 7031 };
bgneal@45 7032
bgneal@45 7033 Sizzle.filter = function(expr, set, inplace, not){
bgneal@45 7034 var old = expr, result = [], curLoop = set, match, anyFound,
bgneal@45 7035 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
bgneal@45 7036
bgneal@45 7037 while ( expr && set.length ) {
bgneal@45 7038 for ( var type in Expr.filter ) {
bgneal@45 7039 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
bgneal@45 7040 var filter = Expr.filter[ type ], found, item, left = match[1];
bgneal@45 7041 anyFound = false;
bgneal@45 7042
bgneal@45 7043 match.splice(1,1);
bgneal@45 7044
bgneal@45 7045 if ( left.substr( left.length - 1 ) === "\\" ) {
bgneal@45 7046 continue;
bgneal@45 7047 }
bgneal@45 7048
bgneal@45 7049 if ( curLoop === result ) {
bgneal@45 7050 result = [];
bgneal@45 7051 }
bgneal@45 7052
bgneal@45 7053 if ( Expr.preFilter[ type ] ) {
bgneal@45 7054 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
bgneal@45 7055
bgneal@45 7056 if ( !match ) {
bgneal@45 7057 anyFound = found = true;
bgneal@45 7058 } else if ( match === true ) {
bgneal@45 7059 continue;
bgneal@45 7060 }
bgneal@45 7061 }
bgneal@45 7062
bgneal@45 7063 if ( match ) {
bgneal@45 7064 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
bgneal@45 7065 if ( item ) {
bgneal@45 7066 found = filter( item, match, i, curLoop );
bgneal@45 7067 var pass = not ^ !!found;
bgneal@45 7068
bgneal@45 7069 if ( inplace && found != null ) {
bgneal@45 7070 if ( pass ) {
bgneal@45 7071 anyFound = true;
bgneal@45 7072 } else {
bgneal@45 7073 curLoop[i] = false;
bgneal@45 7074 }
bgneal@45 7075 } else if ( pass ) {
bgneal@45 7076 result.push( item );
bgneal@45 7077 anyFound = true;
bgneal@45 7078 }
bgneal@45 7079 }
bgneal@45 7080 }
bgneal@45 7081 }
bgneal@45 7082
bgneal@45 7083 if ( found !== undefined ) {
bgneal@45 7084 if ( !inplace ) {
bgneal@45 7085 curLoop = result;
bgneal@45 7086 }
bgneal@45 7087
bgneal@45 7088 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 7089
bgneal@45 7090 if ( !anyFound ) {
bgneal@45 7091 return [];
bgneal@45 7092 }
bgneal@45 7093
bgneal@45 7094 break;
bgneal@45 7095 }
bgneal@45 7096 }
bgneal@45 7097 }
bgneal@45 7098
bgneal@45 7099 // Improper expression
bgneal@45 7100 if ( expr === old ) {
bgneal@45 7101 if ( anyFound == null ) {
bgneal@45 7102 Sizzle.error( expr );
bgneal@45 7103 } else {
bgneal@45 7104 break;
bgneal@45 7105 }
bgneal@45 7106 }
bgneal@45 7107
bgneal@45 7108 old = expr;
bgneal@45 7109 }
bgneal@45 7110
bgneal@45 7111 return curLoop;
bgneal@45 7112 };
bgneal@45 7113
bgneal@45 7114 Sizzle.error = function( msg ) {
bgneal@45 7115 throw "Syntax error, unrecognized expression: " + msg;
bgneal@45 7116 };
bgneal@45 7117
bgneal@45 7118 var Expr = Sizzle.selectors = {
bgneal@45 7119 order: [ "ID", "NAME", "TAG" ],
bgneal@45 7120 match: {
bgneal@45 7121 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@45 7122 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@45 7123 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
bgneal@45 7124 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
bgneal@45 7125 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
bgneal@45 7126 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
bgneal@45 7127 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
bgneal@45 7128 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
bgneal@45 7129 },
bgneal@45 7130 leftMatch: {},
bgneal@45 7131 attrMap: {
bgneal@45 7132 "class": "className",
bgneal@45 7133 "for": "htmlFor"
bgneal@45 7134 },
bgneal@45 7135 attrHandle: {
bgneal@45 7136 href: function(elem){
bgneal@45 7137 return elem.getAttribute("href");
bgneal@45 7138 }
bgneal@45 7139 },
bgneal@45 7140 relative: {
bgneal@45 7141 "+": function(checkSet, part){
bgneal@45 7142 var isPartStr = typeof part === "string",
bgneal@45 7143 isTag = isPartStr && !/\W/.test(part),
bgneal@45 7144 isPartStrNotTag = isPartStr && !isTag;
bgneal@45 7145
bgneal@45 7146 if ( isTag ) {
bgneal@45 7147 part = part.toLowerCase();
bgneal@45 7148 }
bgneal@45 7149
bgneal@45 7150 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
bgneal@45 7151 if ( (elem = checkSet[i]) ) {
bgneal@45 7152 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
bgneal@45 7153
bgneal@45 7154 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
bgneal@45 7155 elem || false :
bgneal@45 7156 elem === part;
bgneal@45 7157 }
bgneal@45 7158 }
bgneal@45 7159
bgneal@45 7160 if ( isPartStrNotTag ) {
bgneal@45 7161 Sizzle.filter( part, checkSet, true );
bgneal@45 7162 }
bgneal@45 7163 },
bgneal@45 7164 ">": function(checkSet, part){
bgneal@45 7165 var isPartStr = typeof part === "string",
bgneal@45 7166 elem, i = 0, l = checkSet.length;
bgneal@45 7167
bgneal@45 7168 if ( isPartStr && !/\W/.test(part) ) {
bgneal@45 7169 part = part.toLowerCase();
bgneal@45 7170
bgneal@45 7171 for ( ; i < l; i++ ) {
bgneal@45 7172 elem = checkSet[i];
bgneal@45 7173 if ( elem ) {
bgneal@45 7174 var parent = elem.parentNode;
bgneal@45 7175 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
bgneal@45 7176 }
bgneal@45 7177 }
bgneal@45 7178 } else {
bgneal@45 7179 for ( ; i < l; i++ ) {
bgneal@45 7180 elem = checkSet[i];
bgneal@45 7181 if ( elem ) {
bgneal@45 7182 checkSet[i] = isPartStr ?
bgneal@45 7183 elem.parentNode :
bgneal@45 7184 elem.parentNode === part;
bgneal@45 7185 }
bgneal@45 7186 }
bgneal@45 7187
bgneal@45 7188 if ( isPartStr ) {
bgneal@45 7189 Sizzle.filter( part, checkSet, true );
bgneal@45 7190 }
bgneal@45 7191 }
bgneal@45 7192 },
bgneal@45 7193 "": function(checkSet, part, isXML){
bgneal@45 7194 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@45 7195
bgneal@45 7196 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@45 7197 part = part.toLowerCase();
bgneal@45 7198 nodeCheck = part;
bgneal@45 7199 checkFn = dirNodeCheck;
bgneal@45 7200 }
bgneal@45 7201
bgneal@45 7202 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 7203 },
bgneal@45 7204 "~": function(checkSet, part, isXML){
bgneal@45 7205 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@45 7206
bgneal@45 7207 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@45 7208 part = part.toLowerCase();
bgneal@45 7209 nodeCheck = part;
bgneal@45 7210 checkFn = dirNodeCheck;
bgneal@45 7211 }
bgneal@45 7212
bgneal@45 7213 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 7214 }
bgneal@45 7215 },
bgneal@45 7216 find: {
bgneal@45 7217 ID: function(match, context, isXML){
bgneal@45 7218 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 7219 var m = context.getElementById(match[1]);
bgneal@45 7220 return m ? [m] : [];
bgneal@45 7221 }
bgneal@45 7222 },
bgneal@45 7223 NAME: function(match, context){
bgneal@45 7224 if ( typeof context.getElementsByName !== "undefined" ) {
bgneal@45 7225 var ret = [], results = context.getElementsByName(match[1]);
bgneal@45 7226
bgneal@45 7227 for ( var i = 0, l = results.length; i < l; i++ ) {
bgneal@45 7228 if ( results[i].getAttribute("name") === match[1] ) {
bgneal@45 7229 ret.push( results[i] );
bgneal@45 7230 }
bgneal@45 7231 }
bgneal@45 7232
bgneal@45 7233 return ret.length === 0 ? null : ret;
bgneal@45 7234 }
bgneal@45 7235 },
bgneal@45 7236 TAG: function(match, context){
bgneal@45 7237 return context.getElementsByTagName(match[1]);
bgneal@45 7238 }
bgneal@45 7239 },
bgneal@45 7240 preFilter: {
bgneal@45 7241 CLASS: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 7242 match = " " + match[1].replace(/\\/g, "") + " ";
bgneal@45 7243
bgneal@45 7244 if ( isXML ) {
bgneal@45 7245 return match;
bgneal@45 7246 }
bgneal@45 7247
bgneal@45 7248 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
bgneal@45 7249 if ( elem ) {
bgneal@45 7250 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
bgneal@45 7251 if ( !inplace ) {
bgneal@45 7252 result.push( elem );
bgneal@45 7253 }
bgneal@45 7254 } else if ( inplace ) {
bgneal@45 7255 curLoop[i] = false;
bgneal@45 7256 }
bgneal@45 7257 }
bgneal@45 7258 }
bgneal@45 7259
bgneal@45 7260 return false;
bgneal@45 7261 },
bgneal@45 7262 ID: function(match){
bgneal@45 7263 return match[1].replace(/\\/g, "");
bgneal@45 7264 },
bgneal@45 7265 TAG: function(match, curLoop){
bgneal@45 7266 return match[1].toLowerCase();
bgneal@45 7267 },
bgneal@45 7268 CHILD: function(match){
bgneal@45 7269 if ( match[1] === "nth" ) {
bgneal@45 7270 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
bgneal@45 7271 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
bgneal@45 7272 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
bgneal@45 7273 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
bgneal@45 7274
bgneal@45 7275 // calculate the numbers (first)n+(last) including if they are negative
bgneal@45 7276 match[2] = (test[1] + (test[2] || 1)) - 0;
bgneal@45 7277 match[3] = test[3] - 0;
bgneal@45 7278 }
bgneal@45 7279
bgneal@45 7280 // TODO: Move to normal caching system
bgneal@45 7281 match[0] = done++;
bgneal@45 7282
bgneal@45 7283 return match;
bgneal@45 7284 },
bgneal@45 7285 ATTR: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 7286 var name = match[1].replace(/\\/g, "");
bgneal@45 7287
bgneal@45 7288 if ( !isXML && Expr.attrMap[name] ) {
bgneal@45 7289 match[1] = Expr.attrMap[name];
bgneal@45 7290 }
bgneal@45 7291
bgneal@45 7292 if ( match[2] === "~=" ) {
bgneal@45 7293 match[4] = " " + match[4] + " ";
bgneal@45 7294 }
bgneal@45 7295
bgneal@45 7296 return match;
bgneal@45 7297 },
bgneal@45 7298 PSEUDO: function(match, curLoop, inplace, result, not){
bgneal@45 7299 if ( match[1] === "not" ) {
bgneal@45 7300 // If we're dealing with a complex expression, or a simple one
bgneal@45 7301 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
bgneal@45 7302 match[3] = Sizzle(match[3], null, null, curLoop);
bgneal@45 7303 } else {
bgneal@45 7304 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
bgneal@45 7305 if ( !inplace ) {
bgneal@45 7306 result.push.apply( result, ret );
bgneal@45 7307 }
bgneal@45 7308 return false;
bgneal@45 7309 }
bgneal@45 7310 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
bgneal@45 7311 return true;
bgneal@45 7312 }
bgneal@45 7313
bgneal@45 7314 return match;
bgneal@45 7315 },
bgneal@45 7316 POS: function(match){
bgneal@45 7317 match.unshift( true );
bgneal@45 7318 return match;
bgneal@45 7319 }
bgneal@45 7320 },
bgneal@45 7321 filters: {
bgneal@45 7322 enabled: function(elem){
bgneal@45 7323 return elem.disabled === false && elem.type !== "hidden";
bgneal@45 7324 },
bgneal@45 7325 disabled: function(elem){
bgneal@45 7326 return elem.disabled === true;
bgneal@45 7327 },
bgneal@45 7328 checked: function(elem){
bgneal@45 7329 return elem.checked === true;
bgneal@45 7330 },
bgneal@45 7331 selected: function(elem){
bgneal@45 7332 // Accessing this property makes selected-by-default
bgneal@45 7333 // options in Safari work properly
bgneal@45 7334 elem.parentNode.selectedIndex;
bgneal@45 7335 return elem.selected === true;
bgneal@45 7336 },
bgneal@45 7337 parent: function(elem){
bgneal@45 7338 return !!elem.firstChild;
bgneal@45 7339 },
bgneal@45 7340 empty: function(elem){
bgneal@45 7341 return !elem.firstChild;
bgneal@45 7342 },
bgneal@45 7343 has: function(elem, i, match){
bgneal@45 7344 return !!Sizzle( match[3], elem ).length;
bgneal@45 7345 },
bgneal@45 7346 header: function(elem){
bgneal@45 7347 return (/h\d/i).test( elem.nodeName );
bgneal@45 7348 },
bgneal@45 7349 text: function(elem){
bgneal@45 7350 return "text" === elem.type;
bgneal@45 7351 },
bgneal@45 7352 radio: function(elem){
bgneal@45 7353 return "radio" === elem.type;
bgneal@45 7354 },
bgneal@45 7355 checkbox: function(elem){
bgneal@45 7356 return "checkbox" === elem.type;
bgneal@45 7357 },
bgneal@45 7358 file: function(elem){
bgneal@45 7359 return "file" === elem.type;
bgneal@45 7360 },
bgneal@45 7361 password: function(elem){
bgneal@45 7362 return "password" === elem.type;
bgneal@45 7363 },
bgneal@45 7364 submit: function(elem){
bgneal@45 7365 return "submit" === elem.type;
bgneal@45 7366 },
bgneal@45 7367 image: function(elem){
bgneal@45 7368 return "image" === elem.type;
bgneal@45 7369 },
bgneal@45 7370 reset: function(elem){
bgneal@45 7371 return "reset" === elem.type;
bgneal@45 7372 },
bgneal@45 7373 button: function(elem){
bgneal@45 7374 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
bgneal@45 7375 },
bgneal@45 7376 input: function(elem){
bgneal@45 7377 return (/input|select|textarea|button/i).test(elem.nodeName);
bgneal@45 7378 }
bgneal@45 7379 },
bgneal@45 7380 setFilters: {
bgneal@45 7381 first: function(elem, i){
bgneal@45 7382 return i === 0;
bgneal@45 7383 },
bgneal@45 7384 last: function(elem, i, match, array){
bgneal@45 7385 return i === array.length - 1;
bgneal@45 7386 },
bgneal@45 7387 even: function(elem, i){
bgneal@45 7388 return i % 2 === 0;
bgneal@45 7389 },
bgneal@45 7390 odd: function(elem, i){
bgneal@45 7391 return i % 2 === 1;
bgneal@45 7392 },
bgneal@45 7393 lt: function(elem, i, match){
bgneal@45 7394 return i < match[3] - 0;
bgneal@45 7395 },
bgneal@45 7396 gt: function(elem, i, match){
bgneal@45 7397 return i > match[3] - 0;
bgneal@45 7398 },
bgneal@45 7399 nth: function(elem, i, match){
bgneal@45 7400 return match[3] - 0 === i;
bgneal@45 7401 },
bgneal@45 7402 eq: function(elem, i, match){
bgneal@45 7403 return match[3] - 0 === i;
bgneal@45 7404 }
bgneal@45 7405 },
bgneal@45 7406 filter: {
bgneal@45 7407 PSEUDO: function(elem, match, i, array){
bgneal@45 7408 var name = match[1], filter = Expr.filters[ name ];
bgneal@45 7409
bgneal@45 7410 if ( filter ) {
bgneal@45 7411 return filter( elem, i, match, array );
bgneal@45 7412 } else if ( name === "contains" ) {
bgneal@45 7413 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
bgneal@45 7414 } else if ( name === "not" ) {
bgneal@45 7415 var not = match[3];
bgneal@45 7416
bgneal@45 7417 for ( var j = 0, l = not.length; j < l; j++ ) {
bgneal@45 7418 if ( not[j] === elem ) {
bgneal@45 7419 return false;
bgneal@45 7420 }
bgneal@45 7421 }
bgneal@45 7422
bgneal@45 7423 return true;
bgneal@45 7424 } else {
bgneal@45 7425 Sizzle.error( "Syntax error, unrecognized expression: " + name );
bgneal@45 7426 }
bgneal@45 7427 },
bgneal@45 7428 CHILD: function(elem, match){
bgneal@45 7429 var type = match[1], node = elem;
bgneal@45 7430 switch (type) {
bgneal@45 7431 case 'only':
bgneal@45 7432 case 'first':
bgneal@45 7433 while ( (node = node.previousSibling) ) {
bgneal@45 7434 if ( node.nodeType === 1 ) {
bgneal@45 7435 return false;
bgneal@45 7436 }
bgneal@45 7437 }
bgneal@45 7438 if ( type === "first" ) {
bgneal@45 7439 return true;
bgneal@45 7440 }
bgneal@45 7441 node = elem;
bgneal@45 7442 case 'last':
bgneal@45 7443 while ( (node = node.nextSibling) ) {
bgneal@45 7444 if ( node.nodeType === 1 ) {
bgneal@45 7445 return false;
bgneal@45 7446 }
bgneal@45 7447 }
bgneal@45 7448 return true;
bgneal@45 7449 case 'nth':
bgneal@45 7450 var first = match[2], last = match[3];
bgneal@45 7451
bgneal@45 7452 if ( first === 1 && last === 0 ) {
bgneal@45 7453 return true;
bgneal@45 7454 }
bgneal@45 7455
bgneal@45 7456 var doneName = match[0],
bgneal@45 7457 parent = elem.parentNode;
bgneal@45 7458
bgneal@45 7459 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
bgneal@45 7460 var count = 0;
bgneal@45 7461 for ( node = parent.firstChild; node; node = node.nextSibling ) {
bgneal@45 7462 if ( node.nodeType === 1 ) {
bgneal@45 7463 node.nodeIndex = ++count;
bgneal@45 7464 }
bgneal@45 7465 }
bgneal@45 7466 parent.sizcache = doneName;
bgneal@45 7467 }
bgneal@45 7468
bgneal@45 7469 var diff = elem.nodeIndex - last;
bgneal@45 7470 if ( first === 0 ) {
bgneal@45 7471 return diff === 0;
bgneal@45 7472 } else {
bgneal@45 7473 return ( diff % first === 0 && diff / first >= 0 );
bgneal@45 7474 }
bgneal@45 7475 }
bgneal@45 7476 },
bgneal@45 7477 ID: function(elem, match){
bgneal@45 7478 return elem.nodeType === 1 && elem.getAttribute("id") === match;
bgneal@45 7479 },
bgneal@45 7480 TAG: function(elem, match){
bgneal@45 7481 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
bgneal@45 7482 },
bgneal@45 7483 CLASS: function(elem, match){
bgneal@45 7484 return (" " + (elem.className || elem.getAttribute("class")) + " ")
bgneal@45 7485 .indexOf( match ) > -1;
bgneal@45 7486 },
bgneal@45 7487 ATTR: function(elem, match){
bgneal@45 7488 var name = match[1],
bgneal@45 7489 result = Expr.attrHandle[ name ] ?
bgneal@45 7490 Expr.attrHandle[ name ]( elem ) :
bgneal@45 7491 elem[ name ] != null ?
bgneal@45 7492 elem[ name ] :
bgneal@45 7493 elem.getAttribute( name ),
bgneal@45 7494 value = result + "",
bgneal@45 7495 type = match[2],
bgneal@45 7496 check = match[4];
bgneal@45 7497
bgneal@45 7498 return result == null ?
bgneal@45 7499 type === "!=" :
bgneal@45 7500 type === "=" ?
bgneal@45 7501 value === check :
bgneal@45 7502 type === "*=" ?
bgneal@45 7503 value.indexOf(check) >= 0 :
bgneal@45 7504 type === "~=" ?
bgneal@45 7505 (" " + value + " ").indexOf(check) >= 0 :
bgneal@45 7506 !check ?
bgneal@45 7507 value && result !== false :
bgneal@45 7508 type === "!=" ?
bgneal@45 7509 value !== check :
bgneal@45 7510 type === "^=" ?
bgneal@45 7511 value.indexOf(check) === 0 :
bgneal@45 7512 type === "$=" ?
bgneal@45 7513 value.substr(value.length - check.length) === check :
bgneal@45 7514 type === "|=" ?
bgneal@45 7515 value === check || value.substr(0, check.length + 1) === check + "-" :
bgneal@45 7516 false;
bgneal@45 7517 },
bgneal@45 7518 POS: function(elem, match, i, array){
bgneal@45 7519 var name = match[2], filter = Expr.setFilters[ name ];
bgneal@45 7520
bgneal@45 7521 if ( filter ) {
bgneal@45 7522 return filter( elem, i, match, array );
bgneal@45 7523 }
bgneal@45 7524 }
bgneal@45 7525 }
bgneal@45 7526 };
bgneal@45 7527
bgneal@45 7528 var origPOS = Expr.match.POS,
bgneal@45 7529 fescape = function(all, num){
bgneal@45 7530 return "\\" + (num - 0 + 1);
bgneal@45 7531 };
bgneal@45 7532
bgneal@45 7533 for ( var type in Expr.match ) {
bgneal@45 7534 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
bgneal@45 7535 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
bgneal@45 7536 }
bgneal@45 7537
bgneal@45 7538 var makeArray = function(array, results) {
bgneal@45 7539 array = Array.prototype.slice.call( array, 0 );
bgneal@45 7540
bgneal@45 7541 if ( results ) {
bgneal@45 7542 results.push.apply( results, array );
bgneal@45 7543 return results;
bgneal@45 7544 }
bgneal@45 7545
bgneal@45 7546 return array;
bgneal@45 7547 };
bgneal@45 7548
bgneal@45 7549 // Perform a simple check to determine if the browser is capable of
bgneal@45 7550 // converting a NodeList to an array using builtin methods.
bgneal@45 7551 // Also verifies that the returned array holds DOM nodes
bgneal@45 7552 // (which is not the case in the Blackberry browser)
bgneal@45 7553 try {
bgneal@45 7554 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
bgneal@45 7555
bgneal@45 7556 // Provide a fallback method if it does not work
bgneal@45 7557 } catch(e){
bgneal@45 7558 makeArray = function(array, results) {
bgneal@45 7559 var ret = results || [], i = 0;
bgneal@45 7560
bgneal@45 7561 if ( toString.call(array) === "[object Array]" ) {
bgneal@45 7562 Array.prototype.push.apply( ret, array );
bgneal@45 7563 } else {
bgneal@45 7564 if ( typeof array.length === "number" ) {
bgneal@45 7565 for ( var l = array.length; i < l; i++ ) {
bgneal@45 7566 ret.push( array[i] );
bgneal@45 7567 }
bgneal@45 7568 } else {
bgneal@45 7569 for ( ; array[i]; i++ ) {
bgneal@45 7570 ret.push( array[i] );
bgneal@45 7571 }
bgneal@45 7572 }
bgneal@45 7573 }
bgneal@45 7574
bgneal@45 7575 return ret;
bgneal@45 7576 };
bgneal@45 7577 }
bgneal@45 7578
bgneal@45 7579 var sortOrder;
bgneal@45 7580
bgneal@45 7581 if ( document.documentElement.compareDocumentPosition ) {
bgneal@45 7582 sortOrder = function( a, b ) {
bgneal@45 7583 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
bgneal@45 7584 if ( a == b ) {
bgneal@45 7585 hasDuplicate = true;
bgneal@45 7586 }
bgneal@45 7587 return a.compareDocumentPosition ? -1 : 1;
bgneal@45 7588 }
bgneal@45 7589
bgneal@45 7590 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
bgneal@45 7591 if ( ret === 0 ) {
bgneal@45 7592 hasDuplicate = true;
bgneal@45 7593 }
bgneal@45 7594 return ret;
bgneal@45 7595 };
bgneal@45 7596 } else if ( "sourceIndex" in document.documentElement ) {
bgneal@45 7597 sortOrder = function( a, b ) {
bgneal@45 7598 if ( !a.sourceIndex || !b.sourceIndex ) {
bgneal@45 7599 if ( a == b ) {
bgneal@45 7600 hasDuplicate = true;
bgneal@45 7601 }
bgneal@45 7602 return a.sourceIndex ? -1 : 1;
bgneal@45 7603 }
bgneal@45 7604
bgneal@45 7605 var ret = a.sourceIndex - b.sourceIndex;
bgneal@45 7606 if ( ret === 0 ) {
bgneal@45 7607 hasDuplicate = true;
bgneal@45 7608 }
bgneal@45 7609 return ret;
bgneal@45 7610 };
bgneal@45 7611 } else if ( document.createRange ) {
bgneal@45 7612 sortOrder = function( a, b ) {
bgneal@45 7613 if ( !a.ownerDocument || !b.ownerDocument ) {
bgneal@45 7614 if ( a == b ) {
bgneal@45 7615 hasDuplicate = true;
bgneal@45 7616 }
bgneal@45 7617 return a.ownerDocument ? -1 : 1;
bgneal@45 7618 }
bgneal@45 7619
bgneal@45 7620 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
bgneal@45 7621 aRange.setStart(a, 0);
bgneal@45 7622 aRange.setEnd(a, 0);
bgneal@45 7623 bRange.setStart(b, 0);
bgneal@45 7624 bRange.setEnd(b, 0);
bgneal@45 7625 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
bgneal@45 7626 if ( ret === 0 ) {
bgneal@45 7627 hasDuplicate = true;
bgneal@45 7628 }
bgneal@45 7629 return ret;
bgneal@45 7630 };
bgneal@45 7631 }
bgneal@45 7632
bgneal@45 7633 // Utility function for retreiving the text value of an array of DOM nodes
bgneal@45 7634 Sizzle.getText = function( elems ) {
bgneal@45 7635 var ret = "", elem;
bgneal@45 7636
bgneal@45 7637 for ( var i = 0; elems[i]; i++ ) {
bgneal@45 7638 elem = elems[i];
bgneal@45 7639
bgneal@45 7640 // Get the text from text nodes and CDATA nodes
bgneal@45 7641 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
bgneal@45 7642 ret += elem.nodeValue;
bgneal@45 7643
bgneal@45 7644 // Traverse everything else, except comment nodes
bgneal@45 7645 } else if ( elem.nodeType !== 8 ) {
bgneal@45 7646 ret += Sizzle.getText( elem.childNodes );
bgneal@45 7647 }
bgneal@45 7648 }
bgneal@45 7649
bgneal@45 7650 return ret;
bgneal@45 7651 };
bgneal@45 7652
bgneal@45 7653 // Check to see if the browser returns elements by name when
bgneal@45 7654 // querying by getElementById (and provide a workaround)
bgneal@45 7655 (function(){
bgneal@45 7656 // We're going to inject a fake input element with a specified name
bgneal@45 7657 var form = document.createElement("div"),
bgneal@45 7658 id = "script" + (new Date()).getTime();
bgneal@45 7659 form.innerHTML = "<a name='" + id + "'/>";
bgneal@45 7660
bgneal@45 7661 // Inject it into the root element, check its status, and remove it quickly
bgneal@45 7662 var root = document.documentElement;
bgneal@45 7663 root.insertBefore( form, root.firstChild );
bgneal@45 7664
bgneal@45 7665 // The workaround has to do additional checks after a getElementById
bgneal@45 7666 // Which slows things down for other browsers (hence the branching)
bgneal@45 7667 if ( document.getElementById( id ) ) {
bgneal@45 7668 Expr.find.ID = function(match, context, isXML){
bgneal@45 7669 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 7670 var m = context.getElementById(match[1]);
bgneal@45 7671 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
bgneal@45 7672 }
bgneal@45 7673 };
bgneal@45 7674
bgneal@45 7675 Expr.filter.ID = function(elem, match){
bgneal@45 7676 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
bgneal@45 7677 return elem.nodeType === 1 && node && node.nodeValue === match;
bgneal@45 7678 };
bgneal@45 7679 }
bgneal@45 7680
bgneal@45 7681 root.removeChild( form );
bgneal@45 7682 root = form = null; // release memory in IE
bgneal@45 7683 })();
bgneal@45 7684
bgneal@45 7685 (function(){
bgneal@45 7686 // Check to see if the browser returns only elements
bgneal@45 7687 // when doing getElementsByTagName("*")
bgneal@45 7688
bgneal@45 7689 // Create a fake element
bgneal@45 7690 var div = document.createElement("div");
bgneal@45 7691 div.appendChild( document.createComment("") );
bgneal@45 7692
bgneal@45 7693 // Make sure no comments are found
bgneal@45 7694 if ( div.getElementsByTagName("*").length > 0 ) {
bgneal@45 7695 Expr.find.TAG = function(match, context){
bgneal@45 7696 var results = context.getElementsByTagName(match[1]);
bgneal@45 7697
bgneal@45 7698 // Filter out possible comments
bgneal@45 7699 if ( match[1] === "*" ) {
bgneal@45 7700 var tmp = [];
bgneal@45 7701
bgneal@45 7702 for ( var i = 0; results[i]; i++ ) {
bgneal@45 7703 if ( results[i].nodeType === 1 ) {
bgneal@45 7704 tmp.push( results[i] );
bgneal@45 7705 }
bgneal@45 7706 }
bgneal@45 7707
bgneal@45 7708 results = tmp;
bgneal@45 7709 }
bgneal@45 7710
bgneal@45 7711 return results;
bgneal@45 7712 };
bgneal@45 7713 }
bgneal@45 7714
bgneal@45 7715 // Check to see if an attribute returns normalized href attributes
bgneal@45 7716 div.innerHTML = "<a href='#'></a>";
bgneal@45 7717 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
bgneal@45 7718 div.firstChild.getAttribute("href") !== "#" ) {
bgneal@45 7719 Expr.attrHandle.href = function(elem){
bgneal@45 7720 return elem.getAttribute("href", 2);
bgneal@45 7721 };
bgneal@45 7722 }
bgneal@45 7723
bgneal@45 7724 div = null; // release memory in IE
bgneal@45 7725 })();
bgneal@45 7726
bgneal@45 7727 if ( document.querySelectorAll ) {
bgneal@45 7728 (function(){
bgneal@45 7729 var oldSizzle = Sizzle, div = document.createElement("div");
bgneal@45 7730 div.innerHTML = "<p class='TEST'></p>";
bgneal@45 7731
bgneal@45 7732 // Safari can't handle uppercase or unicode characters when
bgneal@45 7733 // in quirks mode.
bgneal@45 7734 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
bgneal@45 7735 return;
bgneal@45 7736 }
bgneal@45 7737
bgneal@45 7738 Sizzle = function(query, context, extra, seed){
bgneal@45 7739 context = context || document;
bgneal@45 7740
bgneal@45 7741 // Only use querySelectorAll on non-XML documents
bgneal@45 7742 // (ID selectors don't work in non-HTML documents)
bgneal@45 7743 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
bgneal@45 7744 try {
bgneal@45 7745 return makeArray( context.querySelectorAll(query), extra );
bgneal@45 7746 } catch(e){}
bgneal@45 7747 }
bgneal@45 7748
bgneal@45 7749 return oldSizzle(query, context, extra, seed);
bgneal@45 7750 };
bgneal@45 7751
bgneal@45 7752 for ( var prop in oldSizzle ) {
bgneal@45 7753 Sizzle[ prop ] = oldSizzle[ prop ];
bgneal@45 7754 }
bgneal@45 7755
bgneal@45 7756 div = null; // release memory in IE
bgneal@45 7757 })();
bgneal@45 7758 }
bgneal@45 7759
bgneal@45 7760 (function(){
bgneal@45 7761 var div = document.createElement("div");
bgneal@45 7762
bgneal@45 7763 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
bgneal@45 7764
bgneal@45 7765 // Opera can't find a second classname (in 9.6)
bgneal@45 7766 // Also, make sure that getElementsByClassName actually exists
bgneal@45 7767 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
bgneal@45 7768 return;
bgneal@45 7769 }
bgneal@45 7770
bgneal@45 7771 // Safari caches class attributes, doesn't catch changes (in 3.2)
bgneal@45 7772 div.lastChild.className = "e";
bgneal@45 7773
bgneal@45 7774 if ( div.getElementsByClassName("e").length === 1 ) {
bgneal@45 7775 return;
bgneal@45 7776 }
bgneal@45 7777
bgneal@45 7778 Expr.order.splice(1, 0, "CLASS");
bgneal@45 7779 Expr.find.CLASS = function(match, context, isXML) {
bgneal@45 7780 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
bgneal@45 7781 return context.getElementsByClassName(match[1]);
bgneal@45 7782 }
bgneal@45 7783 };
bgneal@45 7784
bgneal@45 7785 div = null; // release memory in IE
bgneal@45 7786 })();
bgneal@45 7787
bgneal@45 7788 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 7789 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 7790 var elem = checkSet[i];
bgneal@45 7791 if ( elem ) {
bgneal@45 7792 elem = elem[dir];
bgneal@45 7793 var match = false;
bgneal@45 7794
bgneal@45 7795 while ( elem ) {
bgneal@45 7796 if ( elem.sizcache === doneName ) {
bgneal@45 7797 match = checkSet[elem.sizset];
bgneal@45 7798 break;
bgneal@45 7799 }
bgneal@45 7800
bgneal@45 7801 if ( elem.nodeType === 1 && !isXML ){
bgneal@45 7802 elem.sizcache = doneName;
bgneal@45 7803 elem.sizset = i;
bgneal@45 7804 }
bgneal@45 7805
bgneal@45 7806 if ( elem.nodeName.toLowerCase() === cur ) {
bgneal@45 7807 match = elem;
bgneal@45 7808 break;
bgneal@45 7809 }
bgneal@45 7810
bgneal@45 7811 elem = elem[dir];
bgneal@45 7812 }
bgneal@45 7813
bgneal@45 7814 checkSet[i] = match;
bgneal@45 7815 }
bgneal@45 7816 }
bgneal@45 7817 }
bgneal@45 7818
bgneal@45 7819 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 7820 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 7821 var elem = checkSet[i];
bgneal@45 7822 if ( elem ) {
bgneal@45 7823 elem = elem[dir];
bgneal@45 7824 var match = false;
bgneal@45 7825
bgneal@45 7826 while ( elem ) {
bgneal@45 7827 if ( elem.sizcache === doneName ) {
bgneal@45 7828 match = checkSet[elem.sizset];
bgneal@45 7829 break;
bgneal@45 7830 }
bgneal@45 7831
bgneal@45 7832 if ( elem.nodeType === 1 ) {
bgneal@45 7833 if ( !isXML ) {
bgneal@45 7834 elem.sizcache = doneName;
bgneal@45 7835 elem.sizset = i;
bgneal@45 7836 }
bgneal@45 7837 if ( typeof cur !== "string" ) {
bgneal@45 7838 if ( elem === cur ) {
bgneal@45 7839 match = true;
bgneal@45 7840 break;
bgneal@45 7841 }
bgneal@45 7842
bgneal@45 7843 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
bgneal@45 7844 match = elem;
bgneal@45 7845 break;
bgneal@45 7846 }
bgneal@45 7847 }
bgneal@45 7848
bgneal@45 7849 elem = elem[dir];
bgneal@45 7850 }
bgneal@45 7851
bgneal@45 7852 checkSet[i] = match;
bgneal@45 7853 }
bgneal@45 7854 }
bgneal@45 7855 }
bgneal@45 7856
bgneal@45 7857 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
bgneal@45 7858 return !!(a.compareDocumentPosition(b) & 16);
bgneal@45 7859 } : function(a, b){
bgneal@45 7860 return a !== b && (a.contains ? a.contains(b) : true);
bgneal@45 7861 };
bgneal@45 7862
bgneal@45 7863 Sizzle.isXML = function(elem){
bgneal@45 7864 // documentElement is verified for cases where it doesn't yet exist
bgneal@45 7865 // (such as loading iframes in IE - #4833)
bgneal@45 7866 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
bgneal@45 7867 return documentElement ? documentElement.nodeName !== "HTML" : false;
bgneal@45 7868 };
bgneal@45 7869
bgneal@45 7870 var posProcess = function(selector, context){
bgneal@45 7871 var tmpSet = [], later = "", match,
bgneal@45 7872 root = context.nodeType ? [context] : context;
bgneal@45 7873
bgneal@45 7874 // Position selectors must be done after the filter
bgneal@45 7875 // And so must :not(positional) so we move all PSEUDOs to the end
bgneal@45 7876 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
bgneal@45 7877 later += match[0];
bgneal@45 7878 selector = selector.replace( Expr.match.PSEUDO, "" );
bgneal@45 7879 }
bgneal@45 7880
bgneal@45 7881 selector = Expr.relative[selector] ? selector + "*" : selector;
bgneal@45 7882
bgneal@45 7883 for ( var i = 0, l = root.length; i < l; i++ ) {
bgneal@45 7884 Sizzle( selector, root[i], tmpSet );
bgneal@45 7885 }
bgneal@45 7886
bgneal@45 7887 return Sizzle.filter( later, tmpSet );
bgneal@45 7888 };
bgneal@45 7889
bgneal@45 7890 // EXPOSE
bgneal@45 7891
bgneal@45 7892 window.tinymce.dom.Sizzle = Sizzle;
bgneal@45 7893
bgneal@45 7894 })();
bgneal@45 7895
bgneal@45 7896
bgneal@45 7897 (function(tinymce) {
bgneal@45 7898 tinymce.dom.Element = function(id, settings) {
bgneal@45 7899 var t = this, dom, el;
bgneal@45 7900
bgneal@45 7901 t.settings = settings = settings || {};
bgneal@45 7902 t.id = id;
bgneal@45 7903 t.dom = dom = settings.dom || tinymce.DOM;
bgneal@45 7904
bgneal@45 7905 // Only IE leaks DOM references, this is a lot faster
bgneal@45 7906 if (!tinymce.isIE)
bgneal@45 7907 el = dom.get(t.id);
bgneal@45 7908
bgneal@45 7909 tinymce.each(
bgneal@45 7910 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
bgneal@45 7911 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
bgneal@45 7912 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
bgneal@45 7913 'isHidden,setHTML,get').split(/,/)
bgneal@45 7914 , function(k) {
bgneal@45 7915 t[k] = function() {
bgneal@45 7916 var a = [id], i;
bgneal@45 7917
bgneal@45 7918 for (i = 0; i < arguments.length; i++)
bgneal@45 7919 a.push(arguments[i]);
bgneal@45 7920
bgneal@45 7921 a = dom[k].apply(dom, a);
bgneal@45 7922 t.update(k);
bgneal@45 7923
bgneal@45 7924 return a;
bgneal@45 7925 };
bgneal@45 7926 });
bgneal@45 7927
bgneal@45 7928 tinymce.extend(t, {
bgneal@45 7929 on : function(n, f, s) {
bgneal@45 7930 return tinymce.dom.Event.add(t.id, n, f, s);
bgneal@45 7931 },
bgneal@45 7932
bgneal@45 7933 getXY : function() {
bgneal@45 7934 return {
bgneal@45 7935 x : parseInt(t.getStyle('left')),
bgneal@45 7936 y : parseInt(t.getStyle('top'))
bgneal@45 7937 };
bgneal@45 7938 },
bgneal@45 7939
bgneal@45 7940 getSize : function() {
bgneal@45 7941 var n = dom.get(t.id);
bgneal@45 7942
bgneal@45 7943 return {
bgneal@45 7944 w : parseInt(t.getStyle('width') || n.clientWidth),
bgneal@45 7945 h : parseInt(t.getStyle('height') || n.clientHeight)
bgneal@45 7946 };
bgneal@45 7947 },
bgneal@45 7948
bgneal@45 7949 moveTo : function(x, y) {
bgneal@45 7950 t.setStyles({left : x, top : y});
bgneal@45 7951 },
bgneal@45 7952
bgneal@45 7953 moveBy : function(x, y) {
bgneal@45 7954 var p = t.getXY();
bgneal@45 7955
bgneal@45 7956 t.moveTo(p.x + x, p.y + y);
bgneal@45 7957 },
bgneal@45 7958
bgneal@45 7959 resizeTo : function(w, h) {
bgneal@45 7960 t.setStyles({width : w, height : h});
bgneal@45 7961 },
bgneal@45 7962
bgneal@45 7963 resizeBy : function(w, h) {
bgneal@45 7964 var s = t.getSize();
bgneal@45 7965
bgneal@45 7966 t.resizeTo(s.w + w, s.h + h);
bgneal@45 7967 },
bgneal@45 7968
bgneal@45 7969 update : function(k) {
bgneal@45 7970 var b;
bgneal@45 7971
bgneal@45 7972 if (tinymce.isIE6 && settings.blocker) {
bgneal@45 7973 k = k || '';
bgneal@45 7974
bgneal@45 7975 // Ignore getters
bgneal@45 7976 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
bgneal@45 7977 return;
bgneal@45 7978
bgneal@45 7979 // Remove blocker on remove
bgneal@45 7980 if (k == 'remove') {
bgneal@45 7981 dom.remove(t.blocker);
bgneal@45 7982 return;
bgneal@45 7983 }
bgneal@45 7984
bgneal@45 7985 if (!t.blocker) {
bgneal@45 7986 t.blocker = dom.uniqueId();
bgneal@45 7987 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
bgneal@45 7988 dom.setStyle(b, 'opacity', 0);
bgneal@45 7989 } else
bgneal@45 7990 b = dom.get(t.blocker);
bgneal@45 7991
bgneal@45 7992 dom.setStyles(b, {
bgneal@45 7993 left : t.getStyle('left', 1),
bgneal@45 7994 top : t.getStyle('top', 1),
bgneal@45 7995 width : t.getStyle('width', 1),
bgneal@45 7996 height : t.getStyle('height', 1),
bgneal@45 7997 display : t.getStyle('display', 1),
bgneal@45 7998 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
bgneal@45 7999 });
bgneal@45 8000 }
bgneal@45 8001 }
bgneal@45 8002 });
bgneal@45 8003 };
bgneal@45 8004 })(tinymce);
bgneal@45 8005
bgneal@45 8006 (function(tinymce) {
bgneal@45 8007 function trimNl(s) {
bgneal@45 8008 return s.replace(/[\n\r]+/g, '');
bgneal@45 8009 };
bgneal@45 8010
bgneal@45 8011 // Shorten names
bgneal@45 8012 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
bgneal@45 8013
bgneal@45 8014 tinymce.create('tinymce.dom.Selection', {
bgneal@45 8015 Selection : function(dom, win, serializer) {
bgneal@45 8016 var t = this;
bgneal@45 8017
bgneal@45 8018 t.dom = dom;
bgneal@45 8019 t.win = win;
bgneal@45 8020 t.serializer = serializer;
bgneal@45 8021
bgneal@45 8022 // Add events
bgneal@45 8023 each([
bgneal@45 8024 'onBeforeSetContent',
bgneal@45 8025
bgneal@45 8026 'onBeforeGetContent',
bgneal@45 8027
bgneal@45 8028 'onSetContent',
bgneal@45 8029
bgneal@45 8030 'onGetContent'
bgneal@45 8031 ], function(e) {
bgneal@45 8032 t[e] = new tinymce.util.Dispatcher(t);
bgneal@45 8033 });
bgneal@45 8034
bgneal@45 8035 // No W3C Range support
bgneal@45 8036 if (!t.win.getSelection)
bgneal@45 8037 t.tridentSel = new tinymce.dom.TridentSelection(t);
bgneal@45 8038
bgneal@45 8039 if (tinymce.isIE && dom.boxModel)
bgneal@45 8040 this._fixIESelection();
bgneal@45 8041
bgneal@45 8042 // Prevent leaks
bgneal@45 8043 tinymce.addUnload(t.destroy, t);
bgneal@45 8044 },
bgneal@45 8045
bgneal@45 8046 setCursorLocation: function(node, offset) {
bgneal@45 8047 var t = this; var r = t.dom.createRng();
bgneal@45 8048 r.setStart(node, offset);
bgneal@45 8049 r.setEnd(node, offset);
bgneal@45 8050 t.setRng(r);
bgneal@45 8051 t.collapse(false);
bgneal@45 8052 },
bgneal@45 8053 getContent : function(s) {
bgneal@45 8054 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
bgneal@45 8055
bgneal@45 8056 s = s || {};
bgneal@45 8057 wb = wa = '';
bgneal@45 8058 s.get = true;
bgneal@45 8059 s.format = s.format || 'html';
bgneal@45 8060 s.forced_root_block = '';
bgneal@45 8061 t.onBeforeGetContent.dispatch(t, s);
bgneal@45 8062
bgneal@45 8063 if (s.format == 'text')
bgneal@45 8064 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
bgneal@45 8065
bgneal@45 8066 if (r.cloneContents) {
bgneal@45 8067 n = r.cloneContents();
bgneal@45 8068
bgneal@45 8069 if (n)
bgneal@45 8070 e.appendChild(n);
bgneal@45 8071 } else if (is(r.item) || is(r.htmlText)) {
bgneal@45 8072 // IE will produce invalid markup if elements are present that
bgneal@45 8073 // it doesn't understand like custom elements or HTML5 elements.
bgneal@45 8074 // Adding a BR in front of the contents and then remoiving it seems to fix it though.
bgneal@45 8075 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
bgneal@45 8076 e.removeChild(e.firstChild);
bgneal@45 8077 } else
bgneal@45 8078 e.innerHTML = r.toString();
bgneal@45 8079
bgneal@45 8080 // Keep whitespace before and after
bgneal@45 8081 if (/^\s/.test(e.innerHTML))
bgneal@45 8082 wb = ' ';
bgneal@45 8083
bgneal@45 8084 if (/\s+$/.test(e.innerHTML))
bgneal@45 8085 wa = ' ';
bgneal@45 8086
bgneal@45 8087 s.getInner = true;
bgneal@45 8088
bgneal@45 8089 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
bgneal@45 8090 t.onGetContent.dispatch(t, s);
bgneal@45 8091
bgneal@45 8092 return s.content;
bgneal@45 8093 },
bgneal@45 8094
bgneal@45 8095 setContent : function(content, args) {
bgneal@45 8096 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
bgneal@45 8097
bgneal@45 8098 args = args || {format : 'html'};
bgneal@45 8099 args.set = true;
bgneal@45 8100 content = args.content = content;
bgneal@45 8101
bgneal@45 8102 // Dispatch before set content event
bgneal@45 8103 if (!args.no_events)
bgneal@45 8104 self.onBeforeSetContent.dispatch(self, args);
bgneal@45 8105
bgneal@45 8106 content = args.content;
bgneal@45 8107
bgneal@45 8108 if (rng.insertNode) {
bgneal@45 8109 // Make caret marker since insertNode places the caret in the beginning of text after insert
bgneal@45 8110 content += '<span id="__caret">_</span>';
bgneal@45 8111
bgneal@45 8112 // Delete and insert new node
bgneal@45 8113 if (rng.startContainer == doc && rng.endContainer == doc) {
bgneal@45 8114 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
bgneal@45 8115 doc.body.innerHTML = content;
bgneal@45 8116 } else {
bgneal@45 8117 rng.deleteContents();
bgneal@45 8118
bgneal@45 8119 if (doc.body.childNodes.length == 0) {
bgneal@45 8120 doc.body.innerHTML = content;
bgneal@45 8121 } else {
bgneal@45 8122 // createContextualFragment doesn't exists in IE 9 DOMRanges
bgneal@45 8123 if (rng.createContextualFragment) {
bgneal@45 8124 rng.insertNode(rng.createContextualFragment(content));
bgneal@45 8125 } else {
bgneal@45 8126 // Fake createContextualFragment call in IE 9
bgneal@45 8127 frag = doc.createDocumentFragment();
bgneal@45 8128 temp = doc.createElement('div');
bgneal@45 8129
bgneal@45 8130 frag.appendChild(temp);
bgneal@45 8131 temp.outerHTML = content;
bgneal@45 8132
bgneal@45 8133 rng.insertNode(frag);
bgneal@45 8134 }
bgneal@45 8135 }
bgneal@45 8136 }
bgneal@45 8137
bgneal@45 8138 // Move to caret marker
bgneal@45 8139 caretNode = self.dom.get('__caret');
bgneal@45 8140
bgneal@45 8141 // Make sure we wrap it compleatly, Opera fails with a simple select call
bgneal@45 8142 rng = doc.createRange();
bgneal@45 8143 rng.setStartBefore(caretNode);
bgneal@45 8144 rng.setEndBefore(caretNode);
bgneal@45 8145 self.setRng(rng);
bgneal@45 8146
bgneal@45 8147 // Remove the caret position
bgneal@45 8148 self.dom.remove('__caret');
bgneal@45 8149
bgneal@45 8150 try {
bgneal@45 8151 self.setRng(rng);
bgneal@45 8152 } catch (ex) {
bgneal@45 8153 // Might fail on Opera for some odd reason
bgneal@45 8154 }
bgneal@45 8155 } else {
bgneal@45 8156 if (rng.item) {
bgneal@45 8157 // Delete content and get caret text selection
bgneal@45 8158 doc.execCommand('Delete', false, null);
bgneal@45 8159 rng = self.getRng();
bgneal@45 8160 }
bgneal@45 8161
bgneal@45 8162 // Explorer removes spaces from the beginning of pasted contents
bgneal@45 8163 if (/^\s+/.test(content)) {
bgneal@45 8164 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
bgneal@45 8165 self.dom.remove('__mce_tmp');
bgneal@45 8166 } else
bgneal@45 8167 rng.pasteHTML(content);
bgneal@45 8168 }
bgneal@45 8169
bgneal@45 8170 // Dispatch set content event
bgneal@45 8171 if (!args.no_events)
bgneal@45 8172 self.onSetContent.dispatch(self, args);
bgneal@45 8173 },
bgneal@45 8174
bgneal@45 8175 getStart : function() {
bgneal@45 8176 var rng = this.getRng(), startElement, parentElement, checkRng, node;
bgneal@45 8177
bgneal@45 8178 if (rng.duplicate || rng.item) {
bgneal@45 8179 // Control selection, return first item
bgneal@45 8180 if (rng.item)
bgneal@45 8181 return rng.item(0);
bgneal@45 8182
bgneal@45 8183 // Get start element
bgneal@45 8184 checkRng = rng.duplicate();
bgneal@45 8185 checkRng.collapse(1);
bgneal@45 8186 startElement = checkRng.parentElement();
bgneal@45 8187
bgneal@45 8188 // Check if range parent is inside the start element, then return the inner parent element
bgneal@45 8189 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
bgneal@45 8190 parentElement = node = rng.parentElement();
bgneal@45 8191 while (node = node.parentNode) {
bgneal@45 8192 if (node == startElement) {
bgneal@45 8193 startElement = parentElement;
bgneal@45 8194 break;
bgneal@45 8195 }
bgneal@45 8196 }
bgneal@45 8197
bgneal@45 8198 return startElement;
bgneal@45 8199 } else {
bgneal@45 8200 startElement = rng.startContainer;
bgneal@45 8201
bgneal@45 8202 if (startElement.nodeType == 1 && startElement.hasChildNodes())
bgneal@45 8203 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
bgneal@45 8204
bgneal@45 8205 if (startElement && startElement.nodeType == 3)
bgneal@45 8206 return startElement.parentNode;
bgneal@45 8207
bgneal@45 8208 return startElement;
bgneal@45 8209 }
bgneal@45 8210 },
bgneal@45 8211
bgneal@45 8212 getEnd : function() {
bgneal@45 8213 var t = this, r = t.getRng(), e, eo;
bgneal@45 8214
bgneal@45 8215 if (r.duplicate || r.item) {
bgneal@45 8216 if (r.item)
bgneal@45 8217 return r.item(0);
bgneal@45 8218
bgneal@45 8219 r = r.duplicate();
bgneal@45 8220 r.collapse(0);
bgneal@45 8221 e = r.parentElement();
bgneal@45 8222
bgneal@45 8223 if (e && e.nodeName == 'BODY')
bgneal@45 8224 return e.lastChild || e;
bgneal@45 8225
bgneal@45 8226 return e;
bgneal@45 8227 } else {
bgneal@45 8228 e = r.endContainer;
bgneal@45 8229 eo = r.endOffset;
bgneal@45 8230
bgneal@45 8231 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@45 8232 e = e.childNodes[eo > 0 ? eo - 1 : eo];
bgneal@45 8233
bgneal@45 8234 if (e && e.nodeType == 3)
bgneal@45 8235 return e.parentNode;
bgneal@45 8236
bgneal@45 8237 return e;
bgneal@45 8238 }
bgneal@45 8239 },
bgneal@45 8240
bgneal@45 8241 getBookmark : function(type, normalized) {
bgneal@45 8242 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
bgneal@45 8243
bgneal@45 8244 function findIndex(name, element) {
bgneal@45 8245 var index = 0;
bgneal@45 8246
bgneal@45 8247 each(dom.select(name), function(node, i) {
bgneal@45 8248 if (node == element)
bgneal@45 8249 index = i;
bgneal@45 8250 });
bgneal@45 8251
bgneal@45 8252 return index;
bgneal@45 8253 };
bgneal@45 8254
bgneal@45 8255 if (type == 2) {
bgneal@45 8256 function getLocation() {
bgneal@45 8257 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
bgneal@45 8258
bgneal@45 8259 function getPoint(rng, start) {
bgneal@45 8260 var container = rng[start ? 'startContainer' : 'endContainer'],
bgneal@45 8261 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
bgneal@45 8262
bgneal@45 8263 if (container.nodeType == 3) {
bgneal@45 8264 if (normalized) {
bgneal@45 8265 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
bgneal@45 8266 offset += node.nodeValue.length;
bgneal@45 8267 }
bgneal@45 8268
bgneal@45 8269 point.push(offset);
bgneal@45 8270 } else {
bgneal@45 8271 childNodes = container.childNodes;
bgneal@45 8272
bgneal@45 8273 if (offset >= childNodes.length && childNodes.length) {
bgneal@45 8274 after = 1;
bgneal@45 8275 offset = Math.max(0, childNodes.length - 1);
bgneal@45 8276 }
bgneal@45 8277
bgneal@45 8278 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
bgneal@45 8279 }
bgneal@45 8280
bgneal@45 8281 for (; container && container != root; container = container.parentNode)
bgneal@45 8282 point.push(t.dom.nodeIndex(container, normalized));
bgneal@45 8283
bgneal@45 8284 return point;
bgneal@45 8285 };
bgneal@45 8286
bgneal@45 8287 bookmark.start = getPoint(rng, true);
bgneal@45 8288
bgneal@45 8289 if (!t.isCollapsed())
bgneal@45 8290 bookmark.end = getPoint(rng);
bgneal@45 8291
bgneal@45 8292 return bookmark;
bgneal@45 8293 };
bgneal@45 8294
bgneal@45 8295 if (t.tridentSel)
bgneal@45 8296 return t.tridentSel.getBookmark(type);
bgneal@45 8297
bgneal@45 8298 return getLocation();
bgneal@45 8299 }
bgneal@45 8300
bgneal@45 8301 // Handle simple range
bgneal@45 8302 if (type)
bgneal@45 8303 return {rng : t.getRng()};
bgneal@45 8304
bgneal@45 8305 rng = t.getRng();
bgneal@45 8306 id = dom.uniqueId();
bgneal@45 8307 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
bgneal@45 8308 styles = 'overflow:hidden;line-height:0px';
bgneal@45 8309
bgneal@45 8310 // Explorer method
bgneal@45 8311 if (rng.duplicate || rng.item) {
bgneal@45 8312 // Text selection
bgneal@45 8313 if (!rng.item) {
bgneal@45 8314 rng2 = rng.duplicate();
bgneal@45 8315
bgneal@45 8316 try {
bgneal@45 8317 // Insert start marker
bgneal@45 8318 rng.collapse();
bgneal@45 8319 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
bgneal@45 8320
bgneal@45 8321 // Insert end marker
bgneal@45 8322 if (!collapsed) {
bgneal@45 8323 rng2.collapse(false);
bgneal@45 8324
bgneal@45 8325 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
bgneal@45 8326 rng.moveToElementText(rng2.parentElement());
bgneal@45 8327 if (rng.compareEndPoints('StartToEnd', rng2) == 0)
bgneal@45 8328 rng2.move('character', -1);
bgneal@45 8329
bgneal@45 8330 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
bgneal@45 8331 }
bgneal@45 8332 } catch (ex) {
bgneal@45 8333 // IE might throw unspecified error so lets ignore it
bgneal@45 8334 return null;
bgneal@45 8335 }
bgneal@45 8336 } else {
bgneal@45 8337 // Control selection
bgneal@45 8338 element = rng.item(0);
bgneal@45 8339 name = element.nodeName;
bgneal@45 8340
bgneal@45 8341 return {name : name, index : findIndex(name, element)};
bgneal@45 8342 }
bgneal@45 8343 } else {
bgneal@45 8344 element = t.getNode();
bgneal@45 8345 name = element.nodeName;
bgneal@45 8346 if (name == 'IMG')
bgneal@45 8347 return {name : name, index : findIndex(name, element)};
bgneal@45 8348
bgneal@45 8349 // W3C method
bgneal@45 8350 rng2 = rng.cloneRange();
bgneal@45 8351
bgneal@45 8352 // Insert end marker
bgneal@45 8353 if (!collapsed) {
bgneal@45 8354 rng2.collapse(false);
bgneal@45 8355 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
bgneal@45 8356 }
bgneal@45 8357
bgneal@45 8358 rng.collapse(true);
bgneal@45 8359 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
bgneal@45 8360 }
bgneal@45 8361
bgneal@45 8362 t.moveToBookmark({id : id, keep : 1});
bgneal@45 8363
bgneal@45 8364 return {id : id};
bgneal@45 8365 },
bgneal@45 8366
bgneal@45 8367 moveToBookmark : function(bookmark) {
bgneal@45 8368 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
bgneal@45 8369
bgneal@45 8370 if (bookmark) {
bgneal@45 8371 if (bookmark.start) {
bgneal@45 8372 rng = dom.createRng();
bgneal@45 8373 root = dom.getRoot();
bgneal@45 8374
bgneal@45 8375 function setEndPoint(start) {
bgneal@45 8376 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
bgneal@45 8377
bgneal@45 8378 if (point) {
bgneal@45 8379 offset = point[0];
bgneal@45 8380
bgneal@45 8381 // Find container node
bgneal@45 8382 for (node = root, i = point.length - 1; i >= 1; i--) {
bgneal@45 8383 children = node.childNodes;
bgneal@45 8384
bgneal@45 8385 if (point[i] > children.length - 1)
bgneal@45 8386 return;
bgneal@45 8387
bgneal@45 8388 node = children[point[i]];
bgneal@45 8389 }
bgneal@45 8390
bgneal@45 8391 // Move text offset to best suitable location
bgneal@45 8392 if (node.nodeType === 3)
bgneal@45 8393 offset = Math.min(point[0], node.nodeValue.length);
bgneal@45 8394
bgneal@45 8395 // Move element offset to best suitable location
bgneal@45 8396 if (node.nodeType === 1)
bgneal@45 8397 offset = Math.min(point[0], node.childNodes.length);
bgneal@45 8398
bgneal@45 8399 // Set offset within container node
bgneal@45 8400 if (start)
bgneal@45 8401 rng.setStart(node, offset);
bgneal@45 8402 else
bgneal@45 8403 rng.setEnd(node, offset);
bgneal@45 8404 }
bgneal@45 8405
bgneal@45 8406 return true;
bgneal@45 8407 };
bgneal@45 8408
bgneal@45 8409 if (t.tridentSel)
bgneal@45 8410 return t.tridentSel.moveToBookmark(bookmark);
bgneal@45 8411
bgneal@45 8412 if (setEndPoint(true) && setEndPoint()) {
bgneal@45 8413 t.setRng(rng);
bgneal@45 8414 }
bgneal@45 8415 } else if (bookmark.id) {
bgneal@45 8416 function restoreEndPoint(suffix) {
bgneal@45 8417 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
bgneal@45 8418
bgneal@45 8419 if (marker) {
bgneal@45 8420 node = marker.parentNode;
bgneal@45 8421
bgneal@45 8422 if (suffix == 'start') {
bgneal@45 8423 if (!keep) {
bgneal@45 8424 idx = dom.nodeIndex(marker);
bgneal@45 8425 } else {
bgneal@45 8426 node = marker.firstChild;
bgneal@45 8427 idx = 1;
bgneal@45 8428 }
bgneal@45 8429
bgneal@45 8430 startContainer = endContainer = node;
bgneal@45 8431 startOffset = endOffset = idx;
bgneal@45 8432 } else {
bgneal@45 8433 if (!keep) {
bgneal@45 8434 idx = dom.nodeIndex(marker);
bgneal@45 8435 } else {
bgneal@45 8436 node = marker.firstChild;
bgneal@45 8437 idx = 1;
bgneal@45 8438 }
bgneal@45 8439
bgneal@45 8440 endContainer = node;
bgneal@45 8441 endOffset = idx;
bgneal@45 8442 }
bgneal@45 8443
bgneal@45 8444 if (!keep) {
bgneal@45 8445 prev = marker.previousSibling;
bgneal@45 8446 next = marker.nextSibling;
bgneal@45 8447
bgneal@45 8448 // Remove all marker text nodes
bgneal@45 8449 each(tinymce.grep(marker.childNodes), function(node) {
bgneal@45 8450 if (node.nodeType == 3)
bgneal@45 8451 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
bgneal@45 8452 });
bgneal@45 8453
bgneal@45 8454 // Remove marker but keep children if for example contents where inserted into the marker
bgneal@45 8455 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
bgneal@45 8456 while (marker = dom.get(bookmark.id + '_' + suffix))
bgneal@45 8457 dom.remove(marker, 1);
bgneal@45 8458
bgneal@45 8459 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
bgneal@45 8460 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
bgneal@45 8461 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
bgneal@45 8462 idx = prev.nodeValue.length;
bgneal@45 8463 prev.appendData(next.nodeValue);
bgneal@45 8464 dom.remove(next);
bgneal@45 8465
bgneal@45 8466 if (suffix == 'start') {
bgneal@45 8467 startContainer = endContainer = prev;
bgneal@45 8468 startOffset = endOffset = idx;
bgneal@45 8469 } else {
bgneal@45 8470 endContainer = prev;
bgneal@45 8471 endOffset = idx;
bgneal@45 8472 }
bgneal@45 8473 }
bgneal@45 8474 }
bgneal@45 8475 }
bgneal@45 8476 };
bgneal@45 8477
bgneal@45 8478 function addBogus(node) {
bgneal@45 8479 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
bgneal@45 8480 if (dom.isBlock(node) && !node.innerHTML)
bgneal@45 8481 node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
bgneal@45 8482
bgneal@45 8483 return node;
bgneal@45 8484 };
bgneal@45 8485
bgneal@45 8486 // Restore start/end points
bgneal@45 8487 restoreEndPoint('start');
bgneal@45 8488 restoreEndPoint('end');
bgneal@45 8489
bgneal@45 8490 if (startContainer) {
bgneal@45 8491 rng = dom.createRng();
bgneal@45 8492 rng.setStart(addBogus(startContainer), startOffset);
bgneal@45 8493 rng.setEnd(addBogus(endContainer), endOffset);
bgneal@45 8494 t.setRng(rng);
bgneal@45 8495 }
bgneal@45 8496 } else if (bookmark.name) {
bgneal@45 8497 t.select(dom.select(bookmark.name)[bookmark.index]);
bgneal@45 8498 } else if (bookmark.rng)
bgneal@45 8499 t.setRng(bookmark.rng);
bgneal@45 8500 }
bgneal@45 8501 },
bgneal@45 8502
bgneal@45 8503 select : function(node, content) {
bgneal@45 8504 var t = this, dom = t.dom, rng = dom.createRng(), idx;
bgneal@45 8505
bgneal@45 8506 if (node) {
bgneal@45 8507 idx = dom.nodeIndex(node);
bgneal@45 8508 rng.setStart(node.parentNode, idx);
bgneal@45 8509 rng.setEnd(node.parentNode, idx + 1);
bgneal@45 8510
bgneal@45 8511 // Find first/last text node or BR element
bgneal@45 8512 if (content) {
bgneal@45 8513 function setPoint(node, start) {
bgneal@45 8514 var walker = new tinymce.dom.TreeWalker(node, node);
bgneal@45 8515
bgneal@45 8516 do {
bgneal@45 8517 // Text node
bgneal@45 8518 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
bgneal@45 8519 if (start)
bgneal@45 8520 rng.setStart(node, 0);
bgneal@45 8521 else
bgneal@45 8522 rng.setEnd(node, node.nodeValue.length);
bgneal@45 8523
bgneal@45 8524 return;
bgneal@45 8525 }
bgneal@45 8526
bgneal@45 8527 // BR element
bgneal@45 8528 if (node.nodeName == 'BR') {
bgneal@45 8529 if (start)
bgneal@45 8530 rng.setStartBefore(node);
bgneal@45 8531 else
bgneal@45 8532 rng.setEndBefore(node);
bgneal@45 8533
bgneal@45 8534 return;
bgneal@45 8535 }
bgneal@45 8536 } while (node = (start ? walker.next() : walker.prev()));
bgneal@45 8537 };
bgneal@45 8538
bgneal@45 8539 setPoint(node, 1);
bgneal@45 8540 setPoint(node);
bgneal@45 8541 }
bgneal@45 8542
bgneal@45 8543 t.setRng(rng);
bgneal@45 8544 }
bgneal@45 8545
bgneal@45 8546 return node;
bgneal@45 8547 },
bgneal@45 8548
bgneal@45 8549 isCollapsed : function() {
bgneal@45 8550 var t = this, r = t.getRng(), s = t.getSel();
bgneal@45 8551
bgneal@45 8552 if (!r || r.item)
bgneal@45 8553 return false;
bgneal@45 8554
bgneal@45 8555 if (r.compareEndPoints)
bgneal@45 8556 return r.compareEndPoints('StartToEnd', r) === 0;
bgneal@45 8557
bgneal@45 8558 return !s || r.collapsed;
bgneal@45 8559 },
bgneal@45 8560
bgneal@45 8561 collapse : function(to_start) {
bgneal@45 8562 var self = this, rng = self.getRng(), node;
bgneal@45 8563
bgneal@45 8564 // Control range on IE
bgneal@45 8565 if (rng.item) {
bgneal@45 8566 node = rng.item(0);
bgneal@45 8567 rng = self.win.document.body.createTextRange();
bgneal@45 8568 rng.moveToElementText(node);
bgneal@45 8569 }
bgneal@45 8570
bgneal@45 8571 rng.collapse(!!to_start);
bgneal@45 8572 self.setRng(rng);
bgneal@45 8573 },
bgneal@45 8574
bgneal@45 8575 getSel : function() {
bgneal@45 8576 var t = this, w = this.win;
bgneal@45 8577
bgneal@45 8578 return w.getSelection ? w.getSelection() : w.document.selection;
bgneal@45 8579 },
bgneal@45 8580
bgneal@45 8581 getRng : function(w3c) {
bgneal@45 8582 var t = this, s, r, elm, doc = t.win.document;
bgneal@45 8583
bgneal@45 8584 // Found tridentSel object then we need to use that one
bgneal@45 8585 if (w3c && t.tridentSel)
bgneal@45 8586 return t.tridentSel.getRangeAt(0);
bgneal@45 8587
bgneal@45 8588 try {
bgneal@45 8589 if (s = t.getSel())
bgneal@45 8590 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
bgneal@45 8591 } catch (ex) {
bgneal@45 8592 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
bgneal@45 8593 }
bgneal@45 8594
bgneal@45 8595 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
bgneal@45 8596 if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
bgneal@45 8597 elm = doc.selection.createRange().item(0);
bgneal@45 8598 r = doc.createRange();
bgneal@45 8599 r.setStartBefore(elm);
bgneal@45 8600 r.setEndAfter(elm);
bgneal@45 8601 }
bgneal@45 8602
bgneal@45 8603 // No range found then create an empty one
bgneal@45 8604 // This can occur when the editor is placed in a hidden container element on Gecko
bgneal@45 8605 // Or on IE when there was an exception
bgneal@45 8606 if (!r)
bgneal@45 8607 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
bgneal@45 8608
bgneal@45 8609 if (t.selectedRange && t.explicitRange) {
bgneal@45 8610 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
bgneal@45 8611 // Safari, Opera and Chrome only ever select text which causes the range to change.
bgneal@45 8612 // This lets us use the originally set range if the selection hasn't been changed by the user.
bgneal@45 8613 r = t.explicitRange;
bgneal@45 8614 } else {
bgneal@45 8615 t.selectedRange = null;
bgneal@45 8616 t.explicitRange = null;
bgneal@45 8617 }
bgneal@45 8618 }
bgneal@45 8619
bgneal@45 8620 return r;
bgneal@45 8621 },
bgneal@45 8622
bgneal@45 8623 setRng : function(r) {
bgneal@45 8624 var s, t = this;
bgneal@45 8625
bgneal@45 8626 if (!t.tridentSel) {
bgneal@45 8627 s = t.getSel();
bgneal@45 8628
bgneal@45 8629 if (s) {
bgneal@45 8630 t.explicitRange = r;
bgneal@45 8631
bgneal@45 8632 try {
bgneal@45 8633 s.removeAllRanges();
bgneal@45 8634 } catch (ex) {
bgneal@45 8635 // IE9 might throw errors here don't know why
bgneal@45 8636 }
bgneal@45 8637
bgneal@45 8638 s.addRange(r);
bgneal@45 8639 // adding range isn't always successful so we need to check range count otherwise an exception can occur
bgneal@45 8640 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
bgneal@45 8641 }
bgneal@45 8642 } else {
bgneal@45 8643 // Is W3C Range
bgneal@45 8644 if (r.cloneRange) {
bgneal@45 8645 try {
bgneal@45 8646 t.tridentSel.addRange(r);
bgneal@45 8647 return;
bgneal@45 8648 } catch (ex) {
bgneal@45 8649 //IE9 throws an error here if called before selection is placed in the editor
bgneal@45 8650 }
bgneal@45 8651 }
bgneal@45 8652
bgneal@45 8653 // Is IE specific range
bgneal@45 8654 try {
bgneal@45 8655 r.select();
bgneal@45 8656 } catch (ex) {
bgneal@45 8657 // Needed for some odd IE bug #1843306
bgneal@45 8658 }
bgneal@45 8659 }
bgneal@45 8660 },
bgneal@45 8661
bgneal@45 8662 setNode : function(n) {
bgneal@45 8663 var t = this;
bgneal@45 8664
bgneal@45 8665 t.setContent(t.dom.getOuterHTML(n));
bgneal@45 8666
bgneal@45 8667 return n;
bgneal@45 8668 },
bgneal@45 8669
bgneal@45 8670 getNode : function() {
bgneal@45 8671 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
bgneal@45 8672
bgneal@45 8673 // Range maybe lost after the editor is made visible again
bgneal@45 8674 if (!rng)
bgneal@45 8675 return t.dom.getRoot();
bgneal@45 8676
bgneal@45 8677 if (rng.setStart) {
bgneal@45 8678 elm = rng.commonAncestorContainer;
bgneal@45 8679
bgneal@45 8680 // Handle selection a image or other control like element such as anchors
bgneal@45 8681 if (!rng.collapsed) {
bgneal@45 8682 if (rng.startContainer == rng.endContainer) {
bgneal@45 8683 if (rng.endOffset - rng.startOffset < 2) {
bgneal@45 8684 if (rng.startContainer.hasChildNodes())
bgneal@45 8685 elm = rng.startContainer.childNodes[rng.startOffset];
bgneal@45 8686 }
bgneal@45 8687 }
bgneal@45 8688
bgneal@45 8689 // If the anchor node is a element instead of a text node then return this element
bgneal@45 8690 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
bgneal@45 8691 // return sel.anchorNode.childNodes[sel.anchorOffset];
bgneal@45 8692
bgneal@45 8693 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
bgneal@45 8694 // This happens when you double click an underlined word in FireFox.
bgneal@45 8695 if (start.nodeType === 3 && end.nodeType === 3) {
bgneal@45 8696 function skipEmptyTextNodes(n, forwards) {
bgneal@45 8697 var orig = n;
bgneal@45 8698 while (n && n.nodeType === 3 && n.length === 0) {
bgneal@45 8699 n = forwards ? n.nextSibling : n.previousSibling;
bgneal@45 8700 }
bgneal@45 8701 return n || orig;
bgneal@45 8702 }
bgneal@45 8703 if (start.length === rng.startOffset) {
bgneal@45 8704 start = skipEmptyTextNodes(start.nextSibling, true);
bgneal@45 8705 } else {
bgneal@45 8706 start = start.parentNode;
bgneal@45 8707 }
bgneal@45 8708 if (rng.endOffset === 0) {
bgneal@45 8709 end = skipEmptyTextNodes(end.previousSibling, false);
bgneal@45 8710 } else {
bgneal@45 8711 end = end.parentNode;
bgneal@45 8712 }
bgneal@45 8713
bgneal@45 8714 if (start && start === end)
bgneal@45 8715 return start;
bgneal@45 8716 }
bgneal@45 8717 }
bgneal@45 8718
bgneal@45 8719 if (elm && elm.nodeType == 3)
bgneal@45 8720 return elm.parentNode;
bgneal@45 8721
bgneal@45 8722 return elm;
bgneal@45 8723 }
bgneal@45 8724
bgneal@45 8725 return rng.item ? rng.item(0) : rng.parentElement();
bgneal@45 8726 },
bgneal@45 8727
bgneal@45 8728 getSelectedBlocks : function(st, en) {
bgneal@45 8729 var t = this, dom = t.dom, sb, eb, n, bl = [];
bgneal@45 8730
bgneal@45 8731 sb = dom.getParent(st || t.getStart(), dom.isBlock);
bgneal@45 8732 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
bgneal@45 8733
bgneal@45 8734 if (sb)
bgneal@45 8735 bl.push(sb);
bgneal@45 8736
bgneal@45 8737 if (sb && eb && sb != eb) {
bgneal@45 8738 n = sb;
bgneal@45 8739
bgneal@45 8740 var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot());
bgneal@45 8741 while ((n = walker.next()) && n != eb) {
bgneal@45 8742 if (dom.isBlock(n))
bgneal@45 8743 bl.push(n);
bgneal@45 8744 }
bgneal@45 8745 }
bgneal@45 8746
bgneal@45 8747 if (eb && sb != eb)
bgneal@45 8748 bl.push(eb);
bgneal@45 8749
bgneal@45 8750 return bl;
bgneal@45 8751 },
bgneal@45 8752
bgneal@45 8753 normalize : function() {
bgneal@45 8754 var self = this, rng, normalized;
bgneal@45 8755
bgneal@45 8756 // TODO:
bgneal@45 8757 // Retain selection direction.
bgneal@45 8758 // Lean left/right on Gecko for inline elements.
bgneal@45 8759 // Run this on mouse up/key up when the user manually moves the selection
bgneal@45 8760
bgneal@45 8761 // Normalize only on non IE browsers for now
bgneal@45 8762 if (tinymce.isIE)
bgneal@45 8763 return;
bgneal@45 8764
bgneal@45 8765 function normalizeEndPoint(start) {
bgneal@45 8766 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
bgneal@45 8767
bgneal@45 8768 container = rng[(start ? 'start' : 'end') + 'Container'];
bgneal@45 8769 offset = rng[(start ? 'start' : 'end') + 'Offset'];
bgneal@45 8770
bgneal@45 8771 // If the container is a document move it to the body element
bgneal@45 8772 if (container.nodeType === 9) {
bgneal@45 8773 container = container.body;
bgneal@45 8774 offset = 0;
bgneal@45 8775 }
bgneal@45 8776
bgneal@45 8777 // If the container is body try move it into the closest text node or position
bgneal@45 8778 // TODO: Add more logic here to handle element selection cases
bgneal@45 8779 if (container === body) {
bgneal@45 8780 // Resolve the index
bgneal@45 8781 if (container.hasChildNodes()) {
bgneal@45 8782 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
bgneal@45 8783 offset = 0;
bgneal@45 8784
bgneal@45 8785 // Don't walk into elements that doesn't have any child nodes like a IMG
bgneal@45 8786 if (container.hasChildNodes()) {
bgneal@45 8787 // Walk the DOM to find a text node to place the caret at or a BR
bgneal@45 8788 node = container;
bgneal@45 8789 walker = new tinymce.dom.TreeWalker(container, body);
bgneal@45 8790 do {
bgneal@45 8791 // Found a text node use that position
bgneal@45 8792 if (node.nodeType === 3) {
bgneal@45 8793 offset = start ? 0 : node.nodeValue.length - 1;
bgneal@45 8794 container = node;
bgneal@45 8795 normalized = true;
bgneal@45 8796 break;
bgneal@45 8797 }
bgneal@45 8798
bgneal@45 8799 // Found a BR/IMG element that we can place the caret before
bgneal@45 8800 if (/^(BR|IMG)$/.test(node.nodeName)) {
bgneal@45 8801 offset = dom.nodeIndex(node);
bgneal@45 8802 container = node.parentNode;
bgneal@45 8803
bgneal@45 8804 // Put caret after image when moving the end point
bgneal@45 8805 if (node.nodeName == "IMG" && !start) {
bgneal@45 8806 offset++;
bgneal@45 8807 }
bgneal@45 8808
bgneal@45 8809 normalized = true;
bgneal@45 8810 break;
bgneal@45 8811 }
bgneal@45 8812 } while (node = (start ? walker.next() : walker.prev()));
bgneal@45 8813 }
bgneal@45 8814 }
bgneal@45 8815 }
bgneal@45 8816
bgneal@45 8817 // Set endpoint if it was normalized
bgneal@45 8818 if (normalized)
bgneal@45 8819 rng['set' + (start ? 'Start' : 'End')](container, offset);
bgneal@45 8820 };
bgneal@45 8821
bgneal@45 8822 rng = self.getRng();
bgneal@45 8823
bgneal@45 8824 // Normalize the end points
bgneal@45 8825 normalizeEndPoint(true);
bgneal@45 8826
bgneal@45 8827 if (!rng.collapsed)
bgneal@45 8828 normalizeEndPoint();
bgneal@45 8829
bgneal@45 8830 // Set the selection if it was normalized
bgneal@45 8831 if (normalized) {
bgneal@45 8832 //console.log(self.dom.dumpRng(rng));
bgneal@45 8833 self.setRng(rng);
bgneal@45 8834 }
bgneal@45 8835 },
bgneal@45 8836
bgneal@45 8837 destroy : function(s) {
bgneal@45 8838 var t = this;
bgneal@45 8839
bgneal@45 8840 t.win = null;
bgneal@45 8841
bgneal@45 8842 // Manual destroy then remove unload handler
bgneal@45 8843 if (!s)
bgneal@45 8844 tinymce.removeUnload(t.destroy);
bgneal@45 8845 },
bgneal@45 8846
bgneal@45 8847 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
bgneal@45 8848 _fixIESelection : function() {
bgneal@45 8849 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
bgneal@45 8850
bgneal@45 8851 // Make HTML element unselectable since we are going to handle selection by hand
bgneal@45 8852 doc.documentElement.unselectable = true;
bgneal@45 8853
bgneal@45 8854 // Return range from point or null if it failed
bgneal@45 8855 function rngFromPoint(x, y) {
bgneal@45 8856 var rng = body.createTextRange();
bgneal@45 8857
bgneal@45 8858 try {
bgneal@45 8859 rng.moveToPoint(x, y);
bgneal@45 8860 } catch (ex) {
bgneal@45 8861 // IE sometimes throws and exception, so lets just ignore it
bgneal@45 8862 rng = null;
bgneal@45 8863 }
bgneal@45 8864
bgneal@45 8865 return rng;
bgneal@45 8866 };
bgneal@45 8867
bgneal@45 8868 // Fires while the selection is changing
bgneal@45 8869 function selectionChange(e) {
bgneal@45 8870 var pointRng;
bgneal@45 8871
bgneal@45 8872 // Check if the button is down or not
bgneal@45 8873 if (e.button) {
bgneal@45 8874 // Create range from mouse position
bgneal@45 8875 pointRng = rngFromPoint(e.x, e.y);
bgneal@45 8876
bgneal@45 8877 if (pointRng) {
bgneal@45 8878 // Check if pointRange is before/after selection then change the endPoint
bgneal@45 8879 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
bgneal@45 8880 pointRng.setEndPoint('StartToStart', startRng);
bgneal@45 8881 else
bgneal@45 8882 pointRng.setEndPoint('EndToEnd', startRng);
bgneal@45 8883
bgneal@45 8884 pointRng.select();
bgneal@45 8885 }
bgneal@45 8886 } else
bgneal@45 8887 endSelection();
bgneal@45 8888 }
bgneal@45 8889
bgneal@45 8890 // Removes listeners
bgneal@45 8891 function endSelection() {
bgneal@45 8892 var rng = doc.selection.createRange();
bgneal@45 8893
bgneal@45 8894 // If the range is collapsed then use the last start range
bgneal@45 8895 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
bgneal@45 8896 startRng.select();
bgneal@45 8897
bgneal@45 8898 dom.unbind(doc, 'mouseup', endSelection);
bgneal@45 8899 dom.unbind(doc, 'mousemove', selectionChange);
bgneal@45 8900 startRng = started = 0;
bgneal@45 8901 };
bgneal@45 8902
bgneal@45 8903 // Detect when user selects outside BODY
bgneal@45 8904 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
bgneal@45 8905 if (e.target.nodeName === 'HTML') {
bgneal@45 8906 if (started)
bgneal@45 8907 endSelection();
bgneal@45 8908
bgneal@45 8909 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
bgneal@45 8910 htmlElm = doc.documentElement;
bgneal@45 8911 if (htmlElm.scrollHeight > htmlElm.clientHeight)
bgneal@45 8912 return;
bgneal@45 8913
bgneal@45 8914 started = 1;
bgneal@45 8915 // Setup start position
bgneal@45 8916 startRng = rngFromPoint(e.x, e.y);
bgneal@45 8917 if (startRng) {
bgneal@45 8918 // Listen for selection change events
bgneal@45 8919 dom.bind(doc, 'mouseup', endSelection);
bgneal@45 8920 dom.bind(doc, 'mousemove', selectionChange);
bgneal@45 8921
bgneal@45 8922 dom.win.focus();
bgneal@45 8923 startRng.select();
bgneal@45 8924 }
bgneal@45 8925 }
bgneal@45 8926 });
bgneal@45 8927 }
bgneal@45 8928 });
bgneal@45 8929 })(tinymce);
bgneal@45 8930
bgneal@45 8931 (function(tinymce) {
bgneal@45 8932 tinymce.dom.Serializer = function(settings, dom, schema) {
bgneal@45 8933 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
bgneal@45 8934
bgneal@45 8935 // Support the old apply_source_formatting option
bgneal@45 8936 if (!settings.apply_source_formatting)
bgneal@45 8937 settings.indent = false;
bgneal@45 8938
bgneal@45 8939 // Default DOM and Schema if they are undefined
bgneal@45 8940 dom = dom || tinymce.DOM;
bgneal@45 8941 schema = schema || new tinymce.html.Schema(settings);
bgneal@45 8942 settings.entity_encoding = settings.entity_encoding || 'named';
bgneal@45 8943 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
bgneal@45 8944
bgneal@45 8945 onPreProcess = new tinymce.util.Dispatcher(self);
bgneal@45 8946
bgneal@45 8947 onPostProcess = new tinymce.util.Dispatcher(self);
bgneal@45 8948
bgneal@45 8949 htmlParser = new tinymce.html.DomParser(settings, schema);
bgneal@45 8950
bgneal@45 8951 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
bgneal@45 8952 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
bgneal@45 8953 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
bgneal@45 8954
bgneal@45 8955 while (i--) {
bgneal@45 8956 node = nodes[i];
bgneal@45 8957
bgneal@45 8958 value = node.attributes.map[internalName];
bgneal@45 8959 if (value !== undef) {
bgneal@45 8960 // Set external name to internal value and remove internal
bgneal@45 8961 node.attr(name, value.length > 0 ? value : null);
bgneal@45 8962 node.attr(internalName, null);
bgneal@45 8963 } else {
bgneal@45 8964 // No internal attribute found then convert the value we have in the DOM
bgneal@45 8965 value = node.attributes.map[name];
bgneal@45 8966
bgneal@45 8967 if (name === "style")
bgneal@45 8968 value = dom.serializeStyle(dom.parseStyle(value), node.name);
bgneal@45 8969 else if (urlConverter)
bgneal@45 8970 value = urlConverter.call(urlConverterScope, value, name, node.name);
bgneal@45 8971
bgneal@45 8972 node.attr(name, value.length > 0 ? value : null);
bgneal@45 8973 }
bgneal@45 8974 }
bgneal@45 8975 });
bgneal@45 8976
bgneal@45 8977 // Remove internal classes mceItem<..>
bgneal@45 8978 htmlParser.addAttributeFilter('class', function(nodes, name) {
bgneal@45 8979 var i = nodes.length, node, value;
bgneal@45 8980
bgneal@45 8981 while (i--) {
bgneal@45 8982 node = nodes[i];
bgneal@45 8983 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
bgneal@45 8984 node.attr('class', value.length > 0 ? value : null);
bgneal@45 8985 }
bgneal@45 8986 });
bgneal@45 8987
bgneal@45 8988 // Remove bookmark elements
bgneal@45 8989 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
bgneal@45 8990 var i = nodes.length, node;
bgneal@45 8991
bgneal@45 8992 while (i--) {
bgneal@45 8993 node = nodes[i];
bgneal@45 8994
bgneal@45 8995 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
bgneal@45 8996 node.remove();
bgneal@45 8997 }
bgneal@45 8998 });
bgneal@45 8999
bgneal@45 9000 // Remove expando attributes
bgneal@45 9001 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
bgneal@45 9002 var i = nodes.length;
bgneal@45 9003
bgneal@45 9004 while (i--) {
bgneal@45 9005 nodes[i].attr(name, null);
bgneal@45 9006 }
bgneal@45 9007 });
bgneal@45 9008
bgneal@45 9009 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
bgneal@45 9010 htmlParser.addNodeFilter('script,style', function(nodes, name) {
bgneal@45 9011 var i = nodes.length, node, value;
bgneal@45 9012
bgneal@45 9013 function trim(value) {
bgneal@45 9014 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
bgneal@45 9015 .replace(/^[\r\n]*|[\r\n]*$/g, '')
bgneal@45 9016 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
bgneal@45 9017 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
bgneal@45 9018 };
bgneal@45 9019
bgneal@45 9020 while (i--) {
bgneal@45 9021 node = nodes[i];
bgneal@45 9022 value = node.firstChild ? node.firstChild.value : '';
bgneal@45 9023
bgneal@45 9024 if (name === "script") {
bgneal@45 9025 // Remove mce- prefix from script elements
bgneal@45 9026 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
bgneal@45 9027
bgneal@45 9028 if (value.length > 0)
bgneal@45 9029 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
bgneal@45 9030 } else {
bgneal@45 9031 if (value.length > 0)
bgneal@45 9032 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
bgneal@45 9033 }
bgneal@45 9034 }
bgneal@45 9035 });
bgneal@45 9036
bgneal@45 9037 // Convert comments to cdata and handle protected comments
bgneal@45 9038 htmlParser.addNodeFilter('#comment', function(nodes, name) {
bgneal@45 9039 var i = nodes.length, node;
bgneal@45 9040
bgneal@45 9041 while (i--) {
bgneal@45 9042 node = nodes[i];
bgneal@45 9043
bgneal@45 9044 if (node.value.indexOf('[CDATA[') === 0) {
bgneal@45 9045 node.name = '#cdata';
bgneal@45 9046 node.type = 4;
bgneal@45 9047 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
bgneal@45 9048 } else if (node.value.indexOf('mce:protected ') === 0) {
bgneal@45 9049 node.name = "#text";
bgneal@45 9050 node.type = 3;
bgneal@45 9051 node.raw = true;
bgneal@45 9052 node.value = unescape(node.value).substr(14);
bgneal@45 9053 }
bgneal@45 9054 }
bgneal@45 9055 });
bgneal@45 9056
bgneal@45 9057 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
bgneal@45 9058 var i = nodes.length, node;
bgneal@45 9059
bgneal@45 9060 while (i--) {
bgneal@45 9061 node = nodes[i];
bgneal@45 9062 if (node.type === 7)
bgneal@45 9063 node.remove();
bgneal@45 9064 else if (node.type === 1) {
bgneal@45 9065 if (name === "input" && !("type" in node.attributes.map))
bgneal@45 9066 node.attr('type', 'text');
bgneal@45 9067 }
bgneal@45 9068 }
bgneal@45 9069 });
bgneal@45 9070
bgneal@45 9071 // Fix list elements, TODO: Replace this later
bgneal@45 9072 if (settings.fix_list_elements) {
bgneal@45 9073 htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
bgneal@45 9074 var i = nodes.length, node, parentNode;
bgneal@45 9075
bgneal@45 9076 while (i--) {
bgneal@45 9077 node = nodes[i];
bgneal@45 9078 parentNode = node.parent;
bgneal@45 9079
bgneal@45 9080 if (parentNode.name === 'ul' || parentNode.name === 'ol') {
bgneal@45 9081 if (node.prev && node.prev.name === 'li') {
bgneal@45 9082 node.prev.append(node);
bgneal@45 9083 }
bgneal@45 9084 }
bgneal@45 9085 }
bgneal@45 9086 });
bgneal@45 9087 }
bgneal@45 9088
bgneal@45 9089 // Remove internal data attributes
bgneal@45 9090 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
bgneal@45 9091 var i = nodes.length;
bgneal@45 9092
bgneal@45 9093 while (i--) {
bgneal@45 9094 nodes[i].attr(name, null);
bgneal@45 9095 }
bgneal@45 9096 });
bgneal@45 9097
bgneal@45 9098 // Return public methods
bgneal@45 9099 return {
bgneal@45 9100 schema : schema,
bgneal@45 9101
bgneal@45 9102 addNodeFilter : htmlParser.addNodeFilter,
bgneal@45 9103
bgneal@45 9104 addAttributeFilter : htmlParser.addAttributeFilter,
bgneal@45 9105
bgneal@45 9106 onPreProcess : onPreProcess,
bgneal@45 9107
bgneal@45 9108 onPostProcess : onPostProcess,
bgneal@45 9109
bgneal@45 9110 serialize : function(node, args) {
bgneal@45 9111 var impl, doc, oldDoc, htmlSerializer, content;
bgneal@45 9112
bgneal@45 9113 // Explorer won't clone contents of script and style and the
bgneal@45 9114 // selected index of select elements are cleared on a clone operation.
bgneal@45 9115 if (isIE && dom.select('script,style,select,map').length > 0) {
bgneal@45 9116 content = node.innerHTML;
bgneal@45 9117 node = node.cloneNode(false);
bgneal@45 9118 dom.setHTML(node, content);
bgneal@45 9119 } else
bgneal@45 9120 node = node.cloneNode(true);
bgneal@45 9121
bgneal@45 9122 // Nodes needs to be attached to something in WebKit/Opera
bgneal@45 9123 // Older builds of Opera crashes if you attach the node to an document created dynamically
bgneal@45 9124 // and since we can't feature detect a crash we need to sniff the acutal build number
bgneal@45 9125 // This fix will make DOM ranges and make Sizzle happy!
bgneal@45 9126 impl = node.ownerDocument.implementation;
bgneal@45 9127 if (impl.createHTMLDocument) {
bgneal@45 9128 // Create an empty HTML document
bgneal@45 9129 doc = impl.createHTMLDocument("");
bgneal@45 9130
bgneal@45 9131 // Add the element or it's children if it's a body element to the new document
bgneal@45 9132 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
bgneal@45 9133 doc.body.appendChild(doc.importNode(node, true));
bgneal@45 9134 });
bgneal@45 9135
bgneal@45 9136 // Grab first child or body element for serialization
bgneal@45 9137 if (node.nodeName != 'BODY')
bgneal@45 9138 node = doc.body.firstChild;
bgneal@45 9139 else
bgneal@45 9140 node = doc.body;
bgneal@45 9141
bgneal@45 9142 // set the new document in DOMUtils so createElement etc works
bgneal@45 9143 oldDoc = dom.doc;
bgneal@45 9144 dom.doc = doc;
bgneal@45 9145 }
bgneal@45 9146
bgneal@45 9147 args = args || {};
bgneal@45 9148 args.format = args.format || 'html';
bgneal@45 9149
bgneal@45 9150 // Pre process
bgneal@45 9151 if (!args.no_events) {
bgneal@45 9152 args.node = node;
bgneal@45 9153 onPreProcess.dispatch(self, args);
bgneal@45 9154 }
bgneal@45 9155
bgneal@45 9156 // Setup serializer
bgneal@45 9157 htmlSerializer = new tinymce.html.Serializer(settings, schema);
bgneal@45 9158
bgneal@45 9159 // Parse and serialize HTML
bgneal@45 9160 args.content = htmlSerializer.serialize(
bgneal@45 9161 htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
bgneal@45 9162 );
bgneal@45 9163
bgneal@45 9164 // Replace all BOM characters for now until we can find a better solution
bgneal@45 9165 if (!args.cleanup)
bgneal@45 9166 args.content = args.content.replace(/\uFEFF|\u200B/g, '');
bgneal@45 9167
bgneal@45 9168 // Post process
bgneal@45 9169 if (!args.no_events)
bgneal@45 9170 onPostProcess.dispatch(self, args);
bgneal@45 9171
bgneal@45 9172 // Restore the old document if it was changed
bgneal@45 9173 if (oldDoc)
bgneal@45 9174 dom.doc = oldDoc;
bgneal@45 9175
bgneal@45 9176 args.node = null;
bgneal@45 9177
bgneal@45 9178 return args.content;
bgneal@45 9179 },
bgneal@45 9180
bgneal@45 9181 addRules : function(rules) {
bgneal@45 9182 schema.addValidElements(rules);
bgneal@45 9183 },
bgneal@45 9184
bgneal@45 9185 setRules : function(rules) {
bgneal@45 9186 schema.setValidElements(rules);
bgneal@45 9187 }
bgneal@45 9188 };
bgneal@45 9189 };
bgneal@45 9190 })(tinymce);
bgneal@45 9191 (function(tinymce) {
bgneal@45 9192 tinymce.dom.ScriptLoader = function(settings) {
bgneal@45 9193 var QUEUED = 0,
bgneal@45 9194 LOADING = 1,
bgneal@45 9195 LOADED = 2,
bgneal@45 9196 states = {},
bgneal@45 9197 queue = [],
bgneal@45 9198 scriptLoadedCallbacks = {},
bgneal@45 9199 queueLoadedCallbacks = [],
bgneal@45 9200 loading = 0,
bgneal@45 9201 undefined;
bgneal@45 9202
bgneal@45 9203 function loadScript(url, callback) {
bgneal@45 9204 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
bgneal@45 9205
bgneal@45 9206 // Execute callback when script is loaded
bgneal@45 9207 function done() {
bgneal@45 9208 dom.remove(id);
bgneal@45 9209
bgneal@45 9210 if (elm)
bgneal@45 9211 elm.onreadystatechange = elm.onload = elm = null;
bgneal@45 9212
bgneal@45 9213 callback();
bgneal@45 9214 };
bgneal@45 9215
bgneal@45 9216 function error() {
bgneal@45 9217 // Report the error so it's easier for people to spot loading errors
bgneal@45 9218 if (typeof(console) !== "undefined" && console.log)
bgneal@45 9219 console.log("Failed to load: " + url);
bgneal@45 9220
bgneal@45 9221 // We can't mark it as done if there is a load error since
bgneal@45 9222 // A) We don't want to produce 404 errors on the server and
bgneal@45 9223 // B) the onerror event won't fire on all browsers.
bgneal@45 9224 // done();
bgneal@45 9225 };
bgneal@45 9226
bgneal@45 9227 id = dom.uniqueId();
bgneal@45 9228
bgneal@45 9229 if (tinymce.isIE6) {
bgneal@45 9230 uri = new tinymce.util.URI(url);
bgneal@45 9231 loc = location;
bgneal@45 9232
bgneal@45 9233 // If script is from same domain and we
bgneal@45 9234 // use IE 6 then use XHR since it's more reliable
bgneal@45 9235 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
bgneal@45 9236 tinymce.util.XHR.send({
bgneal@45 9237 url : tinymce._addVer(uri.getURI()),
bgneal@45 9238 success : function(content) {
bgneal@45 9239 // Create new temp script element
bgneal@45 9240 var script = dom.create('script', {
bgneal@45 9241 type : 'text/javascript'
bgneal@45 9242 });
bgneal@45 9243
bgneal@45 9244 // Evaluate script in global scope
bgneal@45 9245 script.text = content;
bgneal@45 9246 document.getElementsByTagName('head')[0].appendChild(script);
bgneal@45 9247 dom.remove(script);
bgneal@45 9248
bgneal@45 9249 done();
bgneal@45 9250 },
bgneal@45 9251
bgneal@45 9252 error : error
bgneal@45 9253 });
bgneal@45 9254
bgneal@45 9255 return;
bgneal@45 9256 }
bgneal@45 9257 }
bgneal@45 9258
bgneal@45 9259 // Create new script element
bgneal@45 9260 elm = dom.create('script', {
bgneal@45 9261 id : id,
bgneal@45 9262 type : 'text/javascript',
bgneal@45 9263 src : tinymce._addVer(url)
bgneal@45 9264 });
bgneal@45 9265
bgneal@45 9266 // Add onload listener for non IE browsers since IE9
bgneal@45 9267 // fires onload event before the script is parsed and executed
bgneal@45 9268 if (!tinymce.isIE)
bgneal@45 9269 elm.onload = done;
bgneal@45 9270
bgneal@45 9271 // Add onerror event will get fired on some browsers but not all of them
bgneal@45 9272 elm.onerror = error;
bgneal@45 9273
bgneal@45 9274 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
bgneal@45 9275 if (!tinymce.isOpera) {
bgneal@45 9276 elm.onreadystatechange = function() {
bgneal@45 9277 var state = elm.readyState;
bgneal@45 9278
bgneal@45 9279 // Loaded state is passed on IE 6 however there
bgneal@45 9280 // are known issues with this method but we can't use
bgneal@45 9281 // XHR in a cross domain loading
bgneal@45 9282 if (state == 'complete' || state == 'loaded')
bgneal@45 9283 done();
bgneal@45 9284 };
bgneal@45 9285 }
bgneal@45 9286
bgneal@45 9287 // Most browsers support this feature so we report errors
bgneal@45 9288 // for those at least to help users track their missing plugins etc
bgneal@45 9289 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
bgneal@45 9290 /*elm.onerror = function() {
bgneal@45 9291 alert('Failed to load: ' + url);
bgneal@45 9292 };*/
bgneal@45 9293
bgneal@45 9294 // Add script to document
bgneal@45 9295 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
bgneal@45 9296 };
bgneal@45 9297
bgneal@45 9298 this.isDone = function(url) {
bgneal@45 9299 return states[url] == LOADED;
bgneal@45 9300 };
bgneal@45 9301
bgneal@45 9302 this.markDone = function(url) {
bgneal@45 9303 states[url] = LOADED;
bgneal@45 9304 };
bgneal@45 9305
bgneal@45 9306 this.add = this.load = function(url, callback, scope) {
bgneal@45 9307 var item, state = states[url];
bgneal@45 9308
bgneal@45 9309 // Add url to load queue
bgneal@45 9310 if (state == undefined) {
bgneal@45 9311 queue.push(url);
bgneal@45 9312 states[url] = QUEUED;
bgneal@45 9313 }
bgneal@45 9314
bgneal@45 9315 if (callback) {
bgneal@45 9316 // Store away callback for later execution
bgneal@45 9317 if (!scriptLoadedCallbacks[url])
bgneal@45 9318 scriptLoadedCallbacks[url] = [];
bgneal@45 9319
bgneal@45 9320 scriptLoadedCallbacks[url].push({
bgneal@45 9321 func : callback,
bgneal@45 9322 scope : scope || this
bgneal@45 9323 });
bgneal@45 9324 }
bgneal@45 9325 };
bgneal@45 9326
bgneal@45 9327 this.loadQueue = function(callback, scope) {
bgneal@45 9328 this.loadScripts(queue, callback, scope);
bgneal@45 9329 };
bgneal@45 9330
bgneal@45 9331 this.loadScripts = function(scripts, callback, scope) {
bgneal@45 9332 var loadScripts;
bgneal@45 9333
bgneal@45 9334 function execScriptLoadedCallbacks(url) {
bgneal@45 9335 // Execute URL callback functions
bgneal@45 9336 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
bgneal@45 9337 callback.func.call(callback.scope);
bgneal@45 9338 });
bgneal@45 9339
bgneal@45 9340 scriptLoadedCallbacks[url] = undefined;
bgneal@45 9341 };
bgneal@45 9342
bgneal@45 9343 queueLoadedCallbacks.push({
bgneal@45 9344 func : callback,
bgneal@45 9345 scope : scope || this
bgneal@45 9346 });
bgneal@45 9347
bgneal@45 9348 loadScripts = function() {
bgneal@45 9349 var loadingScripts = tinymce.grep(scripts);
bgneal@45 9350
bgneal@45 9351 // Current scripts has been handled
bgneal@45 9352 scripts.length = 0;
bgneal@45 9353
bgneal@45 9354 // Load scripts that needs to be loaded
bgneal@45 9355 tinymce.each(loadingScripts, function(url) {
bgneal@45 9356 // Script is already loaded then execute script callbacks directly
bgneal@45 9357 if (states[url] == LOADED) {
bgneal@45 9358 execScriptLoadedCallbacks(url);
bgneal@45 9359 return;
bgneal@45 9360 }
bgneal@45 9361
bgneal@45 9362 // Is script not loading then start loading it
bgneal@45 9363 if (states[url] != LOADING) {
bgneal@45 9364 states[url] = LOADING;
bgneal@45 9365 loading++;
bgneal@45 9366
bgneal@45 9367 loadScript(url, function() {
bgneal@45 9368 states[url] = LOADED;
bgneal@45 9369 loading--;
bgneal@45 9370
bgneal@45 9371 execScriptLoadedCallbacks(url);
bgneal@45 9372
bgneal@45 9373 // Load more scripts if they where added by the recently loaded script
bgneal@45 9374 loadScripts();
bgneal@45 9375 });
bgneal@45 9376 }
bgneal@45 9377 });
bgneal@45 9378
bgneal@45 9379 // No scripts are currently loading then execute all pending queue loaded callbacks
bgneal@45 9380 if (!loading) {
bgneal@45 9381 tinymce.each(queueLoadedCallbacks, function(callback) {
bgneal@45 9382 callback.func.call(callback.scope);
bgneal@45 9383 });
bgneal@45 9384
bgneal@45 9385 queueLoadedCallbacks.length = 0;
bgneal@45 9386 }
bgneal@45 9387 };
bgneal@45 9388
bgneal@45 9389 loadScripts();
bgneal@45 9390 };
bgneal@45 9391 };
bgneal@45 9392
bgneal@45 9393 // Global script loader
bgneal@45 9394 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
bgneal@45 9395 })(tinymce);
bgneal@45 9396
bgneal@45 9397 tinymce.dom.TreeWalker = function(start_node, root_node) {
bgneal@45 9398 var node = start_node;
bgneal@45 9399
bgneal@45 9400 function findSibling(node, start_name, sibling_name, shallow) {
bgneal@45 9401 var sibling, parent;
bgneal@45 9402
bgneal@45 9403 if (node) {
bgneal@45 9404 // Walk into nodes if it has a start
bgneal@45 9405 if (!shallow && node[start_name])
bgneal@45 9406 return node[start_name];
bgneal@45 9407
bgneal@45 9408 // Return the sibling if it has one
bgneal@45 9409 if (node != root_node) {
bgneal@45 9410 sibling = node[sibling_name];
bgneal@45 9411 if (sibling)
bgneal@45 9412 return sibling;
bgneal@45 9413
bgneal@45 9414 // Walk up the parents to look for siblings
bgneal@45 9415 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
bgneal@45 9416 sibling = parent[sibling_name];
bgneal@45 9417 if (sibling)
bgneal@45 9418 return sibling;
bgneal@45 9419 }
bgneal@45 9420 }
bgneal@45 9421 }
bgneal@45 9422 };
bgneal@45 9423
bgneal@45 9424 this.current = function() {
bgneal@45 9425 return node;
bgneal@45 9426 };
bgneal@45 9427
bgneal@45 9428 this.next = function(shallow) {
bgneal@45 9429 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
bgneal@45 9430 };
bgneal@45 9431
bgneal@45 9432 this.prev = function(shallow) {
bgneal@45 9433 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
bgneal@45 9434 };
bgneal@45 9435 };
bgneal@45 9436
bgneal@45 9437 (function(tinymce) {
bgneal@45 9438 tinymce.dom.RangeUtils = function(dom) {
bgneal@45 9439 var INVISIBLE_CHAR = '\uFEFF';
bgneal@45 9440
bgneal@45 9441 this.walk = function(rng, callback) {
bgneal@45 9442 var startContainer = rng.startContainer,
bgneal@45 9443 startOffset = rng.startOffset,
bgneal@45 9444 endContainer = rng.endContainer,
bgneal@45 9445 endOffset = rng.endOffset,
bgneal@45 9446 ancestor, startPoint,
bgneal@45 9447 endPoint, node, parent, siblings, nodes;
bgneal@45 9448
bgneal@45 9449 // Handle table cell selection the table plugin enables
bgneal@45 9450 // you to fake select table cells and perform formatting actions on them
bgneal@45 9451 nodes = dom.select('td.mceSelected,th.mceSelected');
bgneal@45 9452 if (nodes.length > 0) {
bgneal@45 9453 tinymce.each(nodes, function(node) {
bgneal@45 9454 callback([node]);
bgneal@45 9455 });
bgneal@45 9456
bgneal@45 9457 return;
bgneal@45 9458 }
bgneal@45 9459
bgneal@45 9460 function exclude(nodes) {
bgneal@45 9461 var node;
bgneal@45 9462
bgneal@45 9463 // First node is excluded
bgneal@45 9464 node = nodes[0];
bgneal@45 9465 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
bgneal@45 9466 nodes.splice(0, 1);
bgneal@45 9467 }
bgneal@45 9468
bgneal@45 9469 // Last node is excluded
bgneal@45 9470 node = nodes[nodes.length - 1];
bgneal@45 9471 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
bgneal@45 9472 nodes.splice(nodes.length - 1, 1);
bgneal@45 9473 }
bgneal@45 9474
bgneal@45 9475 return nodes;
bgneal@45 9476 };
bgneal@45 9477
bgneal@45 9478 function collectSiblings(node, name, end_node) {
bgneal@45 9479 var siblings = [];
bgneal@45 9480
bgneal@45 9481 for (; node && node != end_node; node = node[name])
bgneal@45 9482 siblings.push(node);
bgneal@45 9483
bgneal@45 9484 return siblings;
bgneal@45 9485 };
bgneal@45 9486
bgneal@45 9487 function findEndPoint(node, root) {
bgneal@45 9488 do {
bgneal@45 9489 if (node.parentNode == root)
bgneal@45 9490 return node;
bgneal@45 9491
bgneal@45 9492 node = node.parentNode;
bgneal@45 9493 } while(node);
bgneal@45 9494 };
bgneal@45 9495
bgneal@45 9496 function walkBoundary(start_node, end_node, next) {
bgneal@45 9497 var siblingName = next ? 'nextSibling' : 'previousSibling';
bgneal@45 9498
bgneal@45 9499 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
bgneal@45 9500 parent = node.parentNode;
bgneal@45 9501 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
bgneal@45 9502
bgneal@45 9503 if (siblings.length) {
bgneal@45 9504 if (!next)
bgneal@45 9505 siblings.reverse();
bgneal@45 9506
bgneal@45 9507 callback(exclude(siblings));
bgneal@45 9508 }
bgneal@45 9509 }
bgneal@45 9510 };
bgneal@45 9511
bgneal@45 9512 // If index based start position then resolve it
bgneal@45 9513 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
bgneal@45 9514 startContainer = startContainer.childNodes[startOffset];
bgneal@45 9515
bgneal@45 9516 // If index based end position then resolve it
bgneal@45 9517 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
bgneal@45 9518 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
bgneal@45 9519
bgneal@45 9520 // Same container
bgneal@45 9521 if (startContainer == endContainer)
bgneal@45 9522 return callback(exclude([startContainer]));
bgneal@45 9523
bgneal@45 9524 // Find common ancestor and end points
bgneal@45 9525 ancestor = dom.findCommonAncestor(startContainer, endContainer);
bgneal@45 9526
bgneal@45 9527 // Process left side
bgneal@45 9528 for (node = startContainer; node; node = node.parentNode) {
bgneal@45 9529 if (node === endContainer)
bgneal@45 9530 return walkBoundary(startContainer, ancestor, true);
bgneal@45 9531
bgneal@45 9532 if (node === ancestor)
bgneal@45 9533 break;
bgneal@45 9534 }
bgneal@45 9535
bgneal@45 9536 // Process right side
bgneal@45 9537 for (node = endContainer; node; node = node.parentNode) {
bgneal@45 9538 if (node === startContainer)
bgneal@45 9539 return walkBoundary(endContainer, ancestor);
bgneal@45 9540
bgneal@45 9541 if (node === ancestor)
bgneal@45 9542 break;
bgneal@45 9543 }
bgneal@45 9544
bgneal@45 9545 // Find start/end point
bgneal@45 9546 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
bgneal@45 9547 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
bgneal@45 9548
bgneal@45 9549 // Walk left leaf
bgneal@45 9550 walkBoundary(startContainer, startPoint, true);
bgneal@45 9551
bgneal@45 9552 // Walk the middle from start to end point
bgneal@45 9553 siblings = collectSiblings(
bgneal@45 9554 startPoint == startContainer ? startPoint : startPoint.nextSibling,
bgneal@45 9555 'nextSibling',
bgneal@45 9556 endPoint == endContainer ? endPoint.nextSibling : endPoint
bgneal@45 9557 );
bgneal@45 9558
bgneal@45 9559 if (siblings.length)
bgneal@45 9560 callback(exclude(siblings));
bgneal@45 9561
bgneal@45 9562 // Walk right leaf
bgneal@45 9563 walkBoundary(endContainer, endPoint);
bgneal@45 9564 };
bgneal@45 9565
bgneal@45 9566 this.split = function(rng) {
bgneal@45 9567 var startContainer = rng.startContainer,
bgneal@45 9568 startOffset = rng.startOffset,
bgneal@45 9569 endContainer = rng.endContainer,
bgneal@45 9570 endOffset = rng.endOffset;
bgneal@45 9571
bgneal@45 9572 function splitText(node, offset) {
bgneal@45 9573 return node.splitText(offset);
bgneal@45 9574 };
bgneal@45 9575
bgneal@45 9576 // Handle single text node
bgneal@45 9577 if (startContainer == endContainer && startContainer.nodeType == 3) {
bgneal@45 9578 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
bgneal@45 9579 endContainer = splitText(startContainer, startOffset);
bgneal@45 9580 startContainer = endContainer.previousSibling;
bgneal@45 9581
bgneal@45 9582 if (endOffset > startOffset) {
bgneal@45 9583 endOffset = endOffset - startOffset;
bgneal@45 9584 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@45 9585 endOffset = endContainer.nodeValue.length;
bgneal@45 9586 startOffset = 0;
bgneal@45 9587 } else {
bgneal@45 9588 endOffset = 0;
bgneal@45 9589 }
bgneal@45 9590 }
bgneal@45 9591 } else {
bgneal@45 9592 // Split startContainer text node if needed
bgneal@45 9593 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
bgneal@45 9594 startContainer = splitText(startContainer, startOffset);
bgneal@45 9595 startOffset = 0;
bgneal@45 9596 }
bgneal@45 9597
bgneal@45 9598 // Split endContainer text node if needed
bgneal@45 9599 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
bgneal@45 9600 endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@45 9601 endOffset = endContainer.nodeValue.length;
bgneal@45 9602 }
bgneal@45 9603 }
bgneal@45 9604
bgneal@45 9605 return {
bgneal@45 9606 startContainer : startContainer,
bgneal@45 9607 startOffset : startOffset,
bgneal@45 9608 endContainer : endContainer,
bgneal@45 9609 endOffset : endOffset
bgneal@45 9610 };
bgneal@45 9611 };
bgneal@45 9612
bgneal@45 9613 };
bgneal@45 9614
bgneal@45 9615 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
bgneal@45 9616 if (rng1 && rng2) {
bgneal@45 9617 // Compare native IE ranges
bgneal@45 9618 if (rng1.item || rng1.duplicate) {
bgneal@45 9619 // Both are control ranges and the selected element matches
bgneal@45 9620 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
bgneal@45 9621 return true;
bgneal@45 9622
bgneal@45 9623 // Both are text ranges and the range matches
bgneal@45 9624 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
bgneal@45 9625 return true;
bgneal@45 9626 } else {
bgneal@45 9627 // Compare w3c ranges
bgneal@45 9628 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
bgneal@45 9629 }
bgneal@45 9630 }
bgneal@45 9631
bgneal@45 9632 return false;
bgneal@45 9633 };
bgneal@45 9634 })(tinymce);
bgneal@45 9635
bgneal@45 9636 (function(tinymce) {
bgneal@45 9637 var Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 9638
bgneal@45 9639 tinymce.create('tinymce.ui.KeyboardNavigation', {
bgneal@45 9640 KeyboardNavigation: function(settings, dom) {
bgneal@45 9641 var t = this, root = settings.root, items = settings.items,
bgneal@45 9642 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
bgneal@45 9643 excludeFromTabOrder = settings.excludeFromTabOrder,
bgneal@45 9644 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
bgneal@45 9645
bgneal@45 9646 dom = dom || tinymce.DOM;
bgneal@45 9647
bgneal@45 9648 itemFocussed = function(evt) {
bgneal@45 9649 focussedId = evt.target.id;
bgneal@45 9650 };
bgneal@45 9651
bgneal@45 9652 itemBlurred = function(evt) {
bgneal@45 9653 dom.setAttrib(evt.target.id, 'tabindex', '-1');
bgneal@45 9654 };
bgneal@45 9655
bgneal@45 9656 rootFocussed = function(evt) {
bgneal@45 9657 var item = dom.get(focussedId);
bgneal@45 9658 dom.setAttrib(item, 'tabindex', '0');
bgneal@45 9659 item.focus();
bgneal@45 9660 };
bgneal@45 9661
bgneal@45 9662 t.focus = function() {
bgneal@45 9663 dom.get(focussedId).focus();
bgneal@45 9664 };
bgneal@45 9665
bgneal@45 9666 t.destroy = function() {
bgneal@45 9667 each(items, function(item) {
bgneal@45 9668 dom.unbind(dom.get(item.id), 'focus', itemFocussed);
bgneal@45 9669 dom.unbind(dom.get(item.id), 'blur', itemBlurred);
bgneal@45 9670 });
bgneal@45 9671
bgneal@45 9672 dom.unbind(dom.get(root), 'focus', rootFocussed);
bgneal@45 9673 dom.unbind(dom.get(root), 'keydown', rootKeydown);
bgneal@45 9674
bgneal@45 9675 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
bgneal@45 9676 t.destroy = function() {};
bgneal@45 9677 };
bgneal@45 9678
bgneal@45 9679 t.moveFocus = function(dir, evt) {
bgneal@45 9680 var idx = -1, controls = t.controls, newFocus;
bgneal@45 9681
bgneal@45 9682 if (!focussedId)
bgneal@45 9683 return;
bgneal@45 9684
bgneal@45 9685 each(items, function(item, index) {
bgneal@45 9686 if (item.id === focussedId) {
bgneal@45 9687 idx = index;
bgneal@45 9688 return false;
bgneal@45 9689 }
bgneal@45 9690 });
bgneal@45 9691
bgneal@45 9692 idx += dir;
bgneal@45 9693 if (idx < 0) {
bgneal@45 9694 idx = items.length - 1;
bgneal@45 9695 } else if (idx >= items.length) {
bgneal@45 9696 idx = 0;
bgneal@45 9697 }
bgneal@45 9698
bgneal@45 9699 newFocus = items[idx];
bgneal@45 9700 dom.setAttrib(focussedId, 'tabindex', '-1');
bgneal@45 9701 dom.setAttrib(newFocus.id, 'tabindex', '0');
bgneal@45 9702 dom.get(newFocus.id).focus();
bgneal@45 9703
bgneal@45 9704 if (settings.actOnFocus) {
bgneal@45 9705 settings.onAction(newFocus.id);
bgneal@45 9706 }
bgneal@45 9707
bgneal@45 9708 if (evt)
bgneal@45 9709 Event.cancel(evt);
bgneal@45 9710 };
bgneal@45 9711
bgneal@45 9712 rootKeydown = function(evt) {
bgneal@45 9713 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
bgneal@45 9714
bgneal@45 9715 switch (evt.keyCode) {
bgneal@45 9716 case DOM_VK_LEFT:
bgneal@45 9717 if (enableLeftRight) t.moveFocus(-1);
bgneal@45 9718 break;
bgneal@45 9719
bgneal@45 9720 case DOM_VK_RIGHT:
bgneal@45 9721 if (enableLeftRight) t.moveFocus(1);
bgneal@45 9722 break;
bgneal@45 9723
bgneal@45 9724 case DOM_VK_UP:
bgneal@45 9725 if (enableUpDown) t.moveFocus(-1);
bgneal@45 9726 break;
bgneal@45 9727
bgneal@45 9728 case DOM_VK_DOWN:
bgneal@45 9729 if (enableUpDown) t.moveFocus(1);
bgneal@45 9730 break;
bgneal@45 9731
bgneal@45 9732 case DOM_VK_ESCAPE:
bgneal@45 9733 if (settings.onCancel) {
bgneal@45 9734 settings.onCancel();
bgneal@45 9735 Event.cancel(evt);
bgneal@45 9736 }
bgneal@45 9737 break;
bgneal@45 9738
bgneal@45 9739 case DOM_VK_ENTER:
bgneal@45 9740 case DOM_VK_RETURN:
bgneal@45 9741 case DOM_VK_SPACE:
bgneal@45 9742 if (settings.onAction) {
bgneal@45 9743 settings.onAction(focussedId);
bgneal@45 9744 Event.cancel(evt);
bgneal@45 9745 }
bgneal@45 9746 break;
bgneal@45 9747 }
bgneal@45 9748 };
bgneal@45 9749
bgneal@45 9750 // Set up state and listeners for each item.
bgneal@45 9751 each(items, function(item, idx) {
bgneal@45 9752 var tabindex;
bgneal@45 9753
bgneal@45 9754 if (!item.id) {
bgneal@45 9755 item.id = dom.uniqueId('_mce_item_');
bgneal@45 9756 }
bgneal@45 9757
bgneal@45 9758 if (excludeFromTabOrder) {
bgneal@45 9759 dom.bind(item.id, 'blur', itemBlurred);
bgneal@45 9760 tabindex = '-1';
bgneal@45 9761 } else {
bgneal@45 9762 tabindex = (idx === 0 ? '0' : '-1');
bgneal@45 9763 }
bgneal@45 9764
bgneal@45 9765 dom.setAttrib(item.id, 'tabindex', tabindex);
bgneal@45 9766 dom.bind(dom.get(item.id), 'focus', itemFocussed);
bgneal@45 9767 });
bgneal@45 9768
bgneal@45 9769 // Setup initial state for root element.
bgneal@45 9770 if (items[0]){
bgneal@45 9771 focussedId = items[0].id;
bgneal@45 9772 }
bgneal@45 9773
bgneal@45 9774 dom.setAttrib(root, 'tabindex', '-1');
bgneal@45 9775
bgneal@45 9776 // Setup listeners for root element.
bgneal@45 9777 dom.bind(dom.get(root), 'focus', rootFocussed);
bgneal@45 9778 dom.bind(dom.get(root), 'keydown', rootKeydown);
bgneal@45 9779 }
bgneal@45 9780 });
bgneal@45 9781 })(tinymce);
bgneal@45 9782
bgneal@45 9783 (function(tinymce) {
bgneal@45 9784 // Shorten class names
bgneal@45 9785 var DOM = tinymce.DOM, is = tinymce.is;
bgneal@45 9786
bgneal@45 9787 tinymce.create('tinymce.ui.Control', {
bgneal@45 9788 Control : function(id, s, editor) {
bgneal@45 9789 this.id = id;
bgneal@45 9790 this.settings = s = s || {};
bgneal@45 9791 this.rendered = false;
bgneal@45 9792 this.onRender = new tinymce.util.Dispatcher(this);
bgneal@45 9793 this.classPrefix = '';
bgneal@45 9794 this.scope = s.scope || this;
bgneal@45 9795 this.disabled = 0;
bgneal@45 9796 this.active = 0;
bgneal@45 9797 this.editor = editor;
bgneal@45 9798 },
bgneal@45 9799
bgneal@45 9800 setAriaProperty : function(property, value) {
bgneal@45 9801 var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
bgneal@45 9802 if (element) {
bgneal@45 9803 DOM.setAttrib(element, 'aria-' + property, !!value);
bgneal@45 9804 }
bgneal@45 9805 },
bgneal@45 9806
bgneal@45 9807 focus : function() {
bgneal@45 9808 DOM.get(this.id).focus();
bgneal@45 9809 },
bgneal@45 9810
bgneal@45 9811 setDisabled : function(s) {
bgneal@45 9812 if (s != this.disabled) {
bgneal@45 9813 this.setAriaProperty('disabled', s);
bgneal@45 9814
bgneal@45 9815 this.setState('Disabled', s);
bgneal@45 9816 this.setState('Enabled', !s);
bgneal@45 9817 this.disabled = s;
bgneal@45 9818 }
bgneal@45 9819 },
bgneal@45 9820
bgneal@45 9821 isDisabled : function() {
bgneal@45 9822 return this.disabled;
bgneal@45 9823 },
bgneal@45 9824
bgneal@45 9825 setActive : function(s) {
bgneal@45 9826 if (s != this.active) {
bgneal@45 9827 this.setState('Active', s);
bgneal@45 9828 this.active = s;
bgneal@45 9829 this.setAriaProperty('pressed', s);
bgneal@45 9830 }
bgneal@45 9831 },
bgneal@45 9832
bgneal@45 9833 isActive : function() {
bgneal@45 9834 return this.active;
bgneal@45 9835 },
bgneal@45 9836
bgneal@45 9837 setState : function(c, s) {
bgneal@45 9838 var n = DOM.get(this.id);
bgneal@45 9839
bgneal@45 9840 c = this.classPrefix + c;
bgneal@45 9841
bgneal@45 9842 if (s)
bgneal@45 9843 DOM.addClass(n, c);
bgneal@45 9844 else
bgneal@45 9845 DOM.removeClass(n, c);
bgneal@45 9846 },
bgneal@45 9847
bgneal@45 9848 isRendered : function() {
bgneal@45 9849 return this.rendered;
bgneal@45 9850 },
bgneal@45 9851
bgneal@45 9852 renderHTML : function() {
bgneal@45 9853 },
bgneal@45 9854
bgneal@45 9855 renderTo : function(n) {
bgneal@45 9856 DOM.setHTML(n, this.renderHTML());
bgneal@45 9857 },
bgneal@45 9858
bgneal@45 9859 postRender : function() {
bgneal@45 9860 var t = this, b;
bgneal@45 9861
bgneal@45 9862 // Set pending states
bgneal@45 9863 if (is(t.disabled)) {
bgneal@45 9864 b = t.disabled;
bgneal@45 9865 t.disabled = -1;
bgneal@45 9866 t.setDisabled(b);
bgneal@45 9867 }
bgneal@45 9868
bgneal@45 9869 if (is(t.active)) {
bgneal@45 9870 b = t.active;
bgneal@45 9871 t.active = -1;
bgneal@45 9872 t.setActive(b);
bgneal@45 9873 }
bgneal@45 9874 },
bgneal@45 9875
bgneal@45 9876 remove : function() {
bgneal@45 9877 DOM.remove(this.id);
bgneal@45 9878 this.destroy();
bgneal@45 9879 },
bgneal@45 9880
bgneal@45 9881 destroy : function() {
bgneal@45 9882 tinymce.dom.Event.clear(this.id);
bgneal@45 9883 }
bgneal@45 9884 });
bgneal@45 9885 })(tinymce);
bgneal@45 9886 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
bgneal@45 9887 Container : function(id, s, editor) {
bgneal@45 9888 this.parent(id, s, editor);
bgneal@45 9889
bgneal@45 9890 this.controls = [];
bgneal@45 9891
bgneal@45 9892 this.lookup = {};
bgneal@45 9893 },
bgneal@45 9894
bgneal@45 9895 add : function(c) {
bgneal@45 9896 this.lookup[c.id] = c;
bgneal@45 9897 this.controls.push(c);
bgneal@45 9898
bgneal@45 9899 return c;
bgneal@45 9900 },
bgneal@45 9901
bgneal@45 9902 get : function(n) {
bgneal@45 9903 return this.lookup[n];
bgneal@45 9904 }
bgneal@45 9905 });
bgneal@45 9906
bgneal@45 9907
bgneal@45 9908 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
bgneal@45 9909 Separator : function(id, s) {
bgneal@45 9910 this.parent(id, s);
bgneal@45 9911 this.classPrefix = 'mceSeparator';
bgneal@45 9912 this.setDisabled(true);
bgneal@45 9913 },
bgneal@45 9914
bgneal@45 9915 renderHTML : function() {
bgneal@45 9916 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
bgneal@45 9917 }
bgneal@45 9918 });
bgneal@45 9919
bgneal@45 9920 (function(tinymce) {
bgneal@45 9921 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 9922
bgneal@45 9923 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
bgneal@45 9924 MenuItem : function(id, s) {
bgneal@45 9925 this.parent(id, s);
bgneal@45 9926 this.classPrefix = 'mceMenuItem';
bgneal@45 9927 },
bgneal@45 9928
bgneal@45 9929 setSelected : function(s) {
bgneal@45 9930 this.setState('Selected', s);
bgneal@45 9931 this.setAriaProperty('checked', !!s);
bgneal@45 9932 this.selected = s;
bgneal@45 9933 },
bgneal@45 9934
bgneal@45 9935 isSelected : function() {
bgneal@45 9936 return this.selected;
bgneal@45 9937 },
bgneal@45 9938
bgneal@45 9939 postRender : function() {
bgneal@45 9940 var t = this;
bgneal@45 9941
bgneal@45 9942 t.parent();
bgneal@45 9943
bgneal@45 9944 // Set pending state
bgneal@45 9945 if (is(t.selected))
bgneal@45 9946 t.setSelected(t.selected);
bgneal@45 9947 }
bgneal@45 9948 });
bgneal@45 9949 })(tinymce);
bgneal@45 9950
bgneal@45 9951 (function(tinymce) {
bgneal@45 9952 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 9953
bgneal@45 9954 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
bgneal@45 9955 Menu : function(id, s) {
bgneal@45 9956 var t = this;
bgneal@45 9957
bgneal@45 9958 t.parent(id, s);
bgneal@45 9959 t.items = {};
bgneal@45 9960 t.collapsed = false;
bgneal@45 9961 t.menuCount = 0;
bgneal@45 9962 t.onAddItem = new tinymce.util.Dispatcher(this);
bgneal@45 9963 },
bgneal@45 9964
bgneal@45 9965 expand : function(d) {
bgneal@45 9966 var t = this;
bgneal@45 9967
bgneal@45 9968 if (d) {
bgneal@45 9969 walk(t, function(o) {
bgneal@45 9970 if (o.expand)
bgneal@45 9971 o.expand();
bgneal@45 9972 }, 'items', t);
bgneal@45 9973 }
bgneal@45 9974
bgneal@45 9975 t.collapsed = false;
bgneal@45 9976 },
bgneal@45 9977
bgneal@45 9978 collapse : function(d) {
bgneal@45 9979 var t = this;
bgneal@45 9980
bgneal@45 9981 if (d) {
bgneal@45 9982 walk(t, function(o) {
bgneal@45 9983 if (o.collapse)
bgneal@45 9984 o.collapse();
bgneal@45 9985 }, 'items', t);
bgneal@45 9986 }
bgneal@45 9987
bgneal@45 9988 t.collapsed = true;
bgneal@45 9989 },
bgneal@45 9990
bgneal@45 9991 isCollapsed : function() {
bgneal@45 9992 return this.collapsed;
bgneal@45 9993 },
bgneal@45 9994
bgneal@45 9995 add : function(o) {
bgneal@45 9996 if (!o.settings)
bgneal@45 9997 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
bgneal@45 9998
bgneal@45 9999 this.onAddItem.dispatch(this, o);
bgneal@45 10000
bgneal@45 10001 return this.items[o.id] = o;
bgneal@45 10002 },
bgneal@45 10003
bgneal@45 10004 addSeparator : function() {
bgneal@45 10005 return this.add({separator : true});
bgneal@45 10006 },
bgneal@45 10007
bgneal@45 10008 addMenu : function(o) {
bgneal@45 10009 if (!o.collapse)
bgneal@45 10010 o = this.createMenu(o);
bgneal@45 10011
bgneal@45 10012 this.menuCount++;
bgneal@45 10013
bgneal@45 10014 return this.add(o);
bgneal@45 10015 },
bgneal@45 10016
bgneal@45 10017 hasMenus : function() {
bgneal@45 10018 return this.menuCount !== 0;
bgneal@45 10019 },
bgneal@45 10020
bgneal@45 10021 remove : function(o) {
bgneal@45 10022 delete this.items[o.id];
bgneal@45 10023 },
bgneal@45 10024
bgneal@45 10025 removeAll : function() {
bgneal@45 10026 var t = this;
bgneal@45 10027
bgneal@45 10028 walk(t, function(o) {
bgneal@45 10029 if (o.removeAll)
bgneal@45 10030 o.removeAll();
bgneal@45 10031 else
bgneal@45 10032 o.remove();
bgneal@45 10033
bgneal@45 10034 o.destroy();
bgneal@45 10035 }, 'items', t);
bgneal@45 10036
bgneal@45 10037 t.items = {};
bgneal@45 10038 },
bgneal@45 10039
bgneal@45 10040 createMenu : function(o) {
bgneal@45 10041 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
bgneal@45 10042
bgneal@45 10043 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
bgneal@45 10044
bgneal@45 10045 return m;
bgneal@45 10046 }
bgneal@45 10047 });
bgneal@45 10048 })(tinymce);
bgneal@45 10049 (function(tinymce) {
bgneal@45 10050 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
bgneal@45 10051
bgneal@45 10052 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
bgneal@45 10053 DropMenu : function(id, s) {
bgneal@45 10054 s = s || {};
bgneal@45 10055 s.container = s.container || DOM.doc.body;
bgneal@45 10056 s.offset_x = s.offset_x || 0;
bgneal@45 10057 s.offset_y = s.offset_y || 0;
bgneal@45 10058 s.vp_offset_x = s.vp_offset_x || 0;
bgneal@45 10059 s.vp_offset_y = s.vp_offset_y || 0;
bgneal@45 10060
bgneal@45 10061 if (is(s.icons) && !s.icons)
bgneal@45 10062 s['class'] += ' mceNoIcons';
bgneal@45 10063
bgneal@45 10064 this.parent(id, s);
bgneal@45 10065 this.onShowMenu = new tinymce.util.Dispatcher(this);
bgneal@45 10066 this.onHideMenu = new tinymce.util.Dispatcher(this);
bgneal@45 10067 this.classPrefix = 'mceMenu';
bgneal@45 10068 },
bgneal@45 10069
bgneal@45 10070 createMenu : function(s) {
bgneal@45 10071 var t = this, cs = t.settings, m;
bgneal@45 10072
bgneal@45 10073 s.container = s.container || cs.container;
bgneal@45 10074 s.parent = t;
bgneal@45 10075 s.constrain = s.constrain || cs.constrain;
bgneal@45 10076 s['class'] = s['class'] || cs['class'];
bgneal@45 10077 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
bgneal@45 10078 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
bgneal@45 10079 s.keyboard_focus = cs.keyboard_focus;
bgneal@45 10080 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
bgneal@45 10081
bgneal@45 10082 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
bgneal@45 10083
bgneal@45 10084 return m;
bgneal@45 10085 },
bgneal@45 10086
bgneal@45 10087 focus : function() {
bgneal@45 10088 var t = this;
bgneal@45 10089 if (t.keyboardNav) {
bgneal@45 10090 t.keyboardNav.focus();
bgneal@45 10091 }
bgneal@45 10092 },
bgneal@45 10093
bgneal@45 10094 update : function() {
bgneal@45 10095 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
bgneal@45 10096
bgneal@45 10097 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
bgneal@45 10098 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
bgneal@45 10099
bgneal@45 10100 if (!DOM.boxModel)
bgneal@45 10101 t.element.setStyles({width : tw + 2, height : th + 2});
bgneal@45 10102 else
bgneal@45 10103 t.element.setStyles({width : tw, height : th});
bgneal@45 10104
bgneal@45 10105 if (s.max_width)
bgneal@45 10106 DOM.setStyle(co, 'width', tw);
bgneal@45 10107
bgneal@45 10108 if (s.max_height) {
bgneal@45 10109 DOM.setStyle(co, 'height', th);
bgneal@45 10110
bgneal@45 10111 if (tb.clientHeight < s.max_height)
bgneal@45 10112 DOM.setStyle(co, 'overflow', 'hidden');
bgneal@45 10113 }
bgneal@45 10114 },
bgneal@45 10115
bgneal@45 10116 showMenu : function(x, y, px) {
bgneal@45 10117 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
bgneal@45 10118
bgneal@45 10119 t.collapse(1);
bgneal@45 10120
bgneal@45 10121 if (t.isMenuVisible)
bgneal@45 10122 return;
bgneal@45 10123
bgneal@45 10124 if (!t.rendered) {
bgneal@45 10125 co = DOM.add(t.settings.container, t.renderNode());
bgneal@45 10126
bgneal@45 10127 each(t.items, function(o) {
bgneal@45 10128 o.postRender();
bgneal@45 10129 });
bgneal@45 10130
bgneal@45 10131 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 10132 } else
bgneal@45 10133 co = DOM.get('menu_' + t.id);
bgneal@45 10134
bgneal@45 10135 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
bgneal@45 10136 if (!tinymce.isOpera)
bgneal@45 10137 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
bgneal@45 10138
bgneal@45 10139 DOM.show(co);
bgneal@45 10140 t.update();
bgneal@45 10141
bgneal@45 10142 x += s.offset_x || 0;
bgneal@45 10143 y += s.offset_y || 0;
bgneal@45 10144 vp.w -= 4;
bgneal@45 10145 vp.h -= 4;
bgneal@45 10146
bgneal@45 10147 // Move inside viewport if not submenu
bgneal@45 10148 if (s.constrain) {
bgneal@45 10149 w = co.clientWidth - ot;
bgneal@45 10150 h = co.clientHeight - ot;
bgneal@45 10151 mx = vp.x + vp.w;
bgneal@45 10152 my = vp.y + vp.h;
bgneal@45 10153
bgneal@45 10154 if ((x + s.vp_offset_x + w) > mx)
bgneal@45 10155 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
bgneal@45 10156
bgneal@45 10157 if ((y + s.vp_offset_y + h) > my)
bgneal@45 10158 y = Math.max(0, (my - s.vp_offset_y) - h);
bgneal@45 10159 }
bgneal@45 10160
bgneal@45 10161 DOM.setStyles(co, {left : x , top : y});
bgneal@45 10162 t.element.update();
bgneal@45 10163
bgneal@45 10164 t.isMenuVisible = 1;
bgneal@45 10165 t.mouseClickFunc = Event.add(co, 'click', function(e) {
bgneal@45 10166 var m;
bgneal@45 10167
bgneal@45 10168 e = e.target;
bgneal@45 10169
bgneal@45 10170 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 10171 m = t.items[e.id];
bgneal@45 10172
bgneal@45 10173 if (m.isDisabled())
bgneal@45 10174 return;
bgneal@45 10175
bgneal@45 10176 dm = t;
bgneal@45 10177
bgneal@45 10178 while (dm) {
bgneal@45 10179 if (dm.hideMenu)
bgneal@45 10180 dm.hideMenu();
bgneal@45 10181
bgneal@45 10182 dm = dm.settings.parent;
bgneal@45 10183 }
bgneal@45 10184
bgneal@45 10185 if (m.settings.onclick)
bgneal@45 10186 m.settings.onclick(e);
bgneal@45 10187
bgneal@45 10188 return false; // Cancel to fix onbeforeunload problem
bgneal@45 10189 }
bgneal@45 10190 });
bgneal@45 10191
bgneal@45 10192 if (t.hasMenus()) {
bgneal@45 10193 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
bgneal@45 10194 var m, r, mi;
bgneal@45 10195
bgneal@45 10196 e = e.target;
bgneal@45 10197 if (e && (e = DOM.getParent(e, 'tr'))) {
bgneal@45 10198 m = t.items[e.id];
bgneal@45 10199
bgneal@45 10200 if (t.lastMenu)
bgneal@45 10201 t.lastMenu.collapse(1);
bgneal@45 10202
bgneal@45 10203 if (m.isDisabled())
bgneal@45 10204 return;
bgneal@45 10205
bgneal@45 10206 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 10207 //p = DOM.getPos(s.container);
bgneal@45 10208 r = DOM.getRect(e);
bgneal@45 10209 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
bgneal@45 10210 t.lastMenu = m;
bgneal@45 10211 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
bgneal@45 10212 }
bgneal@45 10213 }
bgneal@45 10214 });
bgneal@45 10215 }
bgneal@45 10216
bgneal@45 10217 Event.add(co, 'keydown', t._keyHandler, t);
bgneal@45 10218
bgneal@45 10219 t.onShowMenu.dispatch(t);
bgneal@45 10220
bgneal@45 10221 if (s.keyboard_focus) {
bgneal@45 10222 t._setupKeyboardNav();
bgneal@45 10223 }
bgneal@45 10224 },
bgneal@45 10225
bgneal@45 10226 hideMenu : function(c) {
bgneal@45 10227 var t = this, co = DOM.get('menu_' + t.id), e;
bgneal@45 10228
bgneal@45 10229 if (!t.isMenuVisible)
bgneal@45 10230 return;
bgneal@45 10231
bgneal@45 10232 if (t.keyboardNav) t.keyboardNav.destroy();
bgneal@45 10233 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 10234 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 10235 Event.remove(co, 'keydown', t._keyHandler);
bgneal@45 10236 DOM.hide(co);
bgneal@45 10237 t.isMenuVisible = 0;
bgneal@45 10238
bgneal@45 10239 if (!c)
bgneal@45 10240 t.collapse(1);
bgneal@45 10241
bgneal@45 10242 if (t.element)
bgneal@45 10243 t.element.hide();
bgneal@45 10244
bgneal@45 10245 if (e = DOM.get(t.id))
bgneal@45 10246 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
bgneal@45 10247
bgneal@45 10248 t.onHideMenu.dispatch(t);
bgneal@45 10249 },
bgneal@45 10250
bgneal@45 10251 add : function(o) {
bgneal@45 10252 var t = this, co;
bgneal@45 10253
bgneal@45 10254 o = t.parent(o);
bgneal@45 10255
bgneal@45 10256 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
bgneal@45 10257 t._add(DOM.select('tbody', co)[0], o);
bgneal@45 10258
bgneal@45 10259 return o;
bgneal@45 10260 },
bgneal@45 10261
bgneal@45 10262 collapse : function(d) {
bgneal@45 10263 this.parent(d);
bgneal@45 10264 this.hideMenu(1);
bgneal@45 10265 },
bgneal@45 10266
bgneal@45 10267 remove : function(o) {
bgneal@45 10268 DOM.remove(o.id);
bgneal@45 10269 this.destroy();
bgneal@45 10270
bgneal@45 10271 return this.parent(o);
bgneal@45 10272 },
bgneal@45 10273
bgneal@45 10274 destroy : function() {
bgneal@45 10275 var t = this, co = DOM.get('menu_' + t.id);
bgneal@45 10276
bgneal@45 10277 if (t.keyboardNav) t.keyboardNav.destroy();
bgneal@45 10278 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 10279 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
bgneal@45 10280 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 10281 Event.remove(co, 'keydown', t._keyHandler);
bgneal@45 10282
bgneal@45 10283 if (t.element)
bgneal@45 10284 t.element.remove();
bgneal@45 10285
bgneal@45 10286 DOM.remove(co);
bgneal@45 10287 },
bgneal@45 10288
bgneal@45 10289 renderNode : function() {
bgneal@45 10290 var t = this, s = t.settings, n, tb, co, w;
bgneal@45 10291
bgneal@45 10292 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
bgneal@45 10293 if (t.settings.parent) {
bgneal@45 10294 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
bgneal@45 10295 }
bgneal@45 10296 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
bgneal@45 10297 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 10298
bgneal@45 10299 if (s.menu_line)
bgneal@45 10300 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
bgneal@45 10301
bgneal@45 10302 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
bgneal@45 10303 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
bgneal@45 10304 tb = DOM.add(n, 'tbody');
bgneal@45 10305
bgneal@45 10306 each(t.items, function(o) {
bgneal@45 10307 t._add(tb, o);
bgneal@45 10308 });
bgneal@45 10309
bgneal@45 10310 t.rendered = true;
bgneal@45 10311
bgneal@45 10312 return w;
bgneal@45 10313 },
bgneal@45 10314
bgneal@45 10315 // Internal functions
bgneal@45 10316 _setupKeyboardNav : function(){
bgneal@45 10317 var contextMenu, menuItems, t=this;
bgneal@45 10318 contextMenu = DOM.get('menu_' + t.id);
bgneal@45 10319 menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
bgneal@45 10320 menuItems.splice(0,0,contextMenu);
bgneal@45 10321 t.keyboardNav = new tinymce.ui.KeyboardNavigation({
bgneal@45 10322 root: 'menu_' + t.id,
bgneal@45 10323 items: menuItems,
bgneal@45 10324 onCancel: function() {
bgneal@45 10325 t.hideMenu();
bgneal@45 10326 },
bgneal@45 10327 enableUpDown: true
bgneal@45 10328 });
bgneal@45 10329 contextMenu.focus();
bgneal@45 10330 },
bgneal@45 10331
bgneal@45 10332 _keyHandler : function(evt) {
bgneal@45 10333 var t = this, e;
bgneal@45 10334 switch (evt.keyCode) {
bgneal@45 10335 case 37: // Left
bgneal@45 10336 if (t.settings.parent) {
bgneal@45 10337 t.hideMenu();
bgneal@45 10338 t.settings.parent.focus();
bgneal@45 10339 Event.cancel(evt);
bgneal@45 10340 }
bgneal@45 10341 break;
bgneal@45 10342 case 39: // Right
bgneal@45 10343 if (t.mouseOverFunc)
bgneal@45 10344 t.mouseOverFunc(evt);
bgneal@45 10345 break;
bgneal@45 10346 }
bgneal@45 10347 },
bgneal@45 10348
bgneal@45 10349 _add : function(tb, o) {
bgneal@45 10350 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
bgneal@45 10351
bgneal@45 10352 if (s.separator) {
bgneal@45 10353 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
bgneal@45 10354 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
bgneal@45 10355
bgneal@45 10356 if (n = ro.previousSibling)
bgneal@45 10357 DOM.addClass(n, 'mceLast');
bgneal@45 10358
bgneal@45 10359 return;
bgneal@45 10360 }
bgneal@45 10361
bgneal@45 10362 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
bgneal@45 10363 n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
bgneal@45 10364 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
bgneal@45 10365
bgneal@45 10366 if (s.parent) {
bgneal@45 10367 DOM.setAttrib(a, 'aria-haspopup', 'true');
bgneal@45 10368 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
bgneal@45 10369 }
bgneal@45 10370
bgneal@45 10371 DOM.addClass(it, s['class']);
bgneal@45 10372 // n = DOM.add(n, 'span', {'class' : 'item'});
bgneal@45 10373
bgneal@45 10374 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
bgneal@45 10375
bgneal@45 10376 if (s.icon_src)
bgneal@45 10377 DOM.add(ic, 'img', {src : s.icon_src});
bgneal@45 10378
bgneal@45 10379 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
bgneal@45 10380
bgneal@45 10381 if (o.settings.style) {
bgneal@45 10382 if (typeof o.settings.style == "function")
bgneal@45 10383 o.settings.style = o.settings.style();
bgneal@45 10384
bgneal@45 10385 DOM.setAttrib(n, 'style', o.settings.style);
bgneal@45 10386 }
bgneal@45 10387
bgneal@45 10388 if (tb.childNodes.length == 1)
bgneal@45 10389 DOM.addClass(ro, 'mceFirst');
bgneal@45 10390
bgneal@45 10391 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
bgneal@45 10392 DOM.addClass(ro, 'mceFirst');
bgneal@45 10393
bgneal@45 10394 if (o.collapse)
bgneal@45 10395 DOM.addClass(ro, cp + 'ItemSub');
bgneal@45 10396
bgneal@45 10397 if (n = ro.previousSibling)
bgneal@45 10398 DOM.removeClass(n, 'mceLast');
bgneal@45 10399
bgneal@45 10400 DOM.addClass(ro, 'mceLast');
bgneal@45 10401 }
bgneal@45 10402 });
bgneal@45 10403 })(tinymce);
bgneal@45 10404 (function(tinymce) {
bgneal@45 10405 var DOM = tinymce.DOM;
bgneal@45 10406
bgneal@45 10407 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
bgneal@45 10408 Button : function(id, s, ed) {
bgneal@45 10409 this.parent(id, s, ed);
bgneal@45 10410 this.classPrefix = 'mceButton';
bgneal@45 10411 },
bgneal@45 10412
bgneal@45 10413 renderHTML : function() {
bgneal@45 10414 var cp = this.classPrefix, s = this.settings, h, l;
bgneal@45 10415
bgneal@45 10416 l = DOM.encode(s.label || '');
bgneal@45 10417 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
bgneal@45 10418 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
bgneal@45 10419 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
bgneal@45 10420 else
bgneal@45 10421 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
bgneal@45 10422
bgneal@45 10423 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
bgneal@45 10424 h += '</a>';
bgneal@45 10425 return h;
bgneal@45 10426 },
bgneal@45 10427
bgneal@45 10428 postRender : function() {
bgneal@45 10429 var t = this, s = t.settings, imgBookmark;
bgneal@45 10430
bgneal@45 10431 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
bgneal@45 10432 // need to keep the selection in case the selection is lost
bgneal@45 10433 if (tinymce.isIE && t.editor) {
bgneal@45 10434 tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
bgneal@45 10435 var nodeName = t.editor.selection.getNode().nodeName;
bgneal@45 10436 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
bgneal@45 10437 });
bgneal@45 10438 }
bgneal@45 10439 tinymce.dom.Event.add(t.id, 'click', function(e) {
bgneal@45 10440 if (!t.isDisabled()) {
bgneal@45 10441 // restore the selection in case the selection is lost in IE
bgneal@45 10442 if (tinymce.isIE && t.editor && imgBookmark !== null) {
bgneal@45 10443 t.editor.selection.moveToBookmark(imgBookmark);
bgneal@45 10444 }
bgneal@45 10445 return s.onclick.call(s.scope, e);
bgneal@45 10446 }
bgneal@45 10447 });
bgneal@45 10448 tinymce.dom.Event.add(t.id, 'keyup', function(e) {
bgneal@45 10449 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
bgneal@45 10450 return s.onclick.call(s.scope, e);
bgneal@45 10451 });
bgneal@45 10452 }
bgneal@45 10453 });
bgneal@45 10454 })(tinymce);
bgneal@45 10455
bgneal@45 10456 (function(tinymce) {
bgneal@45 10457 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 10458
bgneal@45 10459 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
bgneal@45 10460 ListBox : function(id, s, ed) {
bgneal@45 10461 var t = this;
bgneal@45 10462
bgneal@45 10463 t.parent(id, s, ed);
bgneal@45 10464
bgneal@45 10465 t.items = [];
bgneal@45 10466
bgneal@45 10467 t.onChange = new Dispatcher(t);
bgneal@45 10468
bgneal@45 10469 t.onPostRender = new Dispatcher(t);
bgneal@45 10470
bgneal@45 10471 t.onAdd = new Dispatcher(t);
bgneal@45 10472
bgneal@45 10473 t.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@45 10474
bgneal@45 10475 t.classPrefix = 'mceListBox';
bgneal@45 10476 t.marked = {};
bgneal@45 10477 },
bgneal@45 10478
bgneal@45 10479 select : function(va) {
bgneal@45 10480 var t = this, fv, f;
bgneal@45 10481
bgneal@45 10482 t.marked = {};
bgneal@45 10483
bgneal@45 10484 if (va == undefined)
bgneal@45 10485 return t.selectByIndex(-1);
bgneal@45 10486
bgneal@45 10487 // Is string or number make function selector
bgneal@45 10488 if (va && typeof(va)=="function")
bgneal@45 10489 f = va;
bgneal@45 10490 else {
bgneal@45 10491 f = function(v) {
bgneal@45 10492 return v == va;
bgneal@45 10493 };
bgneal@45 10494 }
bgneal@45 10495
bgneal@45 10496 // Do we need to do something?
bgneal@45 10497 if (va != t.selectedValue) {
bgneal@45 10498 // Find item
bgneal@45 10499 each(t.items, function(o, i) {
bgneal@45 10500 if (f(o.value)) {
bgneal@45 10501 fv = 1;
bgneal@45 10502 t.selectByIndex(i);
bgneal@45 10503 return false;
bgneal@45 10504 }
bgneal@45 10505 });
bgneal@45 10506
bgneal@45 10507 if (!fv)
bgneal@45 10508 t.selectByIndex(-1);
bgneal@45 10509 }
bgneal@45 10510 },
bgneal@45 10511
bgneal@45 10512 selectByIndex : function(idx) {
bgneal@45 10513 var t = this, e, o, label;
bgneal@45 10514
bgneal@45 10515 t.marked = {};
bgneal@45 10516
bgneal@45 10517 if (idx != t.selectedIndex) {
bgneal@45 10518 e = DOM.get(t.id + '_text');
bgneal@45 10519 label = DOM.get(t.id + '_voiceDesc');
bgneal@45 10520 o = t.items[idx];
bgneal@45 10521
bgneal@45 10522 if (o) {
bgneal@45 10523 t.selectedValue = o.value;
bgneal@45 10524 t.selectedIndex = idx;
bgneal@45 10525 DOM.setHTML(e, DOM.encode(o.title));
bgneal@45 10526 DOM.setHTML(label, t.settings.title + " - " + o.title);
bgneal@45 10527 DOM.removeClass(e, 'mceTitle');
bgneal@45 10528 DOM.setAttrib(t.id, 'aria-valuenow', o.title);
bgneal@45 10529 } else {
bgneal@45 10530 DOM.setHTML(e, DOM.encode(t.settings.title));
bgneal@45 10531 DOM.setHTML(label, DOM.encode(t.settings.title));
bgneal@45 10532 DOM.addClass(e, 'mceTitle');
bgneal@45 10533 t.selectedValue = t.selectedIndex = null;
bgneal@45 10534 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
bgneal@45 10535 }
bgneal@45 10536 e = 0;
bgneal@45 10537 }
bgneal@45 10538 },
bgneal@45 10539
bgneal@45 10540 mark : function(value) {
bgneal@45 10541 this.marked[value] = true;
bgneal@45 10542 },
bgneal@45 10543
bgneal@45 10544 add : function(n, v, o) {
bgneal@45 10545 var t = this;
bgneal@45 10546
bgneal@45 10547 o = o || {};
bgneal@45 10548 o = tinymce.extend(o, {
bgneal@45 10549 title : n,
bgneal@45 10550 value : v
bgneal@45 10551 });
bgneal@45 10552
bgneal@45 10553 t.items.push(o);
bgneal@45 10554 t.onAdd.dispatch(t, o);
bgneal@45 10555 },
bgneal@45 10556
bgneal@45 10557 getLength : function() {
bgneal@45 10558 return this.items.length;
bgneal@45 10559 },
bgneal@45 10560
bgneal@45 10561 renderHTML : function() {
bgneal@45 10562 var h = '', t = this, s = t.settings, cp = t.classPrefix;
bgneal@45 10563
bgneal@45 10564 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
bgneal@45 10565 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
bgneal@45 10566 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
bgneal@45 10567 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
bgneal@45 10568 h += '</tr></tbody></table></span>';
bgneal@45 10569
bgneal@45 10570 return h;
bgneal@45 10571 },
bgneal@45 10572
bgneal@45 10573 showMenu : function() {
bgneal@45 10574 var t = this, p2, e = DOM.get(this.id), m;
bgneal@45 10575
bgneal@45 10576 if (t.isDisabled() || t.items.length == 0)
bgneal@45 10577 return;
bgneal@45 10578
bgneal@45 10579 if (t.menu && t.menu.isMenuVisible)
bgneal@45 10580 return t.hideMenu();
bgneal@45 10581
bgneal@45 10582 if (!t.isMenuRendered) {
bgneal@45 10583 t.renderMenu();
bgneal@45 10584 t.isMenuRendered = true;
bgneal@45 10585 }
bgneal@45 10586
bgneal@45 10587 p2 = DOM.getPos(e);
bgneal@45 10588
bgneal@45 10589 m = t.menu;
bgneal@45 10590 m.settings.offset_x = p2.x;
bgneal@45 10591 m.settings.offset_y = p2.y;
bgneal@45 10592 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
bgneal@45 10593
bgneal@45 10594 // Select in menu
bgneal@45 10595 each(t.items, function(o) {
bgneal@45 10596 if (m.items[o.id]) {
bgneal@45 10597 m.items[o.id].setSelected(0);
bgneal@45 10598 }
bgneal@45 10599 });
bgneal@45 10600
bgneal@45 10601 each(t.items, function(o) {
bgneal@45 10602 if (m.items[o.id] && t.marked[o.value]) {
bgneal@45 10603 m.items[o.id].setSelected(1);
bgneal@45 10604 }
bgneal@45 10605
bgneal@45 10606 if (o.value === t.selectedValue) {
bgneal@45 10607 m.items[o.id].setSelected(1);
bgneal@45 10608 }
bgneal@45 10609 });
bgneal@45 10610
bgneal@45 10611 m.showMenu(0, e.clientHeight);
bgneal@45 10612
bgneal@45 10613 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 10614 DOM.addClass(t.id, t.classPrefix + 'Selected');
bgneal@45 10615
bgneal@45 10616 //DOM.get(t.id + '_text').focus();
bgneal@45 10617 },
bgneal@45 10618
bgneal@45 10619 hideMenu : function(e) {
bgneal@45 10620 var t = this;
bgneal@45 10621
bgneal@45 10622 if (t.menu && t.menu.isMenuVisible) {
bgneal@45 10623 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@45 10624
bgneal@45 10625 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 10626 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
bgneal@45 10627 return;
bgneal@45 10628
bgneal@45 10629 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@45 10630 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@45 10631 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 10632 t.menu.hideMenu();
bgneal@45 10633 }
bgneal@45 10634 }
bgneal@45 10635 },
bgneal@45 10636
bgneal@45 10637 renderMenu : function() {
bgneal@45 10638 var t = this, m;
bgneal@45 10639
bgneal@45 10640 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 10641 menu_line : 1,
bgneal@45 10642 'class' : t.classPrefix + 'Menu mceNoIcons',
bgneal@45 10643 max_width : 150,
bgneal@45 10644 max_height : 150
bgneal@45 10645 });
bgneal@45 10646
bgneal@45 10647 m.onHideMenu.add(function() {
bgneal@45 10648 t.hideMenu();
bgneal@45 10649 t.focus();
bgneal@45 10650 });
bgneal@45 10651
bgneal@45 10652 m.add({
bgneal@45 10653 title : t.settings.title,
bgneal@45 10654 'class' : 'mceMenuItemTitle',
bgneal@45 10655 onclick : function() {
bgneal@45 10656 if (t.settings.onselect('') !== false)
bgneal@45 10657 t.select(''); // Must be runned after
bgneal@45 10658 }
bgneal@45 10659 });
bgneal@45 10660
bgneal@45 10661 each(t.items, function(o) {
bgneal@45 10662 // No value then treat it as a title
bgneal@45 10663 if (o.value === undefined) {
bgneal@45 10664 m.add({
bgneal@45 10665 title : o.title,
bgneal@45 10666 role : "option",
bgneal@45 10667 'class' : 'mceMenuItemTitle',
bgneal@45 10668 onclick : function() {
bgneal@45 10669 if (t.settings.onselect('') !== false)
bgneal@45 10670 t.select(''); // Must be runned after
bgneal@45 10671 }
bgneal@45 10672 });
bgneal@45 10673 } else {
bgneal@45 10674 o.id = DOM.uniqueId();
bgneal@45 10675 o.role= "option";
bgneal@45 10676 o.onclick = function() {
bgneal@45 10677 if (t.settings.onselect(o.value) !== false)
bgneal@45 10678 t.select(o.value); // Must be runned after
bgneal@45 10679 };
bgneal@45 10680
bgneal@45 10681 m.add(o);
bgneal@45 10682 }
bgneal@45 10683 });
bgneal@45 10684
bgneal@45 10685 t.onRenderMenu.dispatch(t, m);
bgneal@45 10686 t.menu = m;
bgneal@45 10687 },
bgneal@45 10688
bgneal@45 10689 postRender : function() {
bgneal@45 10690 var t = this, cp = t.classPrefix;
bgneal@45 10691
bgneal@45 10692 Event.add(t.id, 'click', t.showMenu, t);
bgneal@45 10693 Event.add(t.id, 'keydown', function(evt) {
bgneal@45 10694 if (evt.keyCode == 32) { // Space
bgneal@45 10695 t.showMenu(evt);
bgneal@45 10696 Event.cancel(evt);
bgneal@45 10697 }
bgneal@45 10698 });
bgneal@45 10699 Event.add(t.id, 'focus', function() {
bgneal@45 10700 if (!t._focused) {
bgneal@45 10701 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
bgneal@45 10702 if (e.keyCode == 40) {
bgneal@45 10703 t.showMenu();
bgneal@45 10704 Event.cancel(e);
bgneal@45 10705 }
bgneal@45 10706 });
bgneal@45 10707 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
bgneal@45 10708 var v;
bgneal@45 10709 if (e.keyCode == 13) {
bgneal@45 10710 // Fake select on enter
bgneal@45 10711 v = t.selectedValue;
bgneal@45 10712 t.selectedValue = null; // Needs to be null to fake change
bgneal@45 10713 Event.cancel(e);
bgneal@45 10714 t.settings.onselect(v);
bgneal@45 10715 }
bgneal@45 10716 });
bgneal@45 10717 }
bgneal@45 10718
bgneal@45 10719 t._focused = 1;
bgneal@45 10720 });
bgneal@45 10721 Event.add(t.id, 'blur', function() {
bgneal@45 10722 Event.remove(t.id, 'keydown', t.keyDownHandler);
bgneal@45 10723 Event.remove(t.id, 'keypress', t.keyPressHandler);
bgneal@45 10724 t._focused = 0;
bgneal@45 10725 });
bgneal@45 10726
bgneal@45 10727 // Old IE doesn't have hover on all elements
bgneal@45 10728 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 10729 Event.add(t.id, 'mouseover', function() {
bgneal@45 10730 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 10731 DOM.addClass(t.id, cp + 'Hover');
bgneal@45 10732 });
bgneal@45 10733
bgneal@45 10734 Event.add(t.id, 'mouseout', function() {
bgneal@45 10735 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 10736 DOM.removeClass(t.id, cp + 'Hover');
bgneal@45 10737 });
bgneal@45 10738 }
bgneal@45 10739
bgneal@45 10740 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 10741 },
bgneal@45 10742
bgneal@45 10743 destroy : function() {
bgneal@45 10744 this.parent();
bgneal@45 10745
bgneal@45 10746 Event.clear(this.id + '_text');
bgneal@45 10747 Event.clear(this.id + '_open');
bgneal@45 10748 }
bgneal@45 10749 });
bgneal@45 10750 })(tinymce);
bgneal@45 10751
bgneal@45 10752 (function(tinymce) {
bgneal@45 10753 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 10754
bgneal@45 10755 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
bgneal@45 10756 NativeListBox : function(id, s) {
bgneal@45 10757 this.parent(id, s);
bgneal@45 10758 this.classPrefix = 'mceNativeListBox';
bgneal@45 10759 },
bgneal@45 10760
bgneal@45 10761 setDisabled : function(s) {
bgneal@45 10762 DOM.get(this.id).disabled = s;
bgneal@45 10763 this.setAriaProperty('disabled', s);
bgneal@45 10764 },
bgneal@45 10765
bgneal@45 10766 isDisabled : function() {
bgneal@45 10767 return DOM.get(this.id).disabled;
bgneal@45 10768 },
bgneal@45 10769
bgneal@45 10770 select : function(va) {
bgneal@45 10771 var t = this, fv, f;
bgneal@45 10772
bgneal@45 10773 if (va == undefined)
bgneal@45 10774 return t.selectByIndex(-1);
bgneal@45 10775
bgneal@45 10776 // Is string or number make function selector
bgneal@45 10777 if (va && typeof(va)=="function")
bgneal@45 10778 f = va;
bgneal@45 10779 else {
bgneal@45 10780 f = function(v) {
bgneal@45 10781 return v == va;
bgneal@45 10782 };
bgneal@45 10783 }
bgneal@45 10784
bgneal@45 10785 // Do we need to do something?
bgneal@45 10786 if (va != t.selectedValue) {
bgneal@45 10787 // Find item
bgneal@45 10788 each(t.items, function(o, i) {
bgneal@45 10789 if (f(o.value)) {
bgneal@45 10790 fv = 1;
bgneal@45 10791 t.selectByIndex(i);
bgneal@45 10792 return false;
bgneal@45 10793 }
bgneal@45 10794 });
bgneal@45 10795
bgneal@45 10796 if (!fv)
bgneal@45 10797 t.selectByIndex(-1);
bgneal@45 10798 }
bgneal@45 10799 },
bgneal@45 10800
bgneal@45 10801 selectByIndex : function(idx) {
bgneal@45 10802 DOM.get(this.id).selectedIndex = idx + 1;
bgneal@45 10803 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
bgneal@45 10804 },
bgneal@45 10805
bgneal@45 10806 add : function(n, v, a) {
bgneal@45 10807 var o, t = this;
bgneal@45 10808
bgneal@45 10809 a = a || {};
bgneal@45 10810 a.value = v;
bgneal@45 10811
bgneal@45 10812 if (t.isRendered())
bgneal@45 10813 DOM.add(DOM.get(this.id), 'option', a, n);
bgneal@45 10814
bgneal@45 10815 o = {
bgneal@45 10816 title : n,
bgneal@45 10817 value : v,
bgneal@45 10818 attribs : a
bgneal@45 10819 };
bgneal@45 10820
bgneal@45 10821 t.items.push(o);
bgneal@45 10822 t.onAdd.dispatch(t, o);
bgneal@45 10823 },
bgneal@45 10824
bgneal@45 10825 getLength : function() {
bgneal@45 10826 return this.items.length;
bgneal@45 10827 },
bgneal@45 10828
bgneal@45 10829 renderHTML : function() {
bgneal@45 10830 var h, t = this;
bgneal@45 10831
bgneal@45 10832 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
bgneal@45 10833
bgneal@45 10834 each(t.items, function(it) {
bgneal@45 10835 h += DOM.createHTML('option', {value : it.value}, it.title);
bgneal@45 10836 });
bgneal@45 10837
bgneal@45 10838 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
bgneal@45 10839 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
bgneal@45 10840 return h;
bgneal@45 10841 },
bgneal@45 10842
bgneal@45 10843 postRender : function() {
bgneal@45 10844 var t = this, ch, changeListenerAdded = true;
bgneal@45 10845
bgneal@45 10846 t.rendered = true;
bgneal@45 10847
bgneal@45 10848 function onChange(e) {
bgneal@45 10849 var v = t.items[e.target.selectedIndex - 1];
bgneal@45 10850
bgneal@45 10851 if (v && (v = v.value)) {
bgneal@45 10852 t.onChange.dispatch(t, v);
bgneal@45 10853
bgneal@45 10854 if (t.settings.onselect)
bgneal@45 10855 t.settings.onselect(v);
bgneal@45 10856 }
bgneal@45 10857 };
bgneal@45 10858
bgneal@45 10859 Event.add(t.id, 'change', onChange);
bgneal@45 10860
bgneal@45 10861 // Accessibility keyhandler
bgneal@45 10862 Event.add(t.id, 'keydown', function(e) {
bgneal@45 10863 var bf;
bgneal@45 10864
bgneal@45 10865 Event.remove(t.id, 'change', ch);
bgneal@45 10866 changeListenerAdded = false;
bgneal@45 10867
bgneal@45 10868 bf = Event.add(t.id, 'blur', function() {
bgneal@45 10869 if (changeListenerAdded) return;
bgneal@45 10870 changeListenerAdded = true;
bgneal@45 10871 Event.add(t.id, 'change', onChange);
bgneal@45 10872 Event.remove(t.id, 'blur', bf);
bgneal@45 10873 });
bgneal@45 10874
bgneal@45 10875 //prevent default left and right keys on chrome - so that the keyboard navigation is used.
bgneal@45 10876 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
bgneal@45 10877 return Event.prevent(e);
bgneal@45 10878 }
bgneal@45 10879
bgneal@45 10880 if (e.keyCode == 13 || e.keyCode == 32) {
bgneal@45 10881 onChange(e);
bgneal@45 10882 return Event.cancel(e);
bgneal@45 10883 }
bgneal@45 10884 });
bgneal@45 10885
bgneal@45 10886 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 10887 }
bgneal@45 10888 });
bgneal@45 10889 })(tinymce);
bgneal@45 10890
bgneal@45 10891 (function(tinymce) {
bgneal@45 10892 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 10893
bgneal@45 10894 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
bgneal@45 10895 MenuButton : function(id, s, ed) {
bgneal@45 10896 this.parent(id, s, ed);
bgneal@45 10897
bgneal@45 10898 this.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@45 10899
bgneal@45 10900 s.menu_container = s.menu_container || DOM.doc.body;
bgneal@45 10901 },
bgneal@45 10902
bgneal@45 10903 showMenu : function() {
bgneal@45 10904 var t = this, p1, p2, e = DOM.get(t.id), m;
bgneal@45 10905
bgneal@45 10906 if (t.isDisabled())
bgneal@45 10907 return;
bgneal@45 10908
bgneal@45 10909 if (!t.isMenuRendered) {
bgneal@45 10910 t.renderMenu();
bgneal@45 10911 t.isMenuRendered = true;
bgneal@45 10912 }
bgneal@45 10913
bgneal@45 10914 if (t.isMenuVisible)
bgneal@45 10915 return t.hideMenu();
bgneal@45 10916
bgneal@45 10917 p1 = DOM.getPos(t.settings.menu_container);
bgneal@45 10918 p2 = DOM.getPos(e);
bgneal@45 10919
bgneal@45 10920 m = t.menu;
bgneal@45 10921 m.settings.offset_x = p2.x;
bgneal@45 10922 m.settings.offset_y = p2.y;
bgneal@45 10923 m.settings.vp_offset_x = p2.x;
bgneal@45 10924 m.settings.vp_offset_y = p2.y;
bgneal@45 10925 m.settings.keyboard_focus = t._focused;
bgneal@45 10926 m.showMenu(0, e.clientHeight);
bgneal@45 10927
bgneal@45 10928 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 10929 t.setState('Selected', 1);
bgneal@45 10930
bgneal@45 10931 t.isMenuVisible = 1;
bgneal@45 10932 },
bgneal@45 10933
bgneal@45 10934 renderMenu : function() {
bgneal@45 10935 var t = this, m;
bgneal@45 10936
bgneal@45 10937 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 10938 menu_line : 1,
bgneal@45 10939 'class' : this.classPrefix + 'Menu',
bgneal@45 10940 icons : t.settings.icons
bgneal@45 10941 });
bgneal@45 10942
bgneal@45 10943 m.onHideMenu.add(function() {
bgneal@45 10944 t.hideMenu();
bgneal@45 10945 t.focus();
bgneal@45 10946 });
bgneal@45 10947
bgneal@45 10948 t.onRenderMenu.dispatch(t, m);
bgneal@45 10949 t.menu = m;
bgneal@45 10950 },
bgneal@45 10951
bgneal@45 10952 hideMenu : function(e) {
bgneal@45 10953 var t = this;
bgneal@45 10954
bgneal@45 10955 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 10956 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
bgneal@45 10957 return;
bgneal@45 10958
bgneal@45 10959 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@45 10960 t.setState('Selected', 0);
bgneal@45 10961 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 10962 if (t.menu)
bgneal@45 10963 t.menu.hideMenu();
bgneal@45 10964 }
bgneal@45 10965
bgneal@45 10966 t.isMenuVisible = 0;
bgneal@45 10967 },
bgneal@45 10968
bgneal@45 10969 postRender : function() {
bgneal@45 10970 var t = this, s = t.settings;
bgneal@45 10971
bgneal@45 10972 Event.add(t.id, 'click', function() {
bgneal@45 10973 if (!t.isDisabled()) {
bgneal@45 10974 if (s.onclick)
bgneal@45 10975 s.onclick(t.value);
bgneal@45 10976
bgneal@45 10977 t.showMenu();
bgneal@45 10978 }
bgneal@45 10979 });
bgneal@45 10980 }
bgneal@45 10981 });
bgneal@45 10982 })(tinymce);
bgneal@45 10983
bgneal@45 10984 (function(tinymce) {
bgneal@45 10985 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 10986
bgneal@45 10987 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
bgneal@45 10988 SplitButton : function(id, s, ed) {
bgneal@45 10989 this.parent(id, s, ed);
bgneal@45 10990 this.classPrefix = 'mceSplitButton';
bgneal@45 10991 },
bgneal@45 10992
bgneal@45 10993 renderHTML : function() {
bgneal@45 10994 var h, t = this, s = t.settings, h1;
bgneal@45 10995
bgneal@45 10996 h = '<tbody><tr>';
bgneal@45 10997
bgneal@45 10998 if (s.image)
bgneal@45 10999 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
bgneal@45 11000 else
bgneal@45 11001 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
bgneal@45 11002
bgneal@45 11003 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
bgneal@45 11004 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 11005
bgneal@45 11006 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
bgneal@45 11007 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 11008
bgneal@45 11009 h += '</tr></tbody>';
bgneal@45 11010 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
bgneal@45 11011 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
bgneal@45 11012 },
bgneal@45 11013
bgneal@45 11014 postRender : function() {
bgneal@45 11015 var t = this, s = t.settings, activate;
bgneal@45 11016
bgneal@45 11017 if (s.onclick) {
bgneal@45 11018 activate = function(evt) {
bgneal@45 11019 if (!t.isDisabled()) {
bgneal@45 11020 s.onclick(t.value);
bgneal@45 11021 Event.cancel(evt);
bgneal@45 11022 }
bgneal@45 11023 };
bgneal@45 11024 Event.add(t.id + '_action', 'click', activate);
bgneal@45 11025 Event.add(t.id, ['click', 'keydown'], function(evt) {
bgneal@45 11026 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
bgneal@45 11027 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
bgneal@45 11028 activate();
bgneal@45 11029 Event.cancel(evt);
bgneal@45 11030 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
bgneal@45 11031 t.showMenu();
bgneal@45 11032 Event.cancel(evt);
bgneal@45 11033 }
bgneal@45 11034 });
bgneal@45 11035 }
bgneal@45 11036
bgneal@45 11037 Event.add(t.id + '_open', 'click', function (evt) {
bgneal@45 11038 t.showMenu();
bgneal@45 11039 Event.cancel(evt);
bgneal@45 11040 });
bgneal@45 11041 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
bgneal@45 11042 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
bgneal@45 11043
bgneal@45 11044 // Old IE doesn't have hover on all elements
bgneal@45 11045 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 11046 Event.add(t.id, 'mouseover', function() {
bgneal@45 11047 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 11048 DOM.addClass(t.id, 'mceSplitButtonHover');
bgneal@45 11049 });
bgneal@45 11050
bgneal@45 11051 Event.add(t.id, 'mouseout', function() {
bgneal@45 11052 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 11053 DOM.removeClass(t.id, 'mceSplitButtonHover');
bgneal@45 11054 });
bgneal@45 11055 }
bgneal@45 11056 },
bgneal@45 11057
bgneal@45 11058 destroy : function() {
bgneal@45 11059 this.parent();
bgneal@45 11060
bgneal@45 11061 Event.clear(this.id + '_action');
bgneal@45 11062 Event.clear(this.id + '_open');
bgneal@45 11063 Event.clear(this.id);
bgneal@45 11064 }
bgneal@45 11065 });
bgneal@45 11066 })(tinymce);
bgneal@45 11067
bgneal@45 11068 (function(tinymce) {
bgneal@45 11069 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
bgneal@45 11070
bgneal@45 11071 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
bgneal@45 11072 ColorSplitButton : function(id, s, ed) {
bgneal@45 11073 var t = this;
bgneal@45 11074
bgneal@45 11075 t.parent(id, s, ed);
bgneal@45 11076
bgneal@45 11077 t.settings = s = tinymce.extend({
bgneal@45 11078 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
bgneal@45 11079 grid_width : 8,
bgneal@45 11080 default_color : '#888888'
bgneal@45 11081 }, t.settings);
bgneal@45 11082
bgneal@45 11083 t.onShowMenu = new tinymce.util.Dispatcher(t);
bgneal@45 11084
bgneal@45 11085 t.onHideMenu = new tinymce.util.Dispatcher(t);
bgneal@45 11086
bgneal@45 11087 t.value = s.default_color;
bgneal@45 11088 },
bgneal@45 11089
bgneal@45 11090 showMenu : function() {
bgneal@45 11091 var t = this, r, p, e, p2;
bgneal@45 11092
bgneal@45 11093 if (t.isDisabled())
bgneal@45 11094 return;
bgneal@45 11095
bgneal@45 11096 if (!t.isMenuRendered) {
bgneal@45 11097 t.renderMenu();
bgneal@45 11098 t.isMenuRendered = true;
bgneal@45 11099 }
bgneal@45 11100
bgneal@45 11101 if (t.isMenuVisible)
bgneal@45 11102 return t.hideMenu();
bgneal@45 11103
bgneal@45 11104 e = DOM.get(t.id);
bgneal@45 11105 DOM.show(t.id + '_menu');
bgneal@45 11106 DOM.addClass(e, 'mceSplitButtonSelected');
bgneal@45 11107 p2 = DOM.getPos(e);
bgneal@45 11108 DOM.setStyles(t.id + '_menu', {
bgneal@45 11109 left : p2.x,
bgneal@45 11110 top : p2.y + e.clientHeight,
bgneal@45 11111 zIndex : 200000
bgneal@45 11112 });
bgneal@45 11113 e = 0;
bgneal@45 11114
bgneal@45 11115 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 11116 t.onShowMenu.dispatch(t);
bgneal@45 11117
bgneal@45 11118 if (t._focused) {
bgneal@45 11119 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
bgneal@45 11120 if (e.keyCode == 27)
bgneal@45 11121 t.hideMenu();
bgneal@45 11122 });
bgneal@45 11123
bgneal@45 11124 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
bgneal@45 11125 }
bgneal@45 11126
bgneal@45 11127 t.isMenuVisible = 1;
bgneal@45 11128 },
bgneal@45 11129
bgneal@45 11130 hideMenu : function(e) {
bgneal@45 11131 var t = this;
bgneal@45 11132
bgneal@45 11133 if (t.isMenuVisible) {
bgneal@45 11134 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 11135 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
bgneal@45 11136 return;
bgneal@45 11137
bgneal@45 11138 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
bgneal@45 11139 DOM.removeClass(t.id, 'mceSplitButtonSelected');
bgneal@45 11140 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 11141 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
bgneal@45 11142 DOM.hide(t.id + '_menu');
bgneal@45 11143 }
bgneal@45 11144
bgneal@45 11145 t.isMenuVisible = 0;
bgneal@45 11146 t.onHideMenu.dispatch();
bgneal@45 11147 }
bgneal@45 11148 },
bgneal@45 11149
bgneal@45 11150 renderMenu : function() {
bgneal@45 11151 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
bgneal@45 11152
bgneal@45 11153 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
bgneal@45 11154 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
bgneal@45 11155 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
bgneal@45 11156
bgneal@45 11157 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
bgneal@45 11158 tb = DOM.add(n, 'tbody');
bgneal@45 11159
bgneal@45 11160 // Generate color grid
bgneal@45 11161 i = 0;
bgneal@45 11162 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
bgneal@45 11163 c = c.replace(/^#/, '');
bgneal@45 11164
bgneal@45 11165 if (!i--) {
bgneal@45 11166 tr = DOM.add(tb, 'tr');
bgneal@45 11167 i = s.grid_width - 1;
bgneal@45 11168 }
bgneal@45 11169
bgneal@45 11170 n = DOM.add(tr, 'td');
bgneal@45 11171 var settings = {
bgneal@45 11172 href : 'javascript:;',
bgneal@45 11173 style : {
bgneal@45 11174 backgroundColor : '#' + c
bgneal@45 11175 },
bgneal@45 11176 'title': t.editor.getLang('colors.' + c, c),
bgneal@45 11177 'data-mce-color' : '#' + c
bgneal@45 11178 };
bgneal@45 11179
bgneal@45 11180 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
bgneal@45 11181 if (!tinymce.isIE ) {
bgneal@45 11182 settings['role']= 'option';
bgneal@45 11183 }
bgneal@45 11184
bgneal@45 11185 n = DOM.add(n, 'a', settings);
bgneal@45 11186
bgneal@45 11187 if (t.editor.forcedHighContrastMode) {
bgneal@45 11188 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
bgneal@45 11189 if (n.getContext && (context = n.getContext("2d"))) {
bgneal@45 11190 context.fillStyle = '#' + c;
bgneal@45 11191 context.fillRect(0, 0, 16, 16);
bgneal@45 11192 } else {
bgneal@45 11193 // No point leaving a canvas element around if it's not supported for drawing on anyway.
bgneal@45 11194 DOM.remove(n);
bgneal@45 11195 }
bgneal@45 11196 }
bgneal@45 11197 });
bgneal@45 11198
bgneal@45 11199 if (s.more_colors_func) {
bgneal@45 11200 n = DOM.add(tb, 'tr');
bgneal@45 11201 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
bgneal@45 11202 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
bgneal@45 11203
bgneal@45 11204 Event.add(n, 'click', function(e) {
bgneal@45 11205 s.more_colors_func.call(s.more_colors_scope || this);
bgneal@45 11206 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@45 11207 });
bgneal@45 11208 }
bgneal@45 11209
bgneal@45 11210 DOM.addClass(m, 'mceColorSplitMenu');
bgneal@45 11211
bgneal@45 11212 new tinymce.ui.KeyboardNavigation({
bgneal@45 11213 root: t.id + '_menu',
bgneal@45 11214 items: DOM.select('a', t.id + '_menu'),
bgneal@45 11215 onCancel: function() {
bgneal@45 11216 t.hideMenu();
bgneal@45 11217 t.focus();
bgneal@45 11218 }
bgneal@45 11219 });
bgneal@45 11220
bgneal@45 11221 // Prevent IE from scrolling and hindering click to occur #4019
bgneal@45 11222 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
bgneal@45 11223
bgneal@45 11224 Event.add(t.id + '_menu', 'click', function(e) {
bgneal@45 11225 var c;
bgneal@45 11226
bgneal@45 11227 e = DOM.getParent(e.target, 'a', tb);
bgneal@45 11228
bgneal@45 11229 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
bgneal@45 11230 t.setColor(c);
bgneal@45 11231
bgneal@45 11232 return false; // Prevent IE auto save warning
bgneal@45 11233 });
bgneal@45 11234
bgneal@45 11235 return w;
bgneal@45 11236 },
bgneal@45 11237
bgneal@45 11238 setColor : function(c) {
bgneal@45 11239 this.displayColor(c);
bgneal@45 11240 this.hideMenu();
bgneal@45 11241 this.settings.onselect(c);
bgneal@45 11242 },
bgneal@45 11243
bgneal@45 11244 displayColor : function(c) {
bgneal@45 11245 var t = this;
bgneal@45 11246
bgneal@45 11247 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
bgneal@45 11248
bgneal@45 11249 t.value = c;
bgneal@45 11250 },
bgneal@45 11251
bgneal@45 11252 postRender : function() {
bgneal@45 11253 var t = this, id = t.id;
bgneal@45 11254
bgneal@45 11255 t.parent();
bgneal@45 11256 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
bgneal@45 11257 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
bgneal@45 11258 },
bgneal@45 11259
bgneal@45 11260 destroy : function() {
bgneal@45 11261 this.parent();
bgneal@45 11262
bgneal@45 11263 Event.clear(this.id + '_menu');
bgneal@45 11264 Event.clear(this.id + '_more');
bgneal@45 11265 DOM.remove(this.id + '_menu');
bgneal@45 11266 }
bgneal@45 11267 });
bgneal@45 11268 })(tinymce);
bgneal@45 11269
bgneal@45 11270 (function(tinymce) {
bgneal@45 11271 // Shorten class names
bgneal@45 11272 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
bgneal@45 11273 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
bgneal@45 11274 renderHTML : function() {
bgneal@45 11275 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
bgneal@45 11276
bgneal@45 11277 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
bgneal@45 11278 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
bgneal@45 11279 h.push("<span role='application'>");
bgneal@45 11280 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
bgneal@45 11281 each(controls, function(toolbar) {
bgneal@45 11282 h.push(toolbar.renderHTML());
bgneal@45 11283 });
bgneal@45 11284 h.push("</span>");
bgneal@45 11285 h.push('</div>');
bgneal@45 11286
bgneal@45 11287 return h.join('');
bgneal@45 11288 },
bgneal@45 11289
bgneal@45 11290 focus : function() {
bgneal@45 11291 var t = this;
bgneal@45 11292 dom.get(t.id).focus();
bgneal@45 11293 },
bgneal@45 11294
bgneal@45 11295 postRender : function() {
bgneal@45 11296 var t = this, items = [];
bgneal@45 11297
bgneal@45 11298 each(t.controls, function(toolbar) {
bgneal@45 11299 each (toolbar.controls, function(control) {
bgneal@45 11300 if (control.id) {
bgneal@45 11301 items.push(control);
bgneal@45 11302 }
bgneal@45 11303 });
bgneal@45 11304 });
bgneal@45 11305
bgneal@45 11306 t.keyNav = new tinymce.ui.KeyboardNavigation({
bgneal@45 11307 root: t.id,
bgneal@45 11308 items: items,
bgneal@45 11309 onCancel: function() {
bgneal@45 11310 //Move focus if webkit so that navigation back will read the item.
bgneal@45 11311 if (tinymce.isWebKit) {
bgneal@45 11312 dom.get(t.editor.id+"_ifr").focus();
bgneal@45 11313 }
bgneal@45 11314 t.editor.focus();
bgneal@45 11315 },
bgneal@45 11316 excludeFromTabOrder: !t.settings.tab_focus_toolbar
bgneal@45 11317 });
bgneal@45 11318 },
bgneal@45 11319
bgneal@45 11320 destroy : function() {
bgneal@45 11321 var self = this;
bgneal@45 11322
bgneal@45 11323 self.parent();
bgneal@45 11324 self.keyNav.destroy();
bgneal@45 11325 Event.clear(self.id);
bgneal@45 11326 }
bgneal@45 11327 });
bgneal@45 11328 })(tinymce);
bgneal@45 11329
bgneal@45 11330 (function(tinymce) {
bgneal@45 11331 // Shorten class names
bgneal@45 11332 var dom = tinymce.DOM, each = tinymce.each;
bgneal@45 11333 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
bgneal@45 11334 renderHTML : function() {
bgneal@45 11335 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
bgneal@45 11336
bgneal@45 11337 cl = t.controls;
bgneal@45 11338 for (i=0; i<cl.length; i++) {
bgneal@45 11339 // Get current control, prev control, next control and if the control is a list box or not
bgneal@45 11340 co = cl[i];
bgneal@45 11341 pr = cl[i - 1];
bgneal@45 11342 nx = cl[i + 1];
bgneal@45 11343
bgneal@45 11344 // Add toolbar start
bgneal@45 11345 if (i === 0) {
bgneal@45 11346 c = 'mceToolbarStart';
bgneal@45 11347
bgneal@45 11348 if (co.Button)
bgneal@45 11349 c += ' mceToolbarStartButton';
bgneal@45 11350 else if (co.SplitButton)
bgneal@45 11351 c += ' mceToolbarStartSplitButton';
bgneal@45 11352 else if (co.ListBox)
bgneal@45 11353 c += ' mceToolbarStartListBox';
bgneal@45 11354
bgneal@45 11355 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 11356 }
bgneal@45 11357
bgneal@45 11358 // Add toolbar end before list box and after the previous button
bgneal@45 11359 // This is to fix the o2k7 editor skins
bgneal@45 11360 if (pr && co.ListBox) {
bgneal@45 11361 if (pr.Button || pr.SplitButton)
bgneal@45 11362 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 11363 }
bgneal@45 11364
bgneal@45 11365 // Render control HTML
bgneal@45 11366
bgneal@45 11367 // IE 8 quick fix, needed to propertly generate a hit area for anchors
bgneal@45 11368 if (dom.stdMode)
bgneal@45 11369 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
bgneal@45 11370 else
bgneal@45 11371 h += '<td>' + co.renderHTML() + '</td>';
bgneal@45 11372
bgneal@45 11373 // Add toolbar start after list box and before the next button
bgneal@45 11374 // This is to fix the o2k7 editor skins
bgneal@45 11375 if (nx && co.ListBox) {
bgneal@45 11376 if (nx.Button || nx.SplitButton)
bgneal@45 11377 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 11378 }
bgneal@45 11379 }
bgneal@45 11380
bgneal@45 11381 c = 'mceToolbarEnd';
bgneal@45 11382
bgneal@45 11383 if (co.Button)
bgneal@45 11384 c += ' mceToolbarEndButton';
bgneal@45 11385 else if (co.SplitButton)
bgneal@45 11386 c += ' mceToolbarEndSplitButton';
bgneal@45 11387 else if (co.ListBox)
bgneal@45 11388 c += ' mceToolbarEndListBox';
bgneal@45 11389
bgneal@45 11390 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 11391
bgneal@45 11392 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
bgneal@45 11393 }
bgneal@45 11394 });
bgneal@45 11395 })(tinymce);
bgneal@45 11396
bgneal@45 11397 (function(tinymce) {
bgneal@45 11398 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
bgneal@45 11399
bgneal@45 11400 tinymce.create('tinymce.AddOnManager', {
bgneal@45 11401 AddOnManager : function() {
bgneal@45 11402 var self = this;
bgneal@45 11403
bgneal@45 11404 self.items = [];
bgneal@45 11405 self.urls = {};
bgneal@45 11406 self.lookup = {};
bgneal@45 11407 self.onAdd = new Dispatcher(self);
bgneal@45 11408 },
bgneal@45 11409
bgneal@45 11410 get : function(n) {
bgneal@45 11411 if (this.lookup[n]) {
bgneal@45 11412 return this.lookup[n].instance;
bgneal@45 11413 } else {
bgneal@45 11414 return undefined;
bgneal@45 11415 }
bgneal@45 11416 },
bgneal@45 11417
bgneal@45 11418 dependencies : function(n) {
bgneal@45 11419 var result;
bgneal@45 11420 if (this.lookup[n]) {
bgneal@45 11421 result = this.lookup[n].dependencies;
bgneal@45 11422 }
bgneal@45 11423 return result || [];
bgneal@45 11424 },
bgneal@45 11425
bgneal@45 11426 requireLangPack : function(n) {
bgneal@45 11427 var s = tinymce.settings;
bgneal@45 11428
bgneal@45 11429 if (s && s.language && s.language_load !== false)
bgneal@45 11430 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
bgneal@45 11431 },
bgneal@45 11432
bgneal@45 11433 add : function(id, o, dependencies) {
bgneal@45 11434 this.items.push(o);
bgneal@45 11435 this.lookup[id] = {instance:o, dependencies:dependencies};
bgneal@45 11436 this.onAdd.dispatch(this, id, o);
bgneal@45 11437
bgneal@45 11438 return o;
bgneal@45 11439 },
bgneal@45 11440 createUrl: function(baseUrl, dep) {
bgneal@45 11441 if (typeof dep === "object") {
bgneal@45 11442 return dep
bgneal@45 11443 } else {
bgneal@45 11444 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
bgneal@45 11445 }
bgneal@45 11446 },
bgneal@45 11447
bgneal@45 11448 addComponents: function(pluginName, scripts) {
bgneal@45 11449 var pluginUrl = this.urls[pluginName];
bgneal@45 11450 tinymce.each(scripts, function(script){
bgneal@45 11451 tinymce.ScriptLoader.add(pluginUrl+"/"+script);
bgneal@45 11452 });
bgneal@45 11453 },
bgneal@45 11454
bgneal@45 11455 load : function(n, u, cb, s) {
bgneal@45 11456 var t = this, url = u;
bgneal@45 11457
bgneal@45 11458 function loadDependencies() {
bgneal@45 11459 var dependencies = t.dependencies(n);
bgneal@45 11460 tinymce.each(dependencies, function(dep) {
bgneal@45 11461 var newUrl = t.createUrl(u, dep);
bgneal@45 11462 t.load(newUrl.resource, newUrl, undefined, undefined);
bgneal@45 11463 });
bgneal@45 11464 if (cb) {
bgneal@45 11465 if (s) {
bgneal@45 11466 cb.call(s);
bgneal@45 11467 } else {
bgneal@45 11468 cb.call(tinymce.ScriptLoader);
bgneal@45 11469 }
bgneal@45 11470 }
bgneal@45 11471 }
bgneal@45 11472
bgneal@45 11473 if (t.urls[n])
bgneal@45 11474 return;
bgneal@45 11475 if (typeof u === "object")
bgneal@45 11476 url = u.prefix + u.resource + u.suffix;
bgneal@45 11477
bgneal@45 11478 if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
bgneal@45 11479 url = tinymce.baseURL + '/' + url;
bgneal@45 11480
bgneal@45 11481 t.urls[n] = url.substring(0, url.lastIndexOf('/'));
bgneal@45 11482
bgneal@45 11483 if (t.lookup[n]) {
bgneal@45 11484 loadDependencies();
bgneal@45 11485 } else {
bgneal@45 11486 tinymce.ScriptLoader.add(url, loadDependencies, s);
bgneal@45 11487 }
bgneal@45 11488 }
bgneal@45 11489 });
bgneal@45 11490
bgneal@45 11491 // Create plugin and theme managers
bgneal@45 11492 tinymce.PluginManager = new tinymce.AddOnManager();
bgneal@45 11493 tinymce.ThemeManager = new tinymce.AddOnManager();
bgneal@45 11494 }(tinymce));
bgneal@45 11495
bgneal@45 11496 (function(tinymce) {
bgneal@45 11497 // Shorten names
bgneal@45 11498 var each = tinymce.each, extend = tinymce.extend,
bgneal@45 11499 DOM = tinymce.DOM, Event = tinymce.dom.Event,
bgneal@45 11500 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@45 11501 explode = tinymce.explode,
bgneal@45 11502 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
bgneal@45 11503
bgneal@45 11504 // Setup some URLs where the editor API is located and where the document is
bgneal@45 11505 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
bgneal@45 11506 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
bgneal@45 11507 tinymce.documentBaseURL += '/';
bgneal@45 11508
bgneal@45 11509 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
bgneal@45 11510
bgneal@45 11511 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
bgneal@45 11512
bgneal@45 11513 // Add before unload listener
bgneal@45 11514 // This was required since IE was leaking memory if you added and removed beforeunload listeners
bgneal@45 11515 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
bgneal@45 11516 tinymce.onBeforeUnload = new Dispatcher(tinymce);
bgneal@45 11517
bgneal@45 11518 // Must be on window or IE will leak if the editor is placed in frame or iframe
bgneal@45 11519 Event.add(window, 'beforeunload', function(e) {
bgneal@45 11520 tinymce.onBeforeUnload.dispatch(tinymce, e);
bgneal@45 11521 });
bgneal@45 11522
bgneal@45 11523 tinymce.onAddEditor = new Dispatcher(tinymce);
bgneal@45 11524
bgneal@45 11525 tinymce.onRemoveEditor = new Dispatcher(tinymce);
bgneal@45 11526
bgneal@45 11527 tinymce.EditorManager = extend(tinymce, {
bgneal@45 11528 editors : [],
bgneal@45 11529
bgneal@45 11530 i18n : {},
bgneal@45 11531
bgneal@45 11532 activeEditor : null,
bgneal@45 11533
bgneal@45 11534 init : function(s) {
bgneal@45 11535 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
bgneal@45 11536
bgneal@45 11537 function createId(elm) {
bgneal@45 11538 var id = elm.id;
bgneal@45 11539
bgneal@45 11540 // Use element id, or unique name or generate a unique id
bgneal@45 11541 if (!id) {
bgneal@45 11542 id = elm.name;
bgneal@45 11543
bgneal@45 11544 if (id && !DOM.get(id)) {
bgneal@45 11545 id = elm.name;
bgneal@45 11546 } else {
bgneal@45 11547 // Generate unique name
bgneal@45 11548 id = DOM.uniqueId();
bgneal@45 11549 }
bgneal@45 11550
bgneal@45 11551 elm.setAttribute('id', id);
bgneal@45 11552 }
bgneal@45 11553
bgneal@45 11554 return id;
bgneal@45 11555 };
bgneal@45 11556
bgneal@45 11557 function execCallback(se, n, s) {
bgneal@45 11558 var f = se[n];
bgneal@45 11559
bgneal@45 11560 if (!f)
bgneal@45 11561 return;
bgneal@45 11562
bgneal@45 11563 if (tinymce.is(f, 'string')) {
bgneal@45 11564 s = f.replace(/\.\w+$/, '');
bgneal@45 11565 s = s ? tinymce.resolve(s) : 0;
bgneal@45 11566 f = tinymce.resolve(f);
bgneal@45 11567 }
bgneal@45 11568
bgneal@45 11569 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
bgneal@45 11570 };
bgneal@45 11571
bgneal@45 11572 s = extend({
bgneal@45 11573 theme : "simple",
bgneal@45 11574 language : "en"
bgneal@45 11575 }, s);
bgneal@45 11576
bgneal@45 11577 t.settings = s;
bgneal@45 11578
bgneal@45 11579 // Legacy call
bgneal@45 11580 Event.bind(window, 'ready', function() {
bgneal@45 11581 var l, co;
bgneal@45 11582
bgneal@45 11583 execCallback(s, 'onpageload');
bgneal@45 11584
bgneal@45 11585 switch (s.mode) {
bgneal@45 11586 case "exact":
bgneal@45 11587 l = s.elements || '';
bgneal@45 11588
bgneal@45 11589 if(l.length > 0) {
bgneal@45 11590 each(explode(l), function(v) {
bgneal@45 11591 if (DOM.get(v)) {
bgneal@45 11592 ed = new tinymce.Editor(v, s);
bgneal@45 11593 el.push(ed);
bgneal@45 11594 ed.render(1);
bgneal@45 11595 } else {
bgneal@45 11596 each(document.forms, function(f) {
bgneal@45 11597 each(f.elements, function(e) {
bgneal@45 11598 if (e.name === v) {
bgneal@45 11599 v = 'mce_editor_' + instanceCounter++;
bgneal@45 11600 DOM.setAttrib(e, 'id', v);
bgneal@45 11601
bgneal@45 11602 ed = new tinymce.Editor(v, s);
bgneal@45 11603 el.push(ed);
bgneal@45 11604 ed.render(1);
bgneal@45 11605 }
bgneal@45 11606 });
bgneal@45 11607 });
bgneal@45 11608 }
bgneal@45 11609 });
bgneal@45 11610 }
bgneal@45 11611 break;
bgneal@45 11612
bgneal@45 11613 case "textareas":
bgneal@45 11614 case "specific_textareas":
bgneal@45 11615 function hasClass(n, c) {
bgneal@45 11616 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
bgneal@45 11617 };
bgneal@45 11618
bgneal@45 11619 each(DOM.select('textarea'), function(elm) {
bgneal@45 11620 if (s.editor_deselector && hasClass(elm, s.editor_deselector))
bgneal@45 11621 return;
bgneal@45 11622
bgneal@45 11623 if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
bgneal@45 11624 ed = new tinymce.Editor(createId(elm), s);
bgneal@45 11625 el.push(ed);
bgneal@45 11626 ed.render(1);
bgneal@45 11627 }
bgneal@45 11628 });
bgneal@45 11629 break;
bgneal@45 11630
bgneal@45 11631 default:
bgneal@45 11632 if (s.types) {
bgneal@45 11633 // Process type specific selector
bgneal@45 11634 each(s.types, function(type) {
bgneal@45 11635 each(DOM.select(type.selector), function(elm) {
bgneal@45 11636 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
bgneal@45 11637 el.push(editor);
bgneal@45 11638 editor.render(1);
bgneal@45 11639 });
bgneal@45 11640 });
bgneal@45 11641 } else if (s.selector) {
bgneal@45 11642 // Process global selector
bgneal@45 11643 each(DOM.select(s.selector), function(elm) {
bgneal@45 11644 var editor = new tinymce.Editor(createId(elm), s);
bgneal@45 11645 el.push(editor);
bgneal@45 11646 editor.render(1);
bgneal@45 11647 });
bgneal@45 11648 }
bgneal@45 11649 }
bgneal@45 11650
bgneal@45 11651 // Call onInit when all editors are initialized
bgneal@45 11652 if (s.oninit) {
bgneal@45 11653 l = co = 0;
bgneal@45 11654
bgneal@45 11655 each(el, function(ed) {
bgneal@45 11656 co++;
bgneal@45 11657
bgneal@45 11658 if (!ed.initialized) {
bgneal@45 11659 // Wait for it
bgneal@45 11660 ed.onInit.add(function() {
bgneal@45 11661 l++;
bgneal@45 11662
bgneal@45 11663 // All done
bgneal@45 11664 if (l == co)
bgneal@45 11665 execCallback(s, 'oninit');
bgneal@45 11666 });
bgneal@45 11667 } else
bgneal@45 11668 l++;
bgneal@45 11669
bgneal@45 11670 // All done
bgneal@45 11671 if (l == co)
bgneal@45 11672 execCallback(s, 'oninit');
bgneal@45 11673 });
bgneal@45 11674 }
bgneal@45 11675 });
bgneal@45 11676 },
bgneal@45 11677
bgneal@45 11678 get : function(id) {
bgneal@45 11679 if (id === undefined)
bgneal@45 11680 return this.editors;
bgneal@45 11681
bgneal@45 11682 return this.editors[id];
bgneal@45 11683 },
bgneal@45 11684
bgneal@45 11685 getInstanceById : function(id) {
bgneal@45 11686 return this.get(id);
bgneal@45 11687 },
bgneal@45 11688
bgneal@45 11689 add : function(editor) {
bgneal@45 11690 var self = this, editors = self.editors;
bgneal@45 11691
bgneal@45 11692 // Add named and index editor instance
bgneal@45 11693 editors[editor.id] = editor;
bgneal@45 11694 editors.push(editor);
bgneal@45 11695
bgneal@45 11696 self._setActive(editor);
bgneal@45 11697 self.onAddEditor.dispatch(self, editor);
bgneal@45 11698
bgneal@45 11699
bgneal@45 11700 return editor;
bgneal@45 11701 },
bgneal@45 11702
bgneal@45 11703 remove : function(editor) {
bgneal@45 11704 var t = this, i, editors = t.editors;
bgneal@45 11705
bgneal@45 11706 // Not in the collection
bgneal@45 11707 if (!editors[editor.id])
bgneal@45 11708 return null;
bgneal@45 11709
bgneal@45 11710 delete editors[editor.id];
bgneal@45 11711
bgneal@45 11712 for (i = 0; i < editors.length; i++) {
bgneal@45 11713 if (editors[i] == editor) {
bgneal@45 11714 editors.splice(i, 1);
bgneal@45 11715 break;
bgneal@45 11716 }
bgneal@45 11717 }
bgneal@45 11718
bgneal@45 11719 // Select another editor since the active one was removed
bgneal@45 11720 if (t.activeEditor == editor)
bgneal@45 11721 t._setActive(editors[0]);
bgneal@45 11722
bgneal@45 11723 editor.destroy();
bgneal@45 11724 t.onRemoveEditor.dispatch(t, editor);
bgneal@45 11725
bgneal@45 11726 return editor;
bgneal@45 11727 },
bgneal@45 11728
bgneal@45 11729 execCommand : function(c, u, v) {
bgneal@45 11730 var t = this, ed = t.get(v), w;
bgneal@45 11731
bgneal@45 11732 // Manager commands
bgneal@45 11733 switch (c) {
bgneal@45 11734 case "mceFocus":
bgneal@45 11735 ed.focus();
bgneal@45 11736 return true;
bgneal@45 11737
bgneal@45 11738 case "mceAddEditor":
bgneal@45 11739 case "mceAddControl":
bgneal@45 11740 if (!t.get(v))
bgneal@45 11741 new tinymce.Editor(v, t.settings).render();
bgneal@45 11742
bgneal@45 11743 return true;
bgneal@45 11744
bgneal@45 11745 case "mceAddFrameControl":
bgneal@45 11746 w = v.window;
bgneal@45 11747
bgneal@45 11748 // Add tinyMCE global instance and tinymce namespace to specified window
bgneal@45 11749 w.tinyMCE = tinyMCE;
bgneal@45 11750 w.tinymce = tinymce;
bgneal@45 11751
bgneal@45 11752 tinymce.DOM.doc = w.document;
bgneal@45 11753 tinymce.DOM.win = w;
bgneal@45 11754
bgneal@45 11755 ed = new tinymce.Editor(v.element_id, v);
bgneal@45 11756 ed.render();
bgneal@45 11757
bgneal@45 11758 // Fix IE memory leaks
bgneal@45 11759 if (tinymce.isIE) {
bgneal@45 11760 function clr() {
bgneal@45 11761 ed.destroy();
bgneal@45 11762 w.detachEvent('onunload', clr);
bgneal@45 11763 w = w.tinyMCE = w.tinymce = null; // IE leak
bgneal@45 11764 };
bgneal@45 11765
bgneal@45 11766 w.attachEvent('onunload', clr);
bgneal@45 11767 }
bgneal@45 11768
bgneal@45 11769 v.page_window = null;
bgneal@45 11770
bgneal@45 11771 return true;
bgneal@45 11772
bgneal@45 11773 case "mceRemoveEditor":
bgneal@45 11774 case "mceRemoveControl":
bgneal@45 11775 if (ed)
bgneal@45 11776 ed.remove();
bgneal@45 11777
bgneal@45 11778 return true;
bgneal@45 11779
bgneal@45 11780 case 'mceToggleEditor':
bgneal@45 11781 if (!ed) {
bgneal@45 11782 t.execCommand('mceAddControl', 0, v);
bgneal@45 11783 return true;
bgneal@45 11784 }
bgneal@45 11785
bgneal@45 11786 if (ed.isHidden())
bgneal@45 11787 ed.show();
bgneal@45 11788 else
bgneal@45 11789 ed.hide();
bgneal@45 11790
bgneal@45 11791 return true;
bgneal@45 11792 }
bgneal@45 11793
bgneal@45 11794 // Run command on active editor
bgneal@45 11795 if (t.activeEditor)
bgneal@45 11796 return t.activeEditor.execCommand(c, u, v);
bgneal@45 11797
bgneal@45 11798 return false;
bgneal@45 11799 },
bgneal@45 11800
bgneal@45 11801 execInstanceCommand : function(id, c, u, v) {
bgneal@45 11802 var ed = this.get(id);
bgneal@45 11803
bgneal@45 11804 if (ed)
bgneal@45 11805 return ed.execCommand(c, u, v);
bgneal@45 11806
bgneal@45 11807 return false;
bgneal@45 11808 },
bgneal@45 11809
bgneal@45 11810 triggerSave : function() {
bgneal@45 11811 each(this.editors, function(e) {
bgneal@45 11812 e.save();
bgneal@45 11813 });
bgneal@45 11814 },
bgneal@45 11815
bgneal@45 11816 addI18n : function(p, o) {
bgneal@45 11817 var lo, i18n = this.i18n;
bgneal@45 11818
bgneal@45 11819 if (!tinymce.is(p, 'string')) {
bgneal@45 11820 each(p, function(o, lc) {
bgneal@45 11821 each(o, function(o, g) {
bgneal@45 11822 each(o, function(o, k) {
bgneal@45 11823 if (g === 'common')
bgneal@45 11824 i18n[lc + '.' + k] = o;
bgneal@45 11825 else
bgneal@45 11826 i18n[lc + '.' + g + '.' + k] = o;
bgneal@45 11827 });
bgneal@45 11828 });
bgneal@45 11829 });
bgneal@45 11830 } else {
bgneal@45 11831 each(o, function(o, k) {
bgneal@45 11832 i18n[p + '.' + k] = o;
bgneal@45 11833 });
bgneal@45 11834 }
bgneal@45 11835 },
bgneal@45 11836
bgneal@45 11837 // Private methods
bgneal@45 11838
bgneal@45 11839 _setActive : function(editor) {
bgneal@45 11840 this.selectedInstance = this.activeEditor = editor;
bgneal@45 11841 }
bgneal@45 11842 });
bgneal@45 11843 })(tinymce);
bgneal@45 11844
bgneal@45 11845 (function(tinymce) {
bgneal@45 11846 // Shorten these names
bgneal@45 11847 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
bgneal@45 11848 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
bgneal@45 11849 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
bgneal@45 11850 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@45 11851 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;
bgneal@45 11852
bgneal@45 11853 tinymce.create('tinymce.Editor', {
bgneal@45 11854 Editor : function(id, s) {
bgneal@45 11855 var t = this;
bgneal@45 11856
bgneal@45 11857 t.id = t.editorId = id;
bgneal@45 11858
bgneal@45 11859 t.execCommands = {};
bgneal@45 11860 t.queryStateCommands = {};
bgneal@45 11861 t.queryValueCommands = {};
bgneal@45 11862
bgneal@45 11863 t.isNotDirty = false;
bgneal@45 11864
bgneal@45 11865 t.plugins = {};
bgneal@45 11866
bgneal@45 11867 // Add events to the editor
bgneal@45 11868 each([
bgneal@45 11869 'onPreInit',
bgneal@45 11870
bgneal@45 11871 'onBeforeRenderUI',
bgneal@45 11872
bgneal@45 11873 'onPostRender',
bgneal@45 11874
bgneal@45 11875 'onLoad',
bgneal@45 11876
bgneal@45 11877 'onInit',
bgneal@45 11878
bgneal@45 11879 'onRemove',
bgneal@45 11880
bgneal@45 11881 'onActivate',
bgneal@45 11882
bgneal@45 11883 'onDeactivate',
bgneal@45 11884
bgneal@45 11885 'onClick',
bgneal@45 11886
bgneal@45 11887 'onEvent',
bgneal@45 11888
bgneal@45 11889 'onMouseUp',
bgneal@45 11890
bgneal@45 11891 'onMouseDown',
bgneal@45 11892
bgneal@45 11893 'onDblClick',
bgneal@45 11894
bgneal@45 11895 'onKeyDown',
bgneal@45 11896
bgneal@45 11897 'onKeyUp',
bgneal@45 11898
bgneal@45 11899 'onKeyPress',
bgneal@45 11900
bgneal@45 11901 'onContextMenu',
bgneal@45 11902
bgneal@45 11903 'onSubmit',
bgneal@45 11904
bgneal@45 11905 'onReset',
bgneal@45 11906
bgneal@45 11907 'onPaste',
bgneal@45 11908
bgneal@45 11909 'onPreProcess',
bgneal@45 11910
bgneal@45 11911 'onPostProcess',
bgneal@45 11912
bgneal@45 11913 'onBeforeSetContent',
bgneal@45 11914
bgneal@45 11915 'onBeforeGetContent',
bgneal@45 11916
bgneal@45 11917 'onSetContent',
bgneal@45 11918
bgneal@45 11919 'onGetContent',
bgneal@45 11920
bgneal@45 11921 'onLoadContent',
bgneal@45 11922
bgneal@45 11923 'onSaveContent',
bgneal@45 11924
bgneal@45 11925 'onNodeChange',
bgneal@45 11926
bgneal@45 11927 'onChange',
bgneal@45 11928
bgneal@45 11929 'onBeforeExecCommand',
bgneal@45 11930
bgneal@45 11931 'onExecCommand',
bgneal@45 11932
bgneal@45 11933 'onUndo',
bgneal@45 11934
bgneal@45 11935 'onRedo',
bgneal@45 11936
bgneal@45 11937 'onVisualAid',
bgneal@45 11938
bgneal@45 11939 'onSetProgressState',
bgneal@45 11940
bgneal@45 11941 'onSetAttrib'
bgneal@45 11942 ], function(e) {
bgneal@45 11943 t[e] = new Dispatcher(t);
bgneal@45 11944 });
bgneal@45 11945
bgneal@45 11946 t.settings = s = extend({
bgneal@45 11947 id : id,
bgneal@45 11948 language : 'en',
bgneal@45 11949 docs_language : 'en',
bgneal@45 11950 theme : 'simple',
bgneal@45 11951 skin : 'default',
bgneal@45 11952 delta_width : 0,
bgneal@45 11953 delta_height : 0,
bgneal@45 11954 popup_css : '',
bgneal@45 11955 plugins : '',
bgneal@45 11956 document_base_url : tinymce.documentBaseURL,
bgneal@45 11957 add_form_submit_trigger : 1,
bgneal@45 11958 submit_patch : 1,
bgneal@45 11959 add_unload_trigger : 1,
bgneal@45 11960 convert_urls : 1,
bgneal@45 11961 relative_urls : 1,
bgneal@45 11962 remove_script_host : 1,
bgneal@45 11963 table_inline_editing : 0,
bgneal@45 11964 object_resizing : 1,
bgneal@45 11965 cleanup : 1,
bgneal@45 11966 accessibility_focus : 1,
bgneal@45 11967 custom_shortcuts : 1,
bgneal@45 11968 custom_undo_redo_keyboard_shortcuts : 1,
bgneal@45 11969 custom_undo_redo_restore_selection : 1,
bgneal@45 11970 custom_undo_redo : 1,
bgneal@45 11971 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
bgneal@45 11972 visual_table_class : 'mceItemTable',
bgneal@45 11973 visual : 1,
bgneal@45 11974 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
bgneal@45 11975 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
bgneal@45 11976 apply_source_formatting : 1,
bgneal@45 11977 directionality : 'ltr',
bgneal@45 11978 forced_root_block : 'p',
bgneal@45 11979 hidden_input : 1,
bgneal@45 11980 padd_empty_editor : 1,
bgneal@45 11981 render_ui : 1,
bgneal@45 11982 init_theme : 1,
bgneal@45 11983 force_p_newlines : 1,
bgneal@45 11984 indentation : '30px',
bgneal@45 11985 keep_styles : 1,
bgneal@45 11986 fix_table_elements : 1,
bgneal@45 11987 inline_styles : 1,
bgneal@45 11988 convert_fonts_to_spans : true,
bgneal@45 11989 indent : 'simple',
bgneal@45 11990 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside',
bgneal@45 11991 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside',
bgneal@45 11992 validate : true,
bgneal@45 11993 entity_encoding : 'named',
bgneal@45 11994 url_converter : t.convertURL,
bgneal@45 11995 url_converter_scope : t,
bgneal@45 11996 ie7_compat : true
bgneal@45 11997 }, s);
bgneal@45 11998
bgneal@45 11999 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
bgneal@45 12000 base_uri : tinyMCE.baseURI
bgneal@45 12001 });
bgneal@45 12002
bgneal@45 12003 t.baseURI = tinymce.baseURI;
bgneal@45 12004
bgneal@45 12005 t.contentCSS = [];
bgneal@45 12006
bgneal@45 12007 // Call setup
bgneal@45 12008 t.execCallback('setup', t);
bgneal@45 12009 },
bgneal@45 12010
bgneal@45 12011 render : function(nst) {
bgneal@45 12012 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
bgneal@45 12013
bgneal@45 12014 // Page is not loaded yet, wait for it
bgneal@45 12015 if (!Event.domLoaded) {
bgneal@45 12016 Event.add(window, 'ready', function() {
bgneal@45 12017 t.render();
bgneal@45 12018 });
bgneal@45 12019 return;
bgneal@45 12020 }
bgneal@45 12021
bgneal@45 12022 tinyMCE.settings = s;
bgneal@45 12023
bgneal@45 12024 // Element not found, then skip initialization
bgneal@45 12025 if (!t.getElement())
bgneal@45 12026 return;
bgneal@45 12027
bgneal@45 12028 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
bgneal@45 12029 // here since the browser says it has contentEditable support but there is no visible
bgneal@45 12030 // caret We will remove this check ones Apple implements full contentEditable support
bgneal@45 12031 if (tinymce.isIDevice && !tinymce.isIOS5)
bgneal@45 12032 return;
bgneal@45 12033
bgneal@45 12034 // Add hidden input for non input elements inside form elements
bgneal@45 12035 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
bgneal@45 12036 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
bgneal@45 12037
bgneal@45 12038 if (tinymce.WindowManager)
bgneal@45 12039 t.windowManager = new tinymce.WindowManager(t);
bgneal@45 12040
bgneal@45 12041 if (s.encoding == 'xml') {
bgneal@45 12042 t.onGetContent.add(function(ed, o) {
bgneal@45 12043 if (o.save)
bgneal@45 12044 o.content = DOM.encode(o.content);
bgneal@45 12045 });
bgneal@45 12046 }
bgneal@45 12047
bgneal@45 12048 if (s.add_form_submit_trigger) {
bgneal@45 12049 t.onSubmit.addToTop(function() {
bgneal@45 12050 if (t.initialized) {
bgneal@45 12051 t.save();
bgneal@45 12052 t.isNotDirty = 1;
bgneal@45 12053 }
bgneal@45 12054 });
bgneal@45 12055 }
bgneal@45 12056
bgneal@45 12057 if (s.add_unload_trigger) {
bgneal@45 12058 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
bgneal@45 12059 if (t.initialized && !t.destroyed && !t.isHidden())
bgneal@45 12060 t.save({format : 'raw', no_events : true});
bgneal@45 12061 });
bgneal@45 12062 }
bgneal@45 12063
bgneal@45 12064 tinymce.addUnload(t.destroy, t);
bgneal@45 12065
bgneal@45 12066 if (s.submit_patch) {
bgneal@45 12067 t.onBeforeRenderUI.add(function() {
bgneal@45 12068 var n = t.getElement().form;
bgneal@45 12069
bgneal@45 12070 if (!n)
bgneal@45 12071 return;
bgneal@45 12072
bgneal@45 12073 // Already patched
bgneal@45 12074 if (n._mceOldSubmit)
bgneal@45 12075 return;
bgneal@45 12076
bgneal@45 12077 // Check page uses id="submit" or name="submit" for it's submit button
bgneal@45 12078 if (!n.submit.nodeType && !n.submit.length) {
bgneal@45 12079 t.formElement = n;
bgneal@45 12080 n._mceOldSubmit = n.submit;
bgneal@45 12081 n.submit = function() {
bgneal@45 12082 // Save all instances
bgneal@45 12083 tinymce.triggerSave();
bgneal@45 12084 t.isNotDirty = 1;
bgneal@45 12085
bgneal@45 12086 return t.formElement._mceOldSubmit(t.formElement);
bgneal@45 12087 };
bgneal@45 12088 }
bgneal@45 12089
bgneal@45 12090 n = null;
bgneal@45 12091 });
bgneal@45 12092 }
bgneal@45 12093
bgneal@45 12094 // Load scripts
bgneal@45 12095 function loadScripts() {
bgneal@45 12096 if (s.language && s.language_load !== false)
bgneal@45 12097 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
bgneal@45 12098
bgneal@45 12099 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
bgneal@45 12100 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
bgneal@45 12101
bgneal@45 12102 each(explode(s.plugins), function(p) {
bgneal@45 12103 if (p &&!PluginManager.urls[p]) {
bgneal@45 12104 if (p.charAt(0) == '-') {
bgneal@45 12105 p = p.substr(1, p.length);
bgneal@45 12106 var dependencies = PluginManager.dependencies(p);
bgneal@45 12107 each(dependencies, function(dep) {
bgneal@45 12108 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
bgneal@45 12109 var dep = PluginManager.createUrl(defaultSettings, dep);
bgneal@45 12110 PluginManager.load(dep.resource, dep);
bgneal@45 12111
bgneal@45 12112 });
bgneal@45 12113 } else {
bgneal@45 12114 // Skip safari plugin, since it is removed as of 3.3b1
bgneal@45 12115 if (p == 'safari') {
bgneal@45 12116 return;
bgneal@45 12117 }
bgneal@45 12118 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
bgneal@45 12119 }
bgneal@45 12120 }
bgneal@45 12121 });
bgneal@45 12122
bgneal@45 12123 // Init when que is loaded
bgneal@45 12124 sl.loadQueue(function() {
bgneal@45 12125 if (!t.removed)
bgneal@45 12126 t.init();
bgneal@45 12127 });
bgneal@45 12128 };
bgneal@45 12129
bgneal@45 12130 loadScripts();
bgneal@45 12131 },
bgneal@45 12132
bgneal@45 12133 init : function() {
bgneal@45 12134 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
bgneal@45 12135
bgneal@45 12136 tinymce.add(t);
bgneal@45 12137
bgneal@45 12138 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
bgneal@45 12139
bgneal@45 12140 if (s.theme) {
bgneal@45 12141 s.theme = s.theme.replace(/-/, '');
bgneal@45 12142 o = ThemeManager.get(s.theme);
bgneal@45 12143 t.theme = new o();
bgneal@45 12144
bgneal@45 12145 if (t.theme.init && s.init_theme)
bgneal@45 12146 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
bgneal@45 12147 }
bgneal@45 12148 function initPlugin(p) {
bgneal@45 12149 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
bgneal@45 12150 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
bgneal@45 12151 each(PluginManager.dependencies(p), function(dep){
bgneal@45 12152 initPlugin(dep);
bgneal@45 12153 });
bgneal@45 12154 po = new c(t, u);
bgneal@45 12155
bgneal@45 12156 t.plugins[p] = po;
bgneal@45 12157
bgneal@45 12158 if (po.init) {
bgneal@45 12159 po.init(t, u);
bgneal@45 12160 initializedPlugins.push(p);
bgneal@45 12161 }
bgneal@45 12162 }
bgneal@45 12163 }
bgneal@45 12164
bgneal@45 12165 // Create all plugins
bgneal@45 12166 each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
bgneal@45 12167
bgneal@45 12168 // Setup popup CSS path(s)
bgneal@45 12169 if (s.popup_css !== false) {
bgneal@45 12170 if (s.popup_css)
bgneal@45 12171 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
bgneal@45 12172 else
bgneal@45 12173 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
bgneal@45 12174 }
bgneal@45 12175
bgneal@45 12176 if (s.popup_css_add)
bgneal@45 12177 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
bgneal@45 12178
bgneal@45 12179 t.controlManager = new tinymce.ControlManager(t);
bgneal@45 12180
bgneal@45 12181 if (s.custom_undo_redo) {
bgneal@45 12182 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@45 12183 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@45 12184 t.undoManager.beforeChange();
bgneal@45 12185 });
bgneal@45 12186
bgneal@45 12187 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@45 12188 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@45 12189 t.undoManager.add();
bgneal@45 12190 });
bgneal@45 12191 }
bgneal@45 12192
bgneal@45 12193 t.onExecCommand.add(function(ed, c) {
bgneal@45 12194 // Don't refresh the select lists until caret move
bgneal@45 12195 if (!/^(FontName|FontSize)$/.test(c))
bgneal@45 12196 t.nodeChanged();
bgneal@45 12197 });
bgneal@45 12198
bgneal@45 12199 // Remove ghost selections on images and tables in Gecko
bgneal@45 12200 if (isGecko) {
bgneal@45 12201 function repaint(a, o) {
bgneal@45 12202 if (!o || !o.initial)
bgneal@45 12203 t.execCommand('mceRepaint');
bgneal@45 12204 };
bgneal@45 12205
bgneal@45 12206 t.onUndo.add(repaint);
bgneal@45 12207 t.onRedo.add(repaint);
bgneal@45 12208 t.onSetContent.add(repaint);
bgneal@45 12209 }
bgneal@45 12210
bgneal@45 12211 // Enables users to override the control factory
bgneal@45 12212 t.onBeforeRenderUI.dispatch(t, t.controlManager);
bgneal@45 12213
bgneal@45 12214 // Measure box
bgneal@45 12215 if (s.render_ui) {
bgneal@45 12216 w = s.width || e.style.width || e.offsetWidth;
bgneal@45 12217 h = s.height || e.style.height || e.offsetHeight;
bgneal@45 12218 t.orgDisplay = e.style.display;
bgneal@45 12219 re = /^[0-9\.]+(|px)$/i;
bgneal@45 12220
bgneal@45 12221 if (re.test('' + w))
bgneal@45 12222 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
bgneal@45 12223
bgneal@45 12224 if (re.test('' + h))
bgneal@45 12225 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
bgneal@45 12226
bgneal@45 12227 // Render UI
bgneal@45 12228 o = t.theme.renderUI({
bgneal@45 12229 targetNode : e,
bgneal@45 12230 width : w,
bgneal@45 12231 height : h,
bgneal@45 12232 deltaWidth : s.delta_width,
bgneal@45 12233 deltaHeight : s.delta_height
bgneal@45 12234 });
bgneal@45 12235
bgneal@45 12236 t.editorContainer = o.editorContainer;
bgneal@45 12237 }
bgneal@45 12238
bgneal@45 12239
bgneal@45 12240 // User specified a document.domain value
bgneal@45 12241 if (document.domain && location.hostname != document.domain)
bgneal@45 12242 tinymce.relaxedDomain = document.domain;
bgneal@45 12243
bgneal@45 12244 // Resize editor
bgneal@45 12245 DOM.setStyles(o.sizeContainer || o.editorContainer, {
bgneal@45 12246 width : w,
bgneal@45 12247 height : h
bgneal@45 12248 });
bgneal@45 12249
bgneal@45 12250 // Load specified content CSS last
bgneal@45 12251 if (s.content_css) {
bgneal@45 12252 tinymce.each(explode(s.content_css), function(u) {
bgneal@45 12253 t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
bgneal@45 12254 });
bgneal@45 12255 }
bgneal@45 12256
bgneal@45 12257 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
bgneal@45 12258 if (h < 100)
bgneal@45 12259 h = 100;
bgneal@45 12260
bgneal@45 12261 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
bgneal@45 12262
bgneal@45 12263 // We only need to override paths if we have to
bgneal@45 12264 // IE has a bug where it remove site absolute urls to relative ones if this is specified
bgneal@45 12265 if (s.document_base_url != tinymce.documentBaseURL)
bgneal@45 12266 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
bgneal@45 12267
bgneal@45 12268 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
bgneal@45 12269 if (s.ie7_compat)
bgneal@45 12270 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
bgneal@45 12271 else
bgneal@45 12272 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
bgneal@45 12273
bgneal@45 12274 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
bgneal@45 12275
bgneal@45 12276 // Load the CSS by injecting them into the HTML this will reduce "flicker"
bgneal@45 12277 for (i = 0; i < t.contentCSS.length; i++) {
bgneal@45 12278 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
bgneal@45 12279 }
bgneal@45 12280
bgneal@45 12281 t.contentCSS = [];
bgneal@45 12282
bgneal@45 12283 bi = s.body_id || 'tinymce';
bgneal@45 12284 if (bi.indexOf('=') != -1) {
bgneal@45 12285 bi = t.getParam('body_id', '', 'hash');
bgneal@45 12286 bi = bi[t.id] || bi;
bgneal@45 12287 }
bgneal@45 12288
bgneal@45 12289 bc = s.body_class || '';
bgneal@45 12290 if (bc.indexOf('=') != -1) {
bgneal@45 12291 bc = t.getParam('body_class', '', 'hash');
bgneal@45 12292 bc = bc[t.id] || '';
bgneal@45 12293 }
bgneal@45 12294
bgneal@45 12295 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
bgneal@45 12296
bgneal@45 12297 // Domain relaxing enabled, then set document domain
bgneal@45 12298 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
bgneal@45 12299 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
bgneal@45 12300 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
bgneal@45 12301 }
bgneal@45 12302
bgneal@45 12303 // Create iframe
bgneal@45 12304 // TODO: ACC add the appropriate description on this.
bgneal@45 12305 n = DOM.add(o.iframeContainer, 'iframe', {
bgneal@45 12306 id : t.id + "_ifr",
bgneal@45 12307 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
bgneal@45 12308 frameBorder : '0',
bgneal@45 12309 allowTransparency : "true",
bgneal@45 12310 title : s.aria_label,
bgneal@45 12311 style : {
bgneal@45 12312 width : '100%',
bgneal@45 12313 height : h,
bgneal@45 12314 display : 'block' // Important for Gecko to render the iframe correctly
bgneal@45 12315 }
bgneal@45 12316 });
bgneal@45 12317
bgneal@45 12318 t.contentAreaContainer = o.iframeContainer;
bgneal@45 12319 DOM.get(o.editorContainer).style.display = t.orgDisplay;
bgneal@45 12320 DOM.get(t.id).style.display = 'none';
bgneal@45 12321 DOM.setAttrib(t.id, 'aria-hidden', true);
bgneal@45 12322
bgneal@45 12323 if (!tinymce.relaxedDomain || !u)
bgneal@45 12324 t.setupIframe();
bgneal@45 12325
bgneal@45 12326 e = n = o = null; // Cleanup
bgneal@45 12327 },
bgneal@45 12328
bgneal@45 12329 setupIframe : function() {
bgneal@45 12330 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
bgneal@45 12331
bgneal@45 12332 // Setup iframe body
bgneal@45 12333 if (!isIE || !tinymce.relaxedDomain) {
bgneal@45 12334 d.open();
bgneal@45 12335 d.write(t.iframeHTML);
bgneal@45 12336 d.close();
bgneal@45 12337
bgneal@45 12338 if (tinymce.relaxedDomain)
bgneal@45 12339 d.domain = tinymce.relaxedDomain;
bgneal@45 12340 }
bgneal@45 12341
bgneal@45 12342 // It will not steal focus while setting contentEditable
bgneal@45 12343 b = t.getBody();
bgneal@45 12344 b.disabled = true;
bgneal@45 12345
bgneal@45 12346 if (!s.readonly)
bgneal@45 12347 b.contentEditable = true;
bgneal@45 12348
bgneal@45 12349 b.disabled = false;
bgneal@45 12350
bgneal@45 12351 t.schema = new tinymce.html.Schema(s);
bgneal@45 12352
bgneal@45 12353 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
bgneal@45 12354 keep_values : true,
bgneal@45 12355 url_converter : t.convertURL,
bgneal@45 12356 url_converter_scope : t,
bgneal@45 12357 hex_colors : s.force_hex_style_colors,
bgneal@45 12358 class_filter : s.class_filter,
bgneal@45 12359 update_styles : 1,
bgneal@45 12360 fix_ie_paragraphs : 1,
bgneal@45 12361 schema : t.schema
bgneal@45 12362 });
bgneal@45 12363
bgneal@45 12364 t.parser = new tinymce.html.DomParser(s, t.schema);
bgneal@45 12365
bgneal@45 12366 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
bgneal@45 12367 if (!t.settings.allow_html_in_named_anchor) {
bgneal@45 12368 t.parser.addAttributeFilter('name', function(nodes, name) {
bgneal@45 12369 var i = nodes.length, sibling, prevSibling, parent, node;
bgneal@45 12370
bgneal@45 12371 while (i--) {
bgneal@45 12372 node = nodes[i];
bgneal@45 12373 if (node.name === 'a' && node.firstChild) {
bgneal@45 12374 parent = node.parent;
bgneal@45 12375
bgneal@45 12376 // Move children after current node
bgneal@45 12377 sibling = node.lastChild;
bgneal@45 12378 do {
bgneal@45 12379 prevSibling = sibling.prev;
bgneal@45 12380 parent.insert(sibling, node);
bgneal@45 12381 sibling = prevSibling;
bgneal@45 12382 } while (sibling);
bgneal@45 12383 }
bgneal@45 12384 }
bgneal@45 12385 });
bgneal@45 12386 }
bgneal@45 12387
bgneal@45 12388 // Convert src and href into data-mce-src, data-mce-href and data-mce-style
bgneal@45 12389 t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
bgneal@45 12390 var i = nodes.length, node, dom = t.dom, value, internalName;
bgneal@45 12391
bgneal@45 12392 while (i--) {
bgneal@45 12393 node = nodes[i];
bgneal@45 12394 value = node.attr(name);
bgneal@45 12395 internalName = 'data-mce-' + name;
bgneal@45 12396
bgneal@45 12397 // Add internal attribute if we need to we don't on a refresh of the document
bgneal@45 12398 if (!node.attributes.map[internalName]) {
bgneal@45 12399 if (name === "style")
bgneal@45 12400 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
bgneal@45 12401 else
bgneal@45 12402 node.attr(internalName, t.convertURL(value, name, node.name));
bgneal@45 12403 }
bgneal@45 12404 }
bgneal@45 12405 });
bgneal@45 12406
bgneal@45 12407 // Keep scripts from executing
bgneal@45 12408 t.parser.addNodeFilter('script', function(nodes, name) {
bgneal@45 12409 var i = nodes.length, node;
bgneal@45 12410
bgneal@45 12411 while (i--) {
bgneal@45 12412 node = nodes[i];
bgneal@45 12413 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
bgneal@45 12414 }
bgneal@45 12415 });
bgneal@45 12416
bgneal@45 12417 t.parser.addNodeFilter('#cdata', function(nodes, name) {
bgneal@45 12418 var i = nodes.length, node;
bgneal@45 12419
bgneal@45 12420 while (i--) {
bgneal@45 12421 node = nodes[i];
bgneal@45 12422 node.type = 8;
bgneal@45 12423 node.name = '#comment';
bgneal@45 12424 node.value = '[CDATA[' + node.value + ']]';
bgneal@45 12425 }
bgneal@45 12426 });
bgneal@45 12427
bgneal@45 12428 t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
bgneal@45 12429 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
bgneal@45 12430
bgneal@45 12431 while (i--) {
bgneal@45 12432 node = nodes[i];
bgneal@45 12433
bgneal@45 12434 if (node.isEmpty(nonEmptyElements))
bgneal@45 12435 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
bgneal@45 12436 }
bgneal@45 12437 });
bgneal@45 12438
bgneal@45 12439 t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
bgneal@45 12440
bgneal@45 12441 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
bgneal@45 12442
bgneal@45 12443 t.formatter = new tinymce.Formatter(this);
bgneal@45 12444
bgneal@45 12445 // Register default formats
bgneal@45 12446 t.formatter.register({
bgneal@45 12447 alignleft : [
bgneal@45 12448 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
bgneal@45 12449 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
bgneal@45 12450 ],
bgneal@45 12451
bgneal@45 12452 aligncenter : [
bgneal@45 12453 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
bgneal@45 12454 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
bgneal@45 12455 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
bgneal@45 12456 ],
bgneal@45 12457
bgneal@45 12458 alignright : [
bgneal@45 12459 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
bgneal@45 12460 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
bgneal@45 12461 ],
bgneal@45 12462
bgneal@45 12463 alignfull : [
bgneal@45 12464 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
bgneal@45 12465 ],
bgneal@45 12466
bgneal@45 12467 bold : [
bgneal@45 12468 {inline : 'strong', remove : 'all'},
bgneal@45 12469 {inline : 'span', styles : {fontWeight : 'bold'}},
bgneal@45 12470 {inline : 'b', remove : 'all'}
bgneal@45 12471 ],
bgneal@45 12472
bgneal@45 12473 italic : [
bgneal@45 12474 {inline : 'em', remove : 'all'},
bgneal@45 12475 {inline : 'span', styles : {fontStyle : 'italic'}},
bgneal@45 12476 {inline : 'i', remove : 'all'}
bgneal@45 12477 ],
bgneal@45 12478
bgneal@45 12479 underline : [
bgneal@45 12480 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
bgneal@45 12481 {inline : 'u', remove : 'all'}
bgneal@45 12482 ],
bgneal@45 12483
bgneal@45 12484 strikethrough : [
bgneal@45 12485 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
bgneal@45 12486 {inline : 'strike', remove : 'all'}
bgneal@45 12487 ],
bgneal@45 12488
bgneal@45 12489 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
bgneal@45 12490 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
bgneal@45 12491 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
bgneal@45 12492 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
bgneal@45 12493 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
bgneal@45 12494 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
bgneal@45 12495 subscript : {inline : 'sub'},
bgneal@45 12496 superscript : {inline : 'sup'},
bgneal@45 12497
bgneal@45 12498 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
bgneal@45 12499 onmatch : function(node) {
bgneal@45 12500 return true;
bgneal@45 12501 },
bgneal@45 12502
bgneal@45 12503 onformat : function(elm, fmt, vars) {
bgneal@45 12504 each(vars, function(value, key) {
bgneal@45 12505 t.dom.setAttrib(elm, key, value);
bgneal@45 12506 });
bgneal@45 12507 }
bgneal@45 12508 },
bgneal@45 12509
bgneal@45 12510 removeformat : [
bgneal@45 12511 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
bgneal@45 12512 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
bgneal@45 12513 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
bgneal@45 12514 ]
bgneal@45 12515 });
bgneal@45 12516
bgneal@45 12517 // Register default block formats
bgneal@45 12518 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
bgneal@45 12519 t.formatter.register(name, {block : name, remove : 'all'});
bgneal@45 12520 });
bgneal@45 12521
bgneal@45 12522 // Register user defined formats
bgneal@45 12523 t.formatter.register(t.settings.formats);
bgneal@45 12524
bgneal@45 12525 t.undoManager = new tinymce.UndoManager(t);
bgneal@45 12526
bgneal@45 12527 // Pass through
bgneal@45 12528 t.undoManager.onAdd.add(function(um, l) {
bgneal@45 12529 if (um.hasUndo())
bgneal@45 12530 return t.onChange.dispatch(t, l, um);
bgneal@45 12531 });
bgneal@45 12532
bgneal@45 12533 t.undoManager.onUndo.add(function(um, l) {
bgneal@45 12534 return t.onUndo.dispatch(t, l, um);
bgneal@45 12535 });
bgneal@45 12536
bgneal@45 12537 t.undoManager.onRedo.add(function(um, l) {
bgneal@45 12538 return t.onRedo.dispatch(t, l, um);
bgneal@45 12539 });
bgneal@45 12540
bgneal@45 12541 t.forceBlocks = new tinymce.ForceBlocks(t, {
bgneal@45 12542 forced_root_block : s.forced_root_block
bgneal@45 12543 });
bgneal@45 12544
bgneal@45 12545 t.editorCommands = new tinymce.EditorCommands(t);
bgneal@45 12546
bgneal@45 12547 // Pass through
bgneal@45 12548 t.serializer.onPreProcess.add(function(se, o) {
bgneal@45 12549 return t.onPreProcess.dispatch(t, o, se);
bgneal@45 12550 });
bgneal@45 12551
bgneal@45 12552 t.serializer.onPostProcess.add(function(se, o) {
bgneal@45 12553 return t.onPostProcess.dispatch(t, o, se);
bgneal@45 12554 });
bgneal@45 12555
bgneal@45 12556 t.onPreInit.dispatch(t);
bgneal@45 12557
bgneal@45 12558 if (!s.gecko_spellcheck)
bgneal@45 12559 t.getBody().spellcheck = 0;
bgneal@45 12560
bgneal@45 12561 if (!s.readonly)
bgneal@45 12562 t._addEvents();
bgneal@45 12563
bgneal@45 12564 t.controlManager.onPostRender.dispatch(t, t.controlManager);
bgneal@45 12565 t.onPostRender.dispatch(t);
bgneal@45 12566
bgneal@45 12567 t.quirks = new tinymce.util.Quirks(this);
bgneal@45 12568
bgneal@45 12569 if (s.directionality)
bgneal@45 12570 t.getBody().dir = s.directionality;
bgneal@45 12571
bgneal@45 12572 if (s.nowrap)
bgneal@45 12573 t.getBody().style.whiteSpace = "nowrap";
bgneal@45 12574
bgneal@45 12575 if (s.handle_node_change_callback) {
bgneal@45 12576 t.onNodeChange.add(function(ed, cm, n) {
bgneal@45 12577 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
bgneal@45 12578 });
bgneal@45 12579 }
bgneal@45 12580
bgneal@45 12581 if (s.save_callback) {
bgneal@45 12582 t.onSaveContent.add(function(ed, o) {
bgneal@45 12583 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 12584
bgneal@45 12585 if (h)
bgneal@45 12586 o.content = h;
bgneal@45 12587 });
bgneal@45 12588 }
bgneal@45 12589
bgneal@45 12590 if (s.onchange_callback) {
bgneal@45 12591 t.onChange.add(function(ed, l) {
bgneal@45 12592 t.execCallback('onchange_callback', t, l);
bgneal@45 12593 });
bgneal@45 12594 }
bgneal@45 12595
bgneal@45 12596 if (s.protect) {
bgneal@45 12597 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 12598 if (s.protect) {
bgneal@45 12599 each(s.protect, function(pattern) {
bgneal@45 12600 o.content = o.content.replace(pattern, function(str) {
bgneal@45 12601 return '<!--mce:protected ' + escape(str) + '-->';
bgneal@45 12602 });
bgneal@45 12603 });
bgneal@45 12604 }
bgneal@45 12605 });
bgneal@45 12606 }
bgneal@45 12607
bgneal@45 12608 if (s.convert_newlines_to_brs) {
bgneal@45 12609 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 12610 if (o.initial)
bgneal@45 12611 o.content = o.content.replace(/\r?\n/g, '<br />');
bgneal@45 12612 });
bgneal@45 12613 }
bgneal@45 12614
bgneal@45 12615 if (s.preformatted) {
bgneal@45 12616 t.onPostProcess.add(function(ed, o) {
bgneal@45 12617 o.content = o.content.replace(/^\s*<pre.*?>/, '');
bgneal@45 12618 o.content = o.content.replace(/<\/pre>\s*$/, '');
bgneal@45 12619
bgneal@45 12620 if (o.set)
bgneal@45 12621 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
bgneal@45 12622 });
bgneal@45 12623 }
bgneal@45 12624
bgneal@45 12625 if (s.verify_css_classes) {
bgneal@45 12626 t.serializer.attribValueFilter = function(n, v) {
bgneal@45 12627 var s, cl;
bgneal@45 12628
bgneal@45 12629 if (n == 'class') {
bgneal@45 12630 // Build regexp for classes
bgneal@45 12631 if (!t.classesRE) {
bgneal@45 12632 cl = t.dom.getClasses();
bgneal@45 12633
bgneal@45 12634 if (cl.length > 0) {
bgneal@45 12635 s = '';
bgneal@45 12636
bgneal@45 12637 each (cl, function(o) {
bgneal@45 12638 s += (s ? '|' : '') + o['class'];
bgneal@45 12639 });
bgneal@45 12640
bgneal@45 12641 t.classesRE = new RegExp('(' + s + ')', 'gi');
bgneal@45 12642 }
bgneal@45 12643 }
bgneal@45 12644
bgneal@45 12645 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
bgneal@45 12646 }
bgneal@45 12647
bgneal@45 12648 return v;
bgneal@45 12649 };
bgneal@45 12650 }
bgneal@45 12651
bgneal@45 12652 if (s.cleanup_callback) {
bgneal@45 12653 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 12654 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 12655 });
bgneal@45 12656
bgneal@45 12657 t.onPreProcess.add(function(ed, o) {
bgneal@45 12658 if (o.set)
bgneal@45 12659 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
bgneal@45 12660
bgneal@45 12661 if (o.get)
bgneal@45 12662 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
bgneal@45 12663 });
bgneal@45 12664
bgneal@45 12665 t.onPostProcess.add(function(ed, o) {
bgneal@45 12666 if (o.set)
bgneal@45 12667 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 12668
bgneal@45 12669 if (o.get)
bgneal@45 12670 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
bgneal@45 12671 });
bgneal@45 12672 }
bgneal@45 12673
bgneal@45 12674 if (s.save_callback) {
bgneal@45 12675 t.onGetContent.add(function(ed, o) {
bgneal@45 12676 if (o.save)
bgneal@45 12677 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 12678 });
bgneal@45 12679 }
bgneal@45 12680
bgneal@45 12681 if (s.handle_event_callback) {
bgneal@45 12682 t.onEvent.add(function(ed, e, o) {
bgneal@45 12683 if (t.execCallback('handle_event_callback', e, ed, o) === false)
bgneal@45 12684 Event.cancel(e);
bgneal@45 12685 });
bgneal@45 12686 }
bgneal@45 12687
bgneal@45 12688 // Add visual aids when new contents is added
bgneal@45 12689 t.onSetContent.add(function() {
bgneal@45 12690 t.addVisual(t.getBody());
bgneal@45 12691 });
bgneal@45 12692
bgneal@45 12693 // Remove empty contents
bgneal@45 12694 if (s.padd_empty_editor) {
bgneal@45 12695 t.onPostProcess.add(function(ed, o) {
bgneal@45 12696 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
bgneal@45 12697 });
bgneal@45 12698 }
bgneal@45 12699
bgneal@45 12700 if (isGecko) {
bgneal@45 12701 // Fix gecko link bug, when a link is placed at the end of block elements there is
bgneal@45 12702 // no way to move the caret behind the link. This fix adds a bogus br element after the link
bgneal@45 12703 function fixLinks(ed, o) {
bgneal@45 12704 each(ed.dom.select('a'), function(n) {
bgneal@45 12705 var pn = n.parentNode;
bgneal@45 12706
bgneal@45 12707 if (ed.dom.isBlock(pn) && pn.lastChild === n)
bgneal@45 12708 ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
bgneal@45 12709 });
bgneal@45 12710 };
bgneal@45 12711
bgneal@45 12712 t.onExecCommand.add(function(ed, cmd) {
bgneal@45 12713 if (cmd === 'CreateLink')
bgneal@45 12714 fixLinks(ed);
bgneal@45 12715 });
bgneal@45 12716
bgneal@45 12717 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
bgneal@45 12718 }
bgneal@45 12719
bgneal@45 12720 t.load({initial : true, format : 'html'});
bgneal@45 12721 t.startContent = t.getContent({format : 'raw'});
bgneal@45 12722 t.undoManager.add();
bgneal@45 12723 t.initialized = true;
bgneal@45 12724
bgneal@45 12725 t.onInit.dispatch(t);
bgneal@45 12726 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
bgneal@45 12727 t.execCallback('init_instance_callback', t);
bgneal@45 12728 t.focus(true);
bgneal@45 12729 t.nodeChanged({initial : 1});
bgneal@45 12730
bgneal@45 12731 // Load specified content CSS last
bgneal@45 12732 each(t.contentCSS, function(u) {
bgneal@45 12733 t.dom.loadCSS(u);
bgneal@45 12734 });
bgneal@45 12735
bgneal@45 12736 // Handle auto focus
bgneal@45 12737 if (s.auto_focus) {
bgneal@45 12738 setTimeout(function () {
bgneal@45 12739 var ed = tinymce.get(s.auto_focus);
bgneal@45 12740
bgneal@45 12741 ed.selection.select(ed.getBody(), 1);
bgneal@45 12742 ed.selection.collapse(1);
bgneal@45 12743 ed.getBody().focus();
bgneal@45 12744 ed.getWin().focus();
bgneal@45 12745 }, 100);
bgneal@45 12746 }
bgneal@45 12747
bgneal@45 12748 e = null;
bgneal@45 12749 },
bgneal@45 12750
bgneal@45 12751
bgneal@45 12752 focus : function(sf) {
bgneal@45 12753 var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
bgneal@45 12754
bgneal@45 12755 if (!sf) {
bgneal@45 12756 // Get selected control element
bgneal@45 12757 ieRng = selection.getRng();
bgneal@45 12758 if (ieRng.item) {
bgneal@45 12759 controlElm = ieRng.item(0);
bgneal@45 12760 }
bgneal@45 12761
bgneal@45 12762 t._refreshContentEditable();
bgneal@45 12763
bgneal@45 12764 // Is not content editable
bgneal@45 12765 if (!ce)
bgneal@45 12766 t.getWin().focus();
bgneal@45 12767
bgneal@45 12768 // Focus the body as well since it's contentEditable
bgneal@45 12769 if (tinymce.isGecko) {
bgneal@45 12770 t.getBody().focus();
bgneal@45 12771 }
bgneal@45 12772
bgneal@45 12773 // Restore selected control element
bgneal@45 12774 // This is needed when for example an image is selected within a
bgneal@45 12775 // layer a call to focus will then remove the control selection
bgneal@45 12776 if (controlElm && controlElm.ownerDocument == doc) {
bgneal@45 12777 ieRng = doc.body.createControlRange();
bgneal@45 12778 ieRng.addElement(controlElm);
bgneal@45 12779 ieRng.select();
bgneal@45 12780 }
bgneal@45 12781
bgneal@45 12782 }
bgneal@45 12783
bgneal@45 12784 if (tinymce.activeEditor != t) {
bgneal@45 12785 if ((oed = tinymce.activeEditor) != null)
bgneal@45 12786 oed.onDeactivate.dispatch(oed, t);
bgneal@45 12787
bgneal@45 12788 t.onActivate.dispatch(t, oed);
bgneal@45 12789 }
bgneal@45 12790
bgneal@45 12791 tinymce._setActive(t);
bgneal@45 12792 },
bgneal@45 12793
bgneal@45 12794 execCallback : function(n) {
bgneal@45 12795 var t = this, f = t.settings[n], s;
bgneal@45 12796
bgneal@45 12797 if (!f)
bgneal@45 12798 return;
bgneal@45 12799
bgneal@45 12800 // Look through lookup
bgneal@45 12801 if (t.callbackLookup && (s = t.callbackLookup[n])) {
bgneal@45 12802 f = s.func;
bgneal@45 12803 s = s.scope;
bgneal@45 12804 }
bgneal@45 12805
bgneal@45 12806 if (is(f, 'string')) {
bgneal@45 12807 s = f.replace(/\.\w+$/, '');
bgneal@45 12808 s = s ? tinymce.resolve(s) : 0;
bgneal@45 12809 f = tinymce.resolve(f);
bgneal@45 12810 t.callbackLookup = t.callbackLookup || {};
bgneal@45 12811 t.callbackLookup[n] = {func : f, scope : s};
bgneal@45 12812 }
bgneal@45 12813
bgneal@45 12814 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
bgneal@45 12815 },
bgneal@45 12816
bgneal@45 12817 translate : function(s) {
bgneal@45 12818 var c = this.settings.language || 'en', i18n = tinymce.i18n;
bgneal@45 12819
bgneal@45 12820 if (!s)
bgneal@45 12821 return '';
bgneal@45 12822
bgneal@45 12823 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
bgneal@45 12824 return i18n[c + '.' + b] || '{#' + b + '}';
bgneal@45 12825 });
bgneal@45 12826 },
bgneal@45 12827
bgneal@45 12828 getLang : function(n, dv) {
bgneal@45 12829 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
bgneal@45 12830 },
bgneal@45 12831
bgneal@45 12832 getParam : function(n, dv, ty) {
bgneal@45 12833 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
bgneal@45 12834
bgneal@45 12835 if (ty === 'hash') {
bgneal@45 12836 o = {};
bgneal@45 12837
bgneal@45 12838 if (is(v, 'string')) {
bgneal@45 12839 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
bgneal@45 12840 v = v.split('=');
bgneal@45 12841
bgneal@45 12842 if (v.length > 1)
bgneal@45 12843 o[tr(v[0])] = tr(v[1]);
bgneal@45 12844 else
bgneal@45 12845 o[tr(v[0])] = tr(v);
bgneal@45 12846 });
bgneal@45 12847 } else
bgneal@45 12848 o = v;
bgneal@45 12849
bgneal@45 12850 return o;
bgneal@45 12851 }
bgneal@45 12852
bgneal@45 12853 return v;
bgneal@45 12854 },
bgneal@45 12855
bgneal@45 12856 nodeChanged : function(o) {
bgneal@45 12857 var t = this, s = t.selection, n = s.getStart() || t.getBody();
bgneal@45 12858
bgneal@45 12859 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
bgneal@45 12860 if (t.initialized) {
bgneal@45 12861 o = o || {};
bgneal@45 12862 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
bgneal@45 12863
bgneal@45 12864 // Get parents and add them to object
bgneal@45 12865 o.parents = [];
bgneal@45 12866 t.dom.getParent(n, function(node) {
bgneal@45 12867 if (node.nodeName == 'BODY')
bgneal@45 12868 return true;
bgneal@45 12869
bgneal@45 12870 o.parents.push(node);
bgneal@45 12871 });
bgneal@45 12872
bgneal@45 12873 t.onNodeChange.dispatch(
bgneal@45 12874 t,
bgneal@45 12875 o ? o.controlManager || t.controlManager : t.controlManager,
bgneal@45 12876 n,
bgneal@45 12877 s.isCollapsed(),
bgneal@45 12878 o
bgneal@45 12879 );
bgneal@45 12880 }
bgneal@45 12881 },
bgneal@45 12882
bgneal@45 12883 addButton : function(n, s) {
bgneal@45 12884 var t = this;
bgneal@45 12885
bgneal@45 12886 t.buttons = t.buttons || {};
bgneal@45 12887 t.buttons[n] = s;
bgneal@45 12888 },
bgneal@45 12889
bgneal@45 12890 addCommand : function(name, callback, scope) {
bgneal@45 12891 this.execCommands[name] = {func : callback, scope : scope || this};
bgneal@45 12892 },
bgneal@45 12893
bgneal@45 12894 addQueryStateHandler : function(name, callback, scope) {
bgneal@45 12895 this.queryStateCommands[name] = {func : callback, scope : scope || this};
bgneal@45 12896 },
bgneal@45 12897
bgneal@45 12898 addQueryValueHandler : function(name, callback, scope) {
bgneal@45 12899 this.queryValueCommands[name] = {func : callback, scope : scope || this};
bgneal@45 12900 },
bgneal@45 12901
bgneal@45 12902 addShortcut : function(pa, desc, cmd_func, sc) {
bgneal@45 12903 var t = this, c;
bgneal@45 12904
bgneal@45 12905 if (!t.settings.custom_shortcuts)
bgneal@45 12906 return false;
bgneal@45 12907
bgneal@45 12908 t.shortcuts = t.shortcuts || {};
bgneal@45 12909
bgneal@45 12910 if (is(cmd_func, 'string')) {
bgneal@45 12911 c = cmd_func;
bgneal@45 12912
bgneal@45 12913 cmd_func = function() {
bgneal@45 12914 t.execCommand(c, false, null);
bgneal@45 12915 };
bgneal@45 12916 }
bgneal@45 12917
bgneal@45 12918 if (is(cmd_func, 'object')) {
bgneal@45 12919 c = cmd_func;
bgneal@45 12920
bgneal@45 12921 cmd_func = function() {
bgneal@45 12922 t.execCommand(c[0], c[1], c[2]);
bgneal@45 12923 };
bgneal@45 12924 }
bgneal@45 12925
bgneal@45 12926 each(explode(pa), function(pa) {
bgneal@45 12927 var o = {
bgneal@45 12928 func : cmd_func,
bgneal@45 12929 scope : sc || this,
bgneal@45 12930 desc : desc,
bgneal@45 12931 alt : false,
bgneal@45 12932 ctrl : false,
bgneal@45 12933 shift : false
bgneal@45 12934 };
bgneal@45 12935
bgneal@45 12936 each(explode(pa, '+'), function(v) {
bgneal@45 12937 switch (v) {
bgneal@45 12938 case 'alt':
bgneal@45 12939 case 'ctrl':
bgneal@45 12940 case 'shift':
bgneal@45 12941 o[v] = true;
bgneal@45 12942 break;
bgneal@45 12943
bgneal@45 12944 default:
bgneal@45 12945 o.charCode = v.charCodeAt(0);
bgneal@45 12946 o.keyCode = v.toUpperCase().charCodeAt(0);
bgneal@45 12947 }
bgneal@45 12948 });
bgneal@45 12949
bgneal@45 12950 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
bgneal@45 12951 });
bgneal@45 12952
bgneal@45 12953 return true;
bgneal@45 12954 },
bgneal@45 12955
bgneal@45 12956 execCommand : function(cmd, ui, val, a) {
bgneal@45 12957 var t = this, s = 0, o, st;
bgneal@45 12958
bgneal@45 12959 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
bgneal@45 12960 t.focus();
bgneal@45 12961
bgneal@45 12962 a = extend({}, a);
bgneal@45 12963 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 12964 if (a.terminate)
bgneal@45 12965 return false;
bgneal@45 12966
bgneal@45 12967 // Command callback
bgneal@45 12968 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
bgneal@45 12969 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 12970 return true;
bgneal@45 12971 }
bgneal@45 12972
bgneal@45 12973 // Registred commands
bgneal@45 12974 if (o = t.execCommands[cmd]) {
bgneal@45 12975 st = o.func.call(o.scope, ui, val);
bgneal@45 12976
bgneal@45 12977 // Fall through on true
bgneal@45 12978 if (st !== true) {
bgneal@45 12979 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 12980 return st;
bgneal@45 12981 }
bgneal@45 12982 }
bgneal@45 12983
bgneal@45 12984 // Plugin commands
bgneal@45 12985 each(t.plugins, function(p) {
bgneal@45 12986 if (p.execCommand && p.execCommand(cmd, ui, val)) {
bgneal@45 12987 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 12988 s = 1;
bgneal@45 12989 return false;
bgneal@45 12990 }
bgneal@45 12991 });
bgneal@45 12992
bgneal@45 12993 if (s)
bgneal@45 12994 return true;
bgneal@45 12995
bgneal@45 12996 // Theme commands
bgneal@45 12997 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
bgneal@45 12998 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 12999 return true;
bgneal@45 13000 }
bgneal@45 13001
bgneal@45 13002 // Editor commands
bgneal@45 13003 if (t.editorCommands.execCommand(cmd, ui, val)) {
bgneal@45 13004 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 13005 return true;
bgneal@45 13006 }
bgneal@45 13007
bgneal@45 13008 // Browser commands
bgneal@45 13009 t.getDoc().execCommand(cmd, ui, val);
bgneal@45 13010 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 13011 },
bgneal@45 13012
bgneal@45 13013 queryCommandState : function(cmd) {
bgneal@45 13014 var t = this, o, s;
bgneal@45 13015
bgneal@45 13016 // Is hidden then return undefined
bgneal@45 13017 if (t._isHidden())
bgneal@45 13018 return;
bgneal@45 13019
bgneal@45 13020 // Registred commands
bgneal@45 13021 if (o = t.queryStateCommands[cmd]) {
bgneal@45 13022 s = o.func.call(o.scope);
bgneal@45 13023
bgneal@45 13024 // Fall though on true
bgneal@45 13025 if (s !== true)
bgneal@45 13026 return s;
bgneal@45 13027 }
bgneal@45 13028
bgneal@45 13029 // Registred commands
bgneal@45 13030 o = t.editorCommands.queryCommandState(cmd);
bgneal@45 13031 if (o !== -1)
bgneal@45 13032 return o;
bgneal@45 13033
bgneal@45 13034 // Browser commands
bgneal@45 13035 try {
bgneal@45 13036 return this.getDoc().queryCommandState(cmd);
bgneal@45 13037 } catch (ex) {
bgneal@45 13038 // Fails sometimes see bug: 1896577
bgneal@45 13039 }
bgneal@45 13040 },
bgneal@45 13041
bgneal@45 13042 queryCommandValue : function(c) {
bgneal@45 13043 var t = this, o, s;
bgneal@45 13044
bgneal@45 13045 // Is hidden then return undefined
bgneal@45 13046 if (t._isHidden())
bgneal@45 13047 return;
bgneal@45 13048
bgneal@45 13049 // Registred commands
bgneal@45 13050 if (o = t.queryValueCommands[c]) {
bgneal@45 13051 s = o.func.call(o.scope);
bgneal@45 13052
bgneal@45 13053 // Fall though on true
bgneal@45 13054 if (s !== true)
bgneal@45 13055 return s;
bgneal@45 13056 }
bgneal@45 13057
bgneal@45 13058 // Registred commands
bgneal@45 13059 o = t.editorCommands.queryCommandValue(c);
bgneal@45 13060 if (is(o))
bgneal@45 13061 return o;
bgneal@45 13062
bgneal@45 13063 // Browser commands
bgneal@45 13064 try {
bgneal@45 13065 return this.getDoc().queryCommandValue(c);
bgneal@45 13066 } catch (ex) {
bgneal@45 13067 // Fails sometimes see bug: 1896577
bgneal@45 13068 }
bgneal@45 13069 },
bgneal@45 13070
bgneal@45 13071 show : function() {
bgneal@45 13072 var t = this;
bgneal@45 13073
bgneal@45 13074 DOM.show(t.getContainer());
bgneal@45 13075 DOM.hide(t.id);
bgneal@45 13076 t.load();
bgneal@45 13077 },
bgneal@45 13078
bgneal@45 13079 hide : function() {
bgneal@45 13080 var t = this, d = t.getDoc();
bgneal@45 13081
bgneal@45 13082 // Fixed bug where IE has a blinking cursor left from the editor
bgneal@45 13083 if (isIE && d)
bgneal@45 13084 d.execCommand('SelectAll');
bgneal@45 13085
bgneal@45 13086 // We must save before we hide so Safari doesn't crash
bgneal@45 13087 t.save();
bgneal@45 13088 DOM.hide(t.getContainer());
bgneal@45 13089 DOM.setStyle(t.id, 'display', t.orgDisplay);
bgneal@45 13090 },
bgneal@45 13091
bgneal@45 13092 isHidden : function() {
bgneal@45 13093 return !DOM.isHidden(this.id);
bgneal@45 13094 },
bgneal@45 13095
bgneal@45 13096 setProgressState : function(b, ti, o) {
bgneal@45 13097 this.onSetProgressState.dispatch(this, b, ti, o);
bgneal@45 13098
bgneal@45 13099 return b;
bgneal@45 13100 },
bgneal@45 13101
bgneal@45 13102 load : function(o) {
bgneal@45 13103 var t = this, e = t.getElement(), h;
bgneal@45 13104
bgneal@45 13105 if (e) {
bgneal@45 13106 o = o || {};
bgneal@45 13107 o.load = true;
bgneal@45 13108
bgneal@45 13109 // Double encode existing entities in the value
bgneal@45 13110 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
bgneal@45 13111 o.element = e;
bgneal@45 13112
bgneal@45 13113 if (!o.no_events)
bgneal@45 13114 t.onLoadContent.dispatch(t, o);
bgneal@45 13115
bgneal@45 13116 o.element = e = null;
bgneal@45 13117
bgneal@45 13118 return h;
bgneal@45 13119 }
bgneal@45 13120 },
bgneal@45 13121
bgneal@45 13122 save : function(o) {
bgneal@45 13123 var t = this, e = t.getElement(), h, f;
bgneal@45 13124
bgneal@45 13125 if (!e || !t.initialized)
bgneal@45 13126 return;
bgneal@45 13127
bgneal@45 13128 o = o || {};
bgneal@45 13129 o.save = true;
bgneal@45 13130
bgneal@45 13131 // Add undo level will trigger onchange event
bgneal@45 13132 if (!o.no_events) {
bgneal@45 13133 t.undoManager.typing = false;
bgneal@45 13134 t.undoManager.add();
bgneal@45 13135 }
bgneal@45 13136
bgneal@45 13137 o.element = e;
bgneal@45 13138 h = o.content = t.getContent(o);
bgneal@45 13139
bgneal@45 13140 if (!o.no_events)
bgneal@45 13141 t.onSaveContent.dispatch(t, o);
bgneal@45 13142
bgneal@45 13143 h = o.content;
bgneal@45 13144
bgneal@45 13145 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
bgneal@45 13146 e.innerHTML = h;
bgneal@45 13147
bgneal@45 13148 // Update hidden form element
bgneal@45 13149 if (f = DOM.getParent(t.id, 'form')) {
bgneal@45 13150 each(f.elements, function(e) {
bgneal@45 13151 if (e.name == t.id) {
bgneal@45 13152 e.value = h;
bgneal@45 13153 return false;
bgneal@45 13154 }
bgneal@45 13155 });
bgneal@45 13156 }
bgneal@45 13157 } else
bgneal@45 13158 e.value = h;
bgneal@45 13159
bgneal@45 13160 o.element = e = null;
bgneal@45 13161
bgneal@45 13162 return h;
bgneal@45 13163 },
bgneal@45 13164
bgneal@45 13165 setContent : function(content, args) {
bgneal@45 13166 var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
bgneal@45 13167
bgneal@45 13168 // Setup args object
bgneal@45 13169 args = args || {};
bgneal@45 13170 args.format = args.format || 'html';
bgneal@45 13171 args.set = true;
bgneal@45 13172 args.content = content;
bgneal@45 13173
bgneal@45 13174 // Do preprocessing
bgneal@45 13175 if (!args.no_events)
bgneal@45 13176 self.onBeforeSetContent.dispatch(self, args);
bgneal@45 13177
bgneal@45 13178 content = args.content;
bgneal@45 13179
bgneal@45 13180 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
bgneal@45 13181 // It will also be impossible to place the caret in the editor unless there is a BR element present
bgneal@45 13182 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
bgneal@45 13183 forcedRootBlockName = self.settings.forced_root_block;
bgneal@45 13184 if (forcedRootBlockName)
bgneal@45 13185 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
bgneal@45 13186 else
bgneal@45 13187 content = '<br data-mce-bogus="1">';
bgneal@45 13188
bgneal@45 13189 body.innerHTML = content;
bgneal@45 13190 self.selection.select(body, true);
bgneal@45 13191 self.selection.collapse(true);
bgneal@45 13192 return;
bgneal@45 13193 }
bgneal@45 13194
bgneal@45 13195 // Parse and serialize the html
bgneal@45 13196 if (args.format !== 'raw') {
bgneal@45 13197 content = new tinymce.html.Serializer({}, self.schema).serialize(
bgneal@45 13198 self.parser.parse(content)
bgneal@45 13199 );
bgneal@45 13200 }
bgneal@45 13201
bgneal@45 13202 // Set the new cleaned contents to the editor
bgneal@45 13203 args.content = tinymce.trim(content);
bgneal@45 13204 self.dom.setHTML(body, args.content);
bgneal@45 13205
bgneal@45 13206 // Do post processing
bgneal@45 13207 if (!args.no_events)
bgneal@45 13208 self.onSetContent.dispatch(self, args);
bgneal@45 13209
bgneal@45 13210 self.selection.normalize();
bgneal@45 13211
bgneal@45 13212 return args.content;
bgneal@45 13213 },
bgneal@45 13214
bgneal@45 13215 getContent : function(args) {
bgneal@45 13216 var self = this, content;
bgneal@45 13217
bgneal@45 13218 // Setup args object
bgneal@45 13219 args = args || {};
bgneal@45 13220 args.format = args.format || 'html';
bgneal@45 13221 args.get = true;
bgneal@45 13222
bgneal@45 13223 // Do preprocessing
bgneal@45 13224 if (!args.no_events)
bgneal@45 13225 self.onBeforeGetContent.dispatch(self, args);
bgneal@45 13226
bgneal@45 13227 // Get raw contents or by default the cleaned contents
bgneal@45 13228 if (args.format == 'raw')
bgneal@45 13229 content = self.getBody().innerHTML;
bgneal@45 13230 else
bgneal@45 13231 content = self.serializer.serialize(self.getBody(), args);
bgneal@45 13232
bgneal@45 13233 args.content = tinymce.trim(content);
bgneal@45 13234
bgneal@45 13235 // Do post processing
bgneal@45 13236 if (!args.no_events)
bgneal@45 13237 self.onGetContent.dispatch(self, args);
bgneal@45 13238
bgneal@45 13239 return args.content;
bgneal@45 13240 },
bgneal@45 13241
bgneal@45 13242 isDirty : function() {
bgneal@45 13243 var self = this;
bgneal@45 13244
bgneal@45 13245 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
bgneal@45 13246 },
bgneal@45 13247
bgneal@45 13248 getContainer : function() {
bgneal@45 13249 var t = this;
bgneal@45 13250
bgneal@45 13251 if (!t.container)
bgneal@45 13252 t.container = DOM.get(t.editorContainer || t.id + '_parent');
bgneal@45 13253
bgneal@45 13254 return t.container;
bgneal@45 13255 },
bgneal@45 13256
bgneal@45 13257 getContentAreaContainer : function() {
bgneal@45 13258 return this.contentAreaContainer;
bgneal@45 13259 },
bgneal@45 13260
bgneal@45 13261 getElement : function() {
bgneal@45 13262 return DOM.get(this.settings.content_element || this.id);
bgneal@45 13263 },
bgneal@45 13264
bgneal@45 13265 getWin : function() {
bgneal@45 13266 var t = this, e;
bgneal@45 13267
bgneal@45 13268 if (!t.contentWindow) {
bgneal@45 13269 e = DOM.get(t.id + "_ifr");
bgneal@45 13270
bgneal@45 13271 if (e)
bgneal@45 13272 t.contentWindow = e.contentWindow;
bgneal@45 13273 }
bgneal@45 13274
bgneal@45 13275 return t.contentWindow;
bgneal@45 13276 },
bgneal@45 13277
bgneal@45 13278 getDoc : function() {
bgneal@45 13279 var t = this, w;
bgneal@45 13280
bgneal@45 13281 if (!t.contentDocument) {
bgneal@45 13282 w = t.getWin();
bgneal@45 13283
bgneal@45 13284 if (w)
bgneal@45 13285 t.contentDocument = w.document;
bgneal@45 13286 }
bgneal@45 13287
bgneal@45 13288 return t.contentDocument;
bgneal@45 13289 },
bgneal@45 13290
bgneal@45 13291 getBody : function() {
bgneal@45 13292 return this.bodyElement || this.getDoc().body;
bgneal@45 13293 },
bgneal@45 13294
bgneal@45 13295 convertURL : function(u, n, e) {
bgneal@45 13296 var t = this, s = t.settings;
bgneal@45 13297
bgneal@45 13298 // Use callback instead
bgneal@45 13299 if (s.urlconverter_callback)
bgneal@45 13300 return t.execCallback('urlconverter_callback', u, e, true, n);
bgneal@45 13301
bgneal@45 13302 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
bgneal@45 13303 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
bgneal@45 13304 return u;
bgneal@45 13305
bgneal@45 13306 // Convert to relative
bgneal@45 13307 if (s.relative_urls)
bgneal@45 13308 return t.documentBaseURI.toRelative(u);
bgneal@45 13309
bgneal@45 13310 // Convert to absolute
bgneal@45 13311 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
bgneal@45 13312
bgneal@45 13313 return u;
bgneal@45 13314 },
bgneal@45 13315
bgneal@45 13316 addVisual : function(e) {
bgneal@45 13317 var t = this, s = t.settings;
bgneal@45 13318
bgneal@45 13319 e = e || t.getBody();
bgneal@45 13320
bgneal@45 13321 if (!is(t.hasVisual))
bgneal@45 13322 t.hasVisual = s.visual;
bgneal@45 13323
bgneal@45 13324 each(t.dom.select('table,a', e), function(e) {
bgneal@45 13325 var v;
bgneal@45 13326
bgneal@45 13327 switch (e.nodeName) {
bgneal@45 13328 case 'TABLE':
bgneal@45 13329 v = t.dom.getAttrib(e, 'border');
bgneal@45 13330
bgneal@45 13331 if (!v || v == '0') {
bgneal@45 13332 if (t.hasVisual)
bgneal@45 13333 t.dom.addClass(e, s.visual_table_class);
bgneal@45 13334 else
bgneal@45 13335 t.dom.removeClass(e, s.visual_table_class);
bgneal@45 13336 }
bgneal@45 13337
bgneal@45 13338 return;
bgneal@45 13339
bgneal@45 13340 case 'A':
bgneal@45 13341 v = t.dom.getAttrib(e, 'name');
bgneal@45 13342
bgneal@45 13343 if (v) {
bgneal@45 13344 if (t.hasVisual)
bgneal@45 13345 t.dom.addClass(e, 'mceItemAnchor');
bgneal@45 13346 else
bgneal@45 13347 t.dom.removeClass(e, 'mceItemAnchor');
bgneal@45 13348 }
bgneal@45 13349
bgneal@45 13350 return;
bgneal@45 13351 }
bgneal@45 13352 });
bgneal@45 13353
bgneal@45 13354 t.onVisualAid.dispatch(t, e, t.hasVisual);
bgneal@45 13355 },
bgneal@45 13356
bgneal@45 13357 remove : function() {
bgneal@45 13358 var t = this, e = t.getContainer();
bgneal@45 13359
bgneal@45 13360 if (!t.removed) {
bgneal@45 13361 t.removed = 1; // Cancels post remove event execution
bgneal@45 13362 t.hide();
bgneal@45 13363
bgneal@45 13364 // Remove all events
bgneal@45 13365
bgneal@45 13366 // Don't clear the window or document if content editable
bgneal@45 13367 // is enabled since other instances might still be present
bgneal@45 13368 if (!t.settings.content_editable) {
bgneal@45 13369 Event.clear(t.getWin());
bgneal@45 13370 Event.clear(t.getDoc());
bgneal@45 13371 }
bgneal@45 13372
bgneal@45 13373 Event.clear(t.getBody());
bgneal@45 13374 Event.clear(t.formElement);
bgneal@45 13375 Event.unbind(e);
bgneal@45 13376
bgneal@45 13377 t.execCallback('remove_instance_callback', t);
bgneal@45 13378 t.onRemove.dispatch(t);
bgneal@45 13379
bgneal@45 13380 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
bgneal@45 13381 t.onExecCommand.listeners = [];
bgneal@45 13382
bgneal@45 13383 tinymce.remove(t);
bgneal@45 13384 DOM.remove(e);
bgneal@45 13385 }
bgneal@45 13386 },
bgneal@45 13387
bgneal@45 13388 destroy : function(s) {
bgneal@45 13389 var t = this;
bgneal@45 13390
bgneal@45 13391 // One time is enough
bgneal@45 13392 if (t.destroyed)
bgneal@45 13393 return;
bgneal@45 13394
bgneal@45 13395 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
bgneal@45 13396 if (isGecko) {
bgneal@45 13397 Event.unbind(t.getDoc());
bgneal@45 13398 Event.unbind(t.getWin());
bgneal@45 13399 Event.unbind(t.getBody());
bgneal@45 13400 }
bgneal@45 13401
bgneal@45 13402 if (!s) {
bgneal@45 13403 tinymce.removeUnload(t.destroy);
bgneal@45 13404 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
bgneal@45 13405
bgneal@45 13406 // Manual destroy
bgneal@45 13407 if (t.theme && t.theme.destroy)
bgneal@45 13408 t.theme.destroy();
bgneal@45 13409
bgneal@45 13410 // Destroy controls, selection and dom
bgneal@45 13411 t.controlManager.destroy();
bgneal@45 13412 t.selection.destroy();
bgneal@45 13413 t.dom.destroy();
bgneal@45 13414 }
bgneal@45 13415
bgneal@45 13416 if (t.formElement) {
bgneal@45 13417 t.formElement.submit = t.formElement._mceOldSubmit;
bgneal@45 13418 t.formElement._mceOldSubmit = null;
bgneal@45 13419 }
bgneal@45 13420
bgneal@45 13421 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
bgneal@45 13422
bgneal@45 13423 if (t.selection)
bgneal@45 13424 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
bgneal@45 13425
bgneal@45 13426 t.destroyed = 1;
bgneal@45 13427 },
bgneal@45 13428
bgneal@45 13429 // Internal functions
bgneal@45 13430
bgneal@45 13431 _addEvents : function() {
bgneal@45 13432 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
bgneal@45 13433 var t = this, i, s = t.settings, dom = t.dom, lo = {
bgneal@45 13434 mouseup : 'onMouseUp',
bgneal@45 13435 mousedown : 'onMouseDown',
bgneal@45 13436 click : 'onClick',
bgneal@45 13437 keyup : 'onKeyUp',
bgneal@45 13438 keydown : 'onKeyDown',
bgneal@45 13439 keypress : 'onKeyPress',
bgneal@45 13440 submit : 'onSubmit',
bgneal@45 13441 reset : 'onReset',
bgneal@45 13442 contextmenu : 'onContextMenu',
bgneal@45 13443 dblclick : 'onDblClick',
bgneal@45 13444 paste : 'onPaste' // Doesn't work in all browsers yet
bgneal@45 13445 };
bgneal@45 13446
bgneal@45 13447 function eventHandler(e, o) {
bgneal@45 13448 var ty = e.type;
bgneal@45 13449
bgneal@45 13450 // Don't fire events when it's removed
bgneal@45 13451 if (t.removed)
bgneal@45 13452 return;
bgneal@45 13453
bgneal@45 13454 // Generic event handler
bgneal@45 13455 if (t.onEvent.dispatch(t, e, o) !== false) {
bgneal@45 13456 // Specific event handler
bgneal@45 13457 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
bgneal@45 13458 }
bgneal@45 13459 };
bgneal@45 13460
bgneal@45 13461 // Add DOM events
bgneal@45 13462 each(lo, function(v, k) {
bgneal@45 13463 switch (k) {
bgneal@45 13464 case 'contextmenu':
bgneal@45 13465 dom.bind(t.getDoc(), k, eventHandler);
bgneal@45 13466 break;
bgneal@45 13467
bgneal@45 13468 case 'paste':
bgneal@45 13469 dom.bind(t.getBody(), k, function(e) {
bgneal@45 13470 eventHandler(e);
bgneal@45 13471 });
bgneal@45 13472 break;
bgneal@45 13473
bgneal@45 13474 case 'submit':
bgneal@45 13475 case 'reset':
bgneal@45 13476 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
bgneal@45 13477 break;
bgneal@45 13478
bgneal@45 13479 default:
bgneal@45 13480 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
bgneal@45 13481 }
bgneal@45 13482 });
bgneal@45 13483
bgneal@45 13484 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
bgneal@45 13485 t.focus(true);
bgneal@45 13486 });
bgneal@45 13487
bgneal@45 13488
bgneal@45 13489 // Fixes bug where a specified document_base_uri could result in broken images
bgneal@45 13490 // This will also fix drag drop of images in Gecko
bgneal@45 13491 if (tinymce.isGecko) {
bgneal@45 13492 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
bgneal@45 13493 var v;
bgneal@45 13494
bgneal@45 13495 e = e.target;
bgneal@45 13496
bgneal@45 13497 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
bgneal@45 13498 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@45 13499 });
bgneal@45 13500 }
bgneal@45 13501
bgneal@45 13502 // Set various midas options in Gecko
bgneal@45 13503 if (isGecko) {
bgneal@45 13504 function setOpts() {
bgneal@45 13505 var t = this, d = t.getDoc(), s = t.settings;
bgneal@45 13506
bgneal@45 13507 if (isGecko && !s.readonly) {
bgneal@45 13508 t._refreshContentEditable();
bgneal@45 13509
bgneal@45 13510 try {
bgneal@45 13511 // Try new Gecko method
bgneal@45 13512 d.execCommand("styleWithCSS", 0, false);
bgneal@45 13513 } catch (ex) {
bgneal@45 13514 // Use old method
bgneal@45 13515 if (!t._isHidden())
bgneal@45 13516 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
bgneal@45 13517 }
bgneal@45 13518
bgneal@45 13519 if (!s.table_inline_editing)
bgneal@45 13520 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
bgneal@45 13521
bgneal@45 13522 if (!s.object_resizing)
bgneal@45 13523 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
bgneal@45 13524 }
bgneal@45 13525 };
bgneal@45 13526
bgneal@45 13527 t.onBeforeExecCommand.add(setOpts);
bgneal@45 13528 t.onMouseDown.add(setOpts);
bgneal@45 13529 }
bgneal@45 13530
bgneal@45 13531 // Add node change handlers
bgneal@45 13532 t.onMouseUp.add(t.nodeChanged);
bgneal@45 13533 //t.onClick.add(t.nodeChanged);
bgneal@45 13534 t.onKeyUp.add(function(ed, e) {
bgneal@45 13535 var c = e.keyCode;
bgneal@45 13536
bgneal@45 13537 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
bgneal@45 13538 t.nodeChanged();
bgneal@45 13539 });
bgneal@45 13540
bgneal@45 13541
bgneal@45 13542 // Add block quote deletion handler
bgneal@45 13543 t.onKeyDown.add(function(ed, e) {
bgneal@45 13544 if (e.keyCode != VK.BACKSPACE)
bgneal@45 13545 return;
bgneal@45 13546
bgneal@45 13547 var rng = ed.selection.getRng();
bgneal@45 13548 if (!rng.collapsed)
bgneal@45 13549 return;
bgneal@45 13550
bgneal@45 13551 var n = rng.startContainer;
bgneal@45 13552 var offset = rng.startOffset;
bgneal@45 13553
bgneal@45 13554 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
bgneal@45 13555 n = n.parentNode;
bgneal@45 13556
bgneal@45 13557 // Is the cursor at the beginning of a blockquote?
bgneal@45 13558 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
bgneal@45 13559 // Remove the blockquote
bgneal@45 13560 ed.formatter.toggle('blockquote', null, n.parentNode);
bgneal@45 13561
bgneal@45 13562 // Move the caret to the beginning of n
bgneal@45 13563 rng.setStart(n, 0);
bgneal@45 13564 rng.setEnd(n, 0);
bgneal@45 13565 ed.selection.setRng(rng);
bgneal@45 13566 ed.selection.collapse(false);
bgneal@45 13567 }
bgneal@45 13568 });
bgneal@45 13569
bgneal@45 13570
bgneal@45 13571
bgneal@45 13572 // Add reset handler
bgneal@45 13573 t.onReset.add(function() {
bgneal@45 13574 t.setContent(t.startContent, {format : 'raw'});
bgneal@45 13575 });
bgneal@45 13576
bgneal@45 13577 // Add shortcuts
bgneal@45 13578 if (s.custom_shortcuts) {
bgneal@45 13579 if (s.custom_undo_redo_keyboard_shortcuts) {
bgneal@45 13580 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
bgneal@45 13581 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
bgneal@45 13582 }
bgneal@45 13583
bgneal@45 13584 // Add default shortcuts for gecko
bgneal@45 13585 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
bgneal@45 13586 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
bgneal@45 13587 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
bgneal@45 13588
bgneal@45 13589 // BlockFormat shortcuts keys
bgneal@45 13590 for (i=1; i<=6; i++)
bgneal@45 13591 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
bgneal@45 13592
bgneal@45 13593 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
bgneal@45 13594 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
bgneal@45 13595 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
bgneal@45 13596
bgneal@45 13597 function find(e) {
bgneal@45 13598 var v = null;
bgneal@45 13599
bgneal@45 13600 if (!e.altKey && !e.ctrlKey && !e.metaKey)
bgneal@45 13601 return v;
bgneal@45 13602
bgneal@45 13603 each(t.shortcuts, function(o) {
bgneal@45 13604 if (tinymce.isMac && o.ctrl != e.metaKey)
bgneal@45 13605 return;
bgneal@45 13606 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
bgneal@45 13607 return;
bgneal@45 13608
bgneal@45 13609 if (o.alt != e.altKey)
bgneal@45 13610 return;
bgneal@45 13611
bgneal@45 13612 if (o.shift != e.shiftKey)
bgneal@45 13613 return;
bgneal@45 13614
bgneal@45 13615 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
bgneal@45 13616 v = o;
bgneal@45 13617 return false;
bgneal@45 13618 }
bgneal@45 13619 });
bgneal@45 13620
bgneal@45 13621 return v;
bgneal@45 13622 };
bgneal@45 13623
bgneal@45 13624 t.onKeyUp.add(function(ed, e) {
bgneal@45 13625 var o = find(e);
bgneal@45 13626
bgneal@45 13627 if (o)
bgneal@45 13628 return Event.cancel(e);
bgneal@45 13629 });
bgneal@45 13630
bgneal@45 13631 t.onKeyPress.add(function(ed, e) {
bgneal@45 13632 var o = find(e);
bgneal@45 13633
bgneal@45 13634 if (o)
bgneal@45 13635 return Event.cancel(e);
bgneal@45 13636 });
bgneal@45 13637
bgneal@45 13638 t.onKeyDown.add(function(ed, e) {
bgneal@45 13639 var o = find(e);
bgneal@45 13640
bgneal@45 13641 if (o) {
bgneal@45 13642 o.func.call(o.scope);
bgneal@45 13643 return Event.cancel(e);
bgneal@45 13644 }
bgneal@45 13645 });
bgneal@45 13646 }
bgneal@45 13647
bgneal@45 13648 if (tinymce.isIE) {
bgneal@45 13649 // Fix so resize will only update the width and height attributes not the styles of an image
bgneal@45 13650 // It will also block mceItemNoResize items
bgneal@45 13651 dom.bind(t.getDoc(), 'controlselect', function(e) {
bgneal@45 13652 var re = t.resizeInfo, cb;
bgneal@45 13653
bgneal@45 13654 e = e.target;
bgneal@45 13655
bgneal@45 13656 // Don't do this action for non image elements
bgneal@45 13657 if (e.nodeName !== 'IMG')
bgneal@45 13658 return;
bgneal@45 13659
bgneal@45 13660 if (re)
bgneal@45 13661 dom.unbind(re.node, re.ev, re.cb);
bgneal@45 13662
bgneal@45 13663 if (!dom.hasClass(e, 'mceItemNoResize')) {
bgneal@45 13664 ev = 'resizeend';
bgneal@45 13665 cb = dom.bind(e, ev, function(e) {
bgneal@45 13666 var v;
bgneal@45 13667
bgneal@45 13668 e = e.target;
bgneal@45 13669
bgneal@45 13670 if (v = dom.getStyle(e, 'width')) {
bgneal@45 13671 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
bgneal@45 13672 dom.setStyle(e, 'width', '');
bgneal@45 13673 }
bgneal@45 13674
bgneal@45 13675 if (v = dom.getStyle(e, 'height')) {
bgneal@45 13676 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
bgneal@45 13677 dom.setStyle(e, 'height', '');
bgneal@45 13678 }
bgneal@45 13679 });
bgneal@45 13680 } else {
bgneal@45 13681 ev = 'resizestart';
bgneal@45 13682 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
bgneal@45 13683 }
bgneal@45 13684
bgneal@45 13685 re = t.resizeInfo = {
bgneal@45 13686 node : e,
bgneal@45 13687 ev : ev,
bgneal@45 13688 cb : cb
bgneal@45 13689 };
bgneal@45 13690 });
bgneal@45 13691 }
bgneal@45 13692
bgneal@45 13693 if (tinymce.isOpera) {
bgneal@45 13694 t.onClick.add(function(ed, e) {
bgneal@45 13695 Event.prevent(e);
bgneal@45 13696 });
bgneal@45 13697 }
bgneal@45 13698
bgneal@45 13699 // Add custom undo/redo handlers
bgneal@45 13700 if (s.custom_undo_redo) {
bgneal@45 13701 function addUndo() {
bgneal@45 13702 t.undoManager.typing = false;
bgneal@45 13703 t.undoManager.add();
bgneal@45 13704 };
bgneal@45 13705
bgneal@45 13706 var focusLostFunc = tinymce.isGecko ? 'blur' : 'focusout';
bgneal@45 13707 dom.bind(t.getDoc(), focusLostFunc, function(e){
bgneal@45 13708 if (!t.removed && t.undoManager.typing)
bgneal@45 13709 addUndo();
bgneal@45 13710 });
bgneal@45 13711
bgneal@45 13712 // Add undo level when contents is drag/dropped within the editor
bgneal@45 13713 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
bgneal@45 13714 addUndo();
bgneal@45 13715 });
bgneal@45 13716
bgneal@45 13717 t.onKeyUp.add(function(ed, e) {
bgneal@45 13718 var keyCode = e.keyCode;
bgneal@45 13719
bgneal@45 13720 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
bgneal@45 13721 addUndo();
bgneal@45 13722 });
bgneal@45 13723
bgneal@45 13724 t.onKeyDown.add(function(ed, e) {
bgneal@45 13725 var keyCode = e.keyCode, sel;
bgneal@45 13726
bgneal@45 13727 if (keyCode == 8) {
bgneal@45 13728 sel = t.getDoc().selection;
bgneal@45 13729
bgneal@45 13730 // Fix IE control + backspace browser bug
bgneal@45 13731 if (sel && sel.createRange && sel.createRange().item) {
bgneal@45 13732 t.undoManager.beforeChange();
bgneal@45 13733 ed.dom.remove(sel.createRange().item(0));
bgneal@45 13734 addUndo();
bgneal@45 13735
bgneal@45 13736 return Event.cancel(e);
bgneal@45 13737 }
bgneal@45 13738 }
bgneal@45 13739
bgneal@45 13740 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
bgneal@45 13741 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
bgneal@45 13742 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
bgneal@45 13743 // Todo: Remove this once we normalize enter behavior on IE
bgneal@45 13744 if (tinymce.isIE && keyCode == 13)
bgneal@45 13745 t.undoManager.beforeChange();
bgneal@45 13746
bgneal@45 13747 if (t.undoManager.typing)
bgneal@45 13748 addUndo();
bgneal@45 13749
bgneal@45 13750 return;
bgneal@45 13751 }
bgneal@45 13752
bgneal@45 13753 // If key isn't shift,ctrl,alt,capslock,metakey
bgneal@45 13754 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
bgneal@45 13755 t.undoManager.beforeChange();
bgneal@45 13756 t.undoManager.typing = true;
bgneal@45 13757 t.undoManager.add();
bgneal@45 13758 }
bgneal@45 13759 });
bgneal@45 13760
bgneal@45 13761 t.onMouseDown.add(function() {
bgneal@45 13762 if (t.undoManager.typing)
bgneal@45 13763 addUndo();
bgneal@45 13764 });
bgneal@45 13765 }
bgneal@45 13766 },
bgneal@45 13767
bgneal@45 13768 _refreshContentEditable : function() {
bgneal@45 13769 var self = this, body, parent;
bgneal@45 13770
bgneal@45 13771 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
bgneal@45 13772 if (self._isHidden()) {
bgneal@45 13773 body = self.getBody();
bgneal@45 13774 parent = body.parentNode;
bgneal@45 13775
bgneal@45 13776 parent.removeChild(body);
bgneal@45 13777 parent.appendChild(body);
bgneal@45 13778
bgneal@45 13779 body.focus();
bgneal@45 13780 }
bgneal@45 13781 },
bgneal@45 13782
bgneal@45 13783 _isHidden : function() {
bgneal@45 13784 var s;
bgneal@45 13785
bgneal@45 13786 if (!isGecko)
bgneal@45 13787 return 0;
bgneal@45 13788
bgneal@45 13789 // Weird, wheres that cursor selection?
bgneal@45 13790 s = this.selection.getSel();
bgneal@45 13791 return (!s || !s.rangeCount || s.rangeCount == 0);
bgneal@45 13792 }
bgneal@45 13793 });
bgneal@45 13794 })(tinymce);
bgneal@45 13795
bgneal@45 13796 (function(tinymce) {
bgneal@45 13797 // Added for compression purposes
bgneal@45 13798 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
bgneal@45 13799
bgneal@45 13800 tinymce.EditorCommands = function(editor) {
bgneal@45 13801 var dom = editor.dom,
bgneal@45 13802 selection = editor.selection,
bgneal@45 13803 commands = {state: {}, exec : {}, value : {}},
bgneal@45 13804 settings = editor.settings,
bgneal@45 13805 formatter = editor.formatter,
bgneal@45 13806 bookmark;
bgneal@45 13807
bgneal@45 13808 function execCommand(command, ui, value) {
bgneal@45 13809 var func;
bgneal@45 13810
bgneal@45 13811 command = command.toLowerCase();
bgneal@45 13812 if (func = commands.exec[command]) {
bgneal@45 13813 func(command, ui, value);
bgneal@45 13814 return TRUE;
bgneal@45 13815 }
bgneal@45 13816
bgneal@45 13817 return FALSE;
bgneal@45 13818 };
bgneal@45 13819
bgneal@45 13820 function queryCommandState(command) {
bgneal@45 13821 var func;
bgneal@45 13822
bgneal@45 13823 command = command.toLowerCase();
bgneal@45 13824 if (func = commands.state[command])
bgneal@45 13825 return func(command);
bgneal@45 13826
bgneal@45 13827 return -1;
bgneal@45 13828 };
bgneal@45 13829
bgneal@45 13830 function queryCommandValue(command) {
bgneal@45 13831 var func;
bgneal@45 13832
bgneal@45 13833 command = command.toLowerCase();
bgneal@45 13834 if (func = commands.value[command])
bgneal@45 13835 return func(command);
bgneal@45 13836
bgneal@45 13837 return FALSE;
bgneal@45 13838 };
bgneal@45 13839
bgneal@45 13840 function addCommands(command_list, type) {
bgneal@45 13841 type = type || 'exec';
bgneal@45 13842
bgneal@45 13843 each(command_list, function(callback, command) {
bgneal@45 13844 each(command.toLowerCase().split(','), function(command) {
bgneal@45 13845 commands[type][command] = callback;
bgneal@45 13846 });
bgneal@45 13847 });
bgneal@45 13848 };
bgneal@45 13849
bgneal@45 13850 // Expose public methods
bgneal@45 13851 tinymce.extend(this, {
bgneal@45 13852 execCommand : execCommand,
bgneal@45 13853 queryCommandState : queryCommandState,
bgneal@45 13854 queryCommandValue : queryCommandValue,
bgneal@45 13855 addCommands : addCommands
bgneal@45 13856 });
bgneal@45 13857
bgneal@45 13858 // Private methods
bgneal@45 13859
bgneal@45 13860 function execNativeCommand(command, ui, value) {
bgneal@45 13861 if (ui === undefined)
bgneal@45 13862 ui = FALSE;
bgneal@45 13863
bgneal@45 13864 if (value === undefined)
bgneal@45 13865 value = null;
bgneal@45 13866
bgneal@45 13867 return editor.getDoc().execCommand(command, ui, value);
bgneal@45 13868 };
bgneal@45 13869
bgneal@45 13870 function isFormatMatch(name) {
bgneal@45 13871 return formatter.match(name);
bgneal@45 13872 };
bgneal@45 13873
bgneal@45 13874 function toggleFormat(name, value) {
bgneal@45 13875 formatter.toggle(name, value ? {value : value} : undefined);
bgneal@45 13876 };
bgneal@45 13877
bgneal@45 13878 function storeSelection(type) {
bgneal@45 13879 bookmark = selection.getBookmark(type);
bgneal@45 13880 };
bgneal@45 13881
bgneal@45 13882 function restoreSelection() {
bgneal@45 13883 selection.moveToBookmark(bookmark);
bgneal@45 13884 };
bgneal@45 13885
bgneal@45 13886 // Add execCommand overrides
bgneal@45 13887 addCommands({
bgneal@45 13888 // Ignore these, added for compatibility
bgneal@45 13889 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
bgneal@45 13890
bgneal@45 13891 // Add undo manager logic
bgneal@45 13892 'mceEndUndoLevel,mceAddUndoLevel' : function() {
bgneal@45 13893 editor.undoManager.add();
bgneal@45 13894 },
bgneal@45 13895
bgneal@45 13896 'Cut,Copy,Paste' : function(command) {
bgneal@45 13897 var doc = editor.getDoc(), failed;
bgneal@45 13898
bgneal@45 13899 // Try executing the native command
bgneal@45 13900 try {
bgneal@45 13901 execNativeCommand(command);
bgneal@45 13902 } catch (ex) {
bgneal@45 13903 // Command failed
bgneal@45 13904 failed = TRUE;
bgneal@45 13905 }
bgneal@45 13906
bgneal@45 13907 // Present alert message about clipboard access not being available
bgneal@45 13908 if (failed || !doc.queryCommandSupported(command)) {
bgneal@45 13909 if (tinymce.isGecko) {
bgneal@45 13910 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
bgneal@45 13911 if (state)
bgneal@45 13912 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
bgneal@45 13913 });
bgneal@45 13914 } else
bgneal@45 13915 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
bgneal@45 13916 }
bgneal@45 13917 },
bgneal@45 13918
bgneal@45 13919 // Override unlink command
bgneal@45 13920 unlink : function(command) {
bgneal@45 13921 if (selection.isCollapsed())
bgneal@45 13922 selection.select(selection.getNode());
bgneal@45 13923
bgneal@45 13924 execNativeCommand(command);
bgneal@45 13925 selection.collapse(FALSE);
bgneal@45 13926 },
bgneal@45 13927
bgneal@45 13928 // Override justify commands to use the text formatter engine
bgneal@45 13929 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@45 13930 var align = command.substring(7);
bgneal@45 13931
bgneal@45 13932 // Remove all other alignments first
bgneal@45 13933 each('left,center,right,full'.split(','), function(name) {
bgneal@45 13934 if (align != name)
bgneal@45 13935 formatter.remove('align' + name);
bgneal@45 13936 });
bgneal@45 13937
bgneal@45 13938 toggleFormat('align' + align);
bgneal@45 13939 execCommand('mceRepaint');
bgneal@45 13940 },
bgneal@45 13941
bgneal@45 13942 // Override list commands to fix WebKit bug
bgneal@45 13943 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@45 13944 var listElm, listParent;
bgneal@45 13945
bgneal@45 13946 execNativeCommand(command);
bgneal@45 13947
bgneal@45 13948 // WebKit produces lists within block elements so we need to split them
bgneal@45 13949 // we will replace the native list creation logic to custom logic later on
bgneal@45 13950 // TODO: Remove this when the list creation logic is removed
bgneal@45 13951 listElm = dom.getParent(selection.getNode(), 'ol,ul');
bgneal@45 13952 if (listElm) {
bgneal@45 13953 listParent = listElm.parentNode;
bgneal@45 13954
bgneal@45 13955 // If list is within a text block then split that block
bgneal@45 13956 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
bgneal@45 13957 storeSelection();
bgneal@45 13958 dom.split(listParent, listElm);
bgneal@45 13959 restoreSelection();
bgneal@45 13960 }
bgneal@45 13961 }
bgneal@45 13962 },
bgneal@45 13963
bgneal@45 13964 // Override commands to use the text formatter engine
bgneal@45 13965 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
bgneal@45 13966 toggleFormat(command);
bgneal@45 13967 },
bgneal@45 13968
bgneal@45 13969 // Override commands to use the text formatter engine
bgneal@45 13970 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
bgneal@45 13971 toggleFormat(command, value);
bgneal@45 13972 },
bgneal@45 13973
bgneal@45 13974 FontSize : function(command, ui, value) {
bgneal@45 13975 var fontClasses, fontSizes;
bgneal@45 13976
bgneal@45 13977 // Convert font size 1-7 to styles
bgneal@45 13978 if (value >= 1 && value <= 7) {
bgneal@45 13979 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@45 13980 fontClasses = tinymce.explode(settings.font_size_classes);
bgneal@45 13981
bgneal@45 13982 if (fontClasses)
bgneal@45 13983 value = fontClasses[value - 1] || value;
bgneal@45 13984 else
bgneal@45 13985 value = fontSizes[value - 1] || value;
bgneal@45 13986 }
bgneal@45 13987
bgneal@45 13988 toggleFormat(command, value);
bgneal@45 13989 },
bgneal@45 13990
bgneal@45 13991 RemoveFormat : function(command) {
bgneal@45 13992 formatter.remove(command);
bgneal@45 13993 },
bgneal@45 13994
bgneal@45 13995 mceBlockQuote : function(command) {
bgneal@45 13996 toggleFormat('blockquote');
bgneal@45 13997 },
bgneal@45 13998
bgneal@45 13999 FormatBlock : function(command, ui, value) {
bgneal@45 14000 return toggleFormat(value || 'p');
bgneal@45 14001 },
bgneal@45 14002
bgneal@45 14003 mceCleanup : function() {
bgneal@45 14004 var bookmark = selection.getBookmark();
bgneal@45 14005
bgneal@45 14006 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
bgneal@45 14007
bgneal@45 14008 selection.moveToBookmark(bookmark);
bgneal@45 14009 },
bgneal@45 14010
bgneal@45 14011 mceRemoveNode : function(command, ui, value) {
bgneal@45 14012 var node = value || selection.getNode();
bgneal@45 14013
bgneal@45 14014 // Make sure that the body node isn't removed
bgneal@45 14015 if (node != editor.getBody()) {
bgneal@45 14016 storeSelection();
bgneal@45 14017 editor.dom.remove(node, TRUE);
bgneal@45 14018 restoreSelection();
bgneal@45 14019 }
bgneal@45 14020 },
bgneal@45 14021
bgneal@45 14022 mceSelectNodeDepth : function(command, ui, value) {
bgneal@45 14023 var counter = 0;
bgneal@45 14024
bgneal@45 14025 dom.getParent(selection.getNode(), function(node) {
bgneal@45 14026 if (node.nodeType == 1 && counter++ == value) {
bgneal@45 14027 selection.select(node);
bgneal@45 14028 return FALSE;
bgneal@45 14029 }
bgneal@45 14030 }, editor.getBody());
bgneal@45 14031 },
bgneal@45 14032
bgneal@45 14033 mceSelectNode : function(command, ui, value) {
bgneal@45 14034 selection.select(value);
bgneal@45 14035 },
bgneal@45 14036
bgneal@45 14037 mceInsertContent : function(command, ui, value) {
bgneal@45 14038 var parser, serializer, parentNode, rootNode, fragment, args,
bgneal@45 14039 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
bgneal@45 14040
bgneal@45 14041 //selection.normalize();
bgneal@45 14042
bgneal@45 14043 // Setup parser and serializer
bgneal@45 14044 parser = editor.parser;
bgneal@45 14045 serializer = new tinymce.html.Serializer({}, editor.schema);
bgneal@45 14046 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
bgneal@45 14047
bgneal@45 14048 // Run beforeSetContent handlers on the HTML to be inserted
bgneal@45 14049 args = {content: value, format: 'html'};
bgneal@45 14050 selection.onBeforeSetContent.dispatch(selection, args);
bgneal@45 14051 value = args.content;
bgneal@45 14052
bgneal@45 14053 // Add caret at end of contents if it's missing
bgneal@45 14054 if (value.indexOf('{$caret}') == -1)
bgneal@45 14055 value += '{$caret}';
bgneal@45 14056
bgneal@45 14057 // Replace the caret marker with a span bookmark element
bgneal@45 14058 value = value.replace(/\{\$caret\}/, bookmarkHtml);
bgneal@45 14059
bgneal@45 14060 // Insert node maker where we will insert the new HTML and get it's parent
bgneal@45 14061 if (!selection.isCollapsed())
bgneal@45 14062 editor.getDoc().execCommand('Delete', false, null);
bgneal@45 14063
bgneal@45 14064 parentNode = selection.getNode();
bgneal@45 14065
bgneal@45 14066 // Parse the fragment within the context of the parent node
bgneal@45 14067 args = {context : parentNode.nodeName.toLowerCase()};
bgneal@45 14068 fragment = parser.parse(value, args);
bgneal@45 14069
bgneal@45 14070 // Move the caret to a more suitable location
bgneal@45 14071 node = fragment.lastChild;
bgneal@45 14072 if (node.attr('id') == 'mce_marker') {
bgneal@45 14073 marker = node;
bgneal@45 14074
bgneal@45 14075 for (node = node.prev; node; node = node.walk(true)) {
bgneal@45 14076 if (node.type == 3 || !dom.isBlock(node.name)) {
bgneal@45 14077 node.parent.insert(marker, node, node.name === 'br');
bgneal@45 14078 break;
bgneal@45 14079 }
bgneal@45 14080 }
bgneal@45 14081 }
bgneal@45 14082
bgneal@45 14083 // If parser says valid we can insert the contents into that parent
bgneal@45 14084 if (!args.invalid) {
bgneal@45 14085 value = serializer.serialize(fragment);
bgneal@45 14086
bgneal@45 14087 // Check if parent is empty or only has one BR element then set the innerHTML of that parent
bgneal@45 14088 node = parentNode.firstChild;
bgneal@45 14089 node2 = parentNode.lastChild;
bgneal@45 14090 if (!node || (node === node2 && node.nodeName === 'BR'))
bgneal@45 14091 dom.setHTML(parentNode, value);
bgneal@45 14092 else
bgneal@45 14093 selection.setContent(value);
bgneal@45 14094 } else {
bgneal@45 14095 // If the fragment was invalid within that context then we need
bgneal@45 14096 // to parse and process the parent it's inserted into
bgneal@45 14097
bgneal@45 14098 // Insert bookmark node and get the parent
bgneal@45 14099 selection.setContent(bookmarkHtml);
bgneal@45 14100 parentNode = editor.selection.getNode();
bgneal@45 14101 rootNode = editor.getBody();
bgneal@45 14102
bgneal@45 14103 // Opera will return the document node when selection is in root
bgneal@45 14104 if (parentNode.nodeType == 9)
bgneal@45 14105 parentNode = node = rootNode;
bgneal@45 14106 else
bgneal@45 14107 node = parentNode;
bgneal@45 14108
bgneal@45 14109 // Find the ancestor just before the root element
bgneal@45 14110 while (node !== rootNode) {
bgneal@45 14111 parentNode = node;
bgneal@45 14112 node = node.parentNode;
bgneal@45 14113 }
bgneal@45 14114
bgneal@45 14115 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
bgneal@45 14116 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
bgneal@45 14117 value = serializer.serialize(
bgneal@45 14118 parser.parse(
bgneal@45 14119 // Need to replace by using a function since $ in the contents would otherwise be a problem
bgneal@45 14120 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
bgneal@45 14121 return serializer.serialize(fragment);
bgneal@45 14122 })
bgneal@45 14123 )
bgneal@45 14124 );
bgneal@45 14125
bgneal@45 14126 // Set the inner/outer HTML depending on if we are in the root or not
bgneal@45 14127 if (parentNode == rootNode)
bgneal@45 14128 dom.setHTML(rootNode, value);
bgneal@45 14129 else
bgneal@45 14130 dom.setOuterHTML(parentNode, value);
bgneal@45 14131 }
bgneal@45 14132
bgneal@45 14133 marker = dom.get('mce_marker');
bgneal@45 14134
bgneal@45 14135 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
bgneal@45 14136 nodeRect = dom.getRect(marker);
bgneal@45 14137 viewPortRect = dom.getViewPort(editor.getWin());
bgneal@45 14138
bgneal@45 14139 // Check if node is out side the viewport if it is then scroll to it
bgneal@45 14140 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
bgneal@45 14141 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
bgneal@45 14142 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
bgneal@45 14143 viewportBodyElement.scrollLeft = nodeRect.x;
bgneal@45 14144 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
bgneal@45 14145 }
bgneal@45 14146
bgneal@45 14147 // Move selection before marker and remove it
bgneal@45 14148 rng = dom.createRng();
bgneal@45 14149
bgneal@45 14150 // If previous sibling is a text node set the selection to the end of that node
bgneal@45 14151 node = marker.previousSibling;
bgneal@45 14152 if (node && node.nodeType == 3) {
bgneal@45 14153 rng.setStart(node, node.nodeValue.length);
bgneal@45 14154 } else {
bgneal@45 14155 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
bgneal@45 14156 rng.setStartBefore(marker);
bgneal@45 14157 rng.setEndBefore(marker);
bgneal@45 14158 }
bgneal@45 14159
bgneal@45 14160 // Remove the marker node and set the new range
bgneal@45 14161 dom.remove(marker);
bgneal@45 14162 selection.setRng(rng);
bgneal@45 14163
bgneal@45 14164 // Dispatch after event and add any visual elements needed
bgneal@45 14165 selection.onSetContent.dispatch(selection, args);
bgneal@45 14166 editor.addVisual();
bgneal@45 14167 },
bgneal@45 14168
bgneal@45 14169 mceInsertRawHTML : function(command, ui, value) {
bgneal@45 14170 selection.setContent('tiny_mce_marker');
bgneal@45 14171 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
bgneal@45 14172 },
bgneal@45 14173
bgneal@45 14174 mceSetContent : function(command, ui, value) {
bgneal@45 14175 editor.setContent(value);
bgneal@45 14176 },
bgneal@45 14177
bgneal@45 14178 'Indent,Outdent' : function(command) {
bgneal@45 14179 var intentValue, indentUnit, value;
bgneal@45 14180
bgneal@45 14181 // Setup indent level
bgneal@45 14182 intentValue = settings.indentation;
bgneal@45 14183 indentUnit = /[a-z%]+$/i.exec(intentValue);
bgneal@45 14184 intentValue = parseInt(intentValue);
bgneal@45 14185
bgneal@45 14186 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
bgneal@45 14187 each(selection.getSelectedBlocks(), function(element) {
bgneal@45 14188 if (command == 'outdent') {
bgneal@45 14189 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
bgneal@45 14190 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
bgneal@45 14191 } else
bgneal@45 14192 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
bgneal@45 14193 });
bgneal@45 14194 } else
bgneal@45 14195 execNativeCommand(command);
bgneal@45 14196 },
bgneal@45 14197
bgneal@45 14198 mceRepaint : function() {
bgneal@45 14199 var bookmark;
bgneal@45 14200
bgneal@45 14201 if (tinymce.isGecko) {
bgneal@45 14202 try {
bgneal@45 14203 storeSelection(TRUE);
bgneal@45 14204
bgneal@45 14205 if (selection.getSel())
bgneal@45 14206 selection.getSel().selectAllChildren(editor.getBody());
bgneal@45 14207
bgneal@45 14208 selection.collapse(TRUE);
bgneal@45 14209 restoreSelection();
bgneal@45 14210 } catch (ex) {
bgneal@45 14211 // Ignore
bgneal@45 14212 }
bgneal@45 14213 }
bgneal@45 14214 },
bgneal@45 14215
bgneal@45 14216 mceToggleFormat : function(command, ui, value) {
bgneal@45 14217 formatter.toggle(value);
bgneal@45 14218 },
bgneal@45 14219
bgneal@45 14220 InsertHorizontalRule : function() {
bgneal@45 14221 editor.execCommand('mceInsertContent', false, '<hr />');
bgneal@45 14222 },
bgneal@45 14223
bgneal@45 14224 mceToggleVisualAid : function() {
bgneal@45 14225 editor.hasVisual = !editor.hasVisual;
bgneal@45 14226 editor.addVisual();
bgneal@45 14227 },
bgneal@45 14228
bgneal@45 14229 mceReplaceContent : function(command, ui, value) {
bgneal@45 14230 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
bgneal@45 14231 },
bgneal@45 14232
bgneal@45 14233 mceInsertLink : function(command, ui, value) {
bgneal@45 14234 var anchor;
bgneal@45 14235
bgneal@45 14236 if (typeof(value) == 'string')
bgneal@45 14237 value = {href : value};
bgneal@45 14238
bgneal@45 14239 anchor = dom.getParent(selection.getNode(), 'a');
bgneal@45 14240
bgneal@45 14241 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
bgneal@45 14242 value.href = value.href.replace(' ', '%20');
bgneal@45 14243
bgneal@45 14244 // Remove existing links if there could be child links or that the href isn't specified
bgneal@45 14245 if (!anchor || !value.href) {
bgneal@45 14246 formatter.remove('link');
bgneal@45 14247 }
bgneal@45 14248
bgneal@45 14249 // Apply new link to selection
bgneal@45 14250 if (value.href) {
bgneal@45 14251 formatter.apply('link', value, anchor);
bgneal@45 14252 }
bgneal@45 14253 },
bgneal@45 14254
bgneal@45 14255 selectAll : function() {
bgneal@45 14256 var root = dom.getRoot(), rng = dom.createRng();
bgneal@45 14257
bgneal@45 14258 rng.setStart(root, 0);
bgneal@45 14259 rng.setEnd(root, root.childNodes.length);
bgneal@45 14260
bgneal@45 14261 editor.selection.setRng(rng);
bgneal@45 14262 }
bgneal@45 14263 });
bgneal@45 14264
bgneal@45 14265 // Add queryCommandState overrides
bgneal@45 14266 addCommands({
bgneal@45 14267 // Override justify commands
bgneal@45 14268 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@45 14269 var name = 'align' + command.substring(7);
bgneal@45 14270 // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
bgneal@45 14271 // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
bgneal@45 14272 var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
bgneal@45 14273 var matches = tinymce.map(nodes, function(node) {
bgneal@45 14274 return !!formatter.matchNode(node, name);
bgneal@45 14275 });
bgneal@45 14276 return tinymce.inArray(matches, TRUE) !== -1;
bgneal@45 14277 },
bgneal@45 14278
bgneal@45 14279 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
bgneal@45 14280 return isFormatMatch(command);
bgneal@45 14281 },
bgneal@45 14282
bgneal@45 14283 mceBlockQuote : function() {
bgneal@45 14284 return isFormatMatch('blockquote');
bgneal@45 14285 },
bgneal@45 14286
bgneal@45 14287 Outdent : function() {
bgneal@45 14288 var node;
bgneal@45 14289
bgneal@45 14290 if (settings.inline_styles) {
bgneal@45 14291 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@45 14292 return TRUE;
bgneal@45 14293
bgneal@45 14294 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@45 14295 return TRUE;
bgneal@45 14296 }
bgneal@45 14297
bgneal@45 14298 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
bgneal@45 14299 },
bgneal@45 14300
bgneal@45 14301 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@45 14302 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
bgneal@45 14303 }
bgneal@45 14304 }, 'state');
bgneal@45 14305
bgneal@45 14306 // Add queryCommandValue overrides
bgneal@45 14307 addCommands({
bgneal@45 14308 'FontSize,FontName' : function(command) {
bgneal@45 14309 var value = 0, parent;
bgneal@45 14310
bgneal@45 14311 if (parent = dom.getParent(selection.getNode(), 'span')) {
bgneal@45 14312 if (command == 'fontsize')
bgneal@45 14313 value = parent.style.fontSize;
bgneal@45 14314 else
bgneal@45 14315 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
bgneal@45 14316 }
bgneal@45 14317
bgneal@45 14318 return value;
bgneal@45 14319 }
bgneal@45 14320 }, 'value');
bgneal@45 14321
bgneal@45 14322 // Add undo manager logic
bgneal@45 14323 if (settings.custom_undo_redo) {
bgneal@45 14324 addCommands({
bgneal@45 14325 Undo : function() {
bgneal@45 14326 editor.undoManager.undo();
bgneal@45 14327 },
bgneal@45 14328
bgneal@45 14329 Redo : function() {
bgneal@45 14330 editor.undoManager.redo();
bgneal@45 14331 }
bgneal@45 14332 });
bgneal@45 14333 }
bgneal@45 14334 };
bgneal@45 14335 })(tinymce);
bgneal@45 14336
bgneal@45 14337 (function(tinymce) {
bgneal@45 14338 var Dispatcher = tinymce.util.Dispatcher;
bgneal@45 14339
bgneal@45 14340 tinymce.UndoManager = function(editor) {
bgneal@45 14341 var self, index = 0, data = [], beforeBookmark;
bgneal@45 14342
bgneal@45 14343 function getContent() {
bgneal@45 14344 // Remove whitespace before/after and remove pure bogus nodes
bgneal@45 14345 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
bgneal@45 14346 };
bgneal@45 14347
bgneal@45 14348 return self = {
bgneal@45 14349 typing : false,
bgneal@45 14350
bgneal@45 14351 onAdd : new Dispatcher(self),
bgneal@45 14352
bgneal@45 14353 onUndo : new Dispatcher(self),
bgneal@45 14354
bgneal@45 14355 onRedo : new Dispatcher(self),
bgneal@45 14356
bgneal@45 14357 beforeChange : function() {
bgneal@45 14358 beforeBookmark = editor.selection.getBookmark(2, true);
bgneal@45 14359 },
bgneal@45 14360
bgneal@45 14361 add : function(level) {
bgneal@45 14362 var i, settings = editor.settings, lastLevel;
bgneal@45 14363
bgneal@45 14364 level = level || {};
bgneal@45 14365 level.content = getContent();
bgneal@45 14366
bgneal@45 14367 // Add undo level if needed
bgneal@45 14368 lastLevel = data[index];
bgneal@45 14369 if (lastLevel && lastLevel.content == level.content)
bgneal@45 14370 return null;
bgneal@45 14371
bgneal@45 14372 // Set before bookmark on previous level
bgneal@45 14373 if (data[index])
bgneal@45 14374 data[index].beforeBookmark = beforeBookmark;
bgneal@45 14375
bgneal@45 14376 // Time to compress
bgneal@45 14377 if (settings.custom_undo_redo_levels) {
bgneal@45 14378 if (data.length > settings.custom_undo_redo_levels) {
bgneal@45 14379 for (i = 0; i < data.length - 1; i++)
bgneal@45 14380 data[i] = data[i + 1];
bgneal@45 14381
bgneal@45 14382 data.length--;
bgneal@45 14383 index = data.length;
bgneal@45 14384 }
bgneal@45 14385 }
bgneal@45 14386
bgneal@45 14387 // Get a non intrusive normalized bookmark
bgneal@45 14388 level.bookmark = editor.selection.getBookmark(2, true);
bgneal@45 14389
bgneal@45 14390 // Crop array if needed
bgneal@45 14391 if (index < data.length - 1)
bgneal@45 14392 data.length = index + 1;
bgneal@45 14393
bgneal@45 14394 data.push(level);
bgneal@45 14395 index = data.length - 1;
bgneal@45 14396
bgneal@45 14397 self.onAdd.dispatch(self, level);
bgneal@45 14398 editor.isNotDirty = 0;
bgneal@45 14399
bgneal@45 14400 return level;
bgneal@45 14401 },
bgneal@45 14402
bgneal@45 14403 undo : function() {
bgneal@45 14404 var level, i;
bgneal@45 14405
bgneal@45 14406 if (self.typing) {
bgneal@45 14407 self.add();
bgneal@45 14408 self.typing = false;
bgneal@45 14409 }
bgneal@45 14410
bgneal@45 14411 if (index > 0) {
bgneal@45 14412 level = data[--index];
bgneal@45 14413
bgneal@45 14414 editor.setContent(level.content, {format : 'raw'});
bgneal@45 14415 editor.selection.moveToBookmark(level.beforeBookmark);
bgneal@45 14416
bgneal@45 14417 self.onUndo.dispatch(self, level);
bgneal@45 14418 }
bgneal@45 14419
bgneal@45 14420 return level;
bgneal@45 14421 },
bgneal@45 14422
bgneal@45 14423 redo : function() {
bgneal@45 14424 var level;
bgneal@45 14425
bgneal@45 14426 if (index < data.length - 1) {
bgneal@45 14427 level = data[++index];
bgneal@45 14428
bgneal@45 14429 editor.setContent(level.content, {format : 'raw'});
bgneal@45 14430 editor.selection.moveToBookmark(level.bookmark);
bgneal@45 14431
bgneal@45 14432 self.onRedo.dispatch(self, level);
bgneal@45 14433 }
bgneal@45 14434
bgneal@45 14435 return level;
bgneal@45 14436 },
bgneal@45 14437
bgneal@45 14438 clear : function() {
bgneal@45 14439 data = [];
bgneal@45 14440 index = 0;
bgneal@45 14441 self.typing = false;
bgneal@45 14442 },
bgneal@45 14443
bgneal@45 14444 hasUndo : function() {
bgneal@45 14445 return index > 0 || this.typing;
bgneal@45 14446 },
bgneal@45 14447
bgneal@45 14448 hasRedo : function() {
bgneal@45 14449 return index < data.length - 1 && !this.typing;
bgneal@45 14450 }
bgneal@45 14451 };
bgneal@45 14452 };
bgneal@45 14453 })(tinymce);
bgneal@45 14454
bgneal@45 14455 (function(tinymce) {
bgneal@45 14456 // Shorten names
bgneal@45 14457 var Event = tinymce.dom.Event,
bgneal@45 14458 isIE = tinymce.isIE,
bgneal@45 14459 isGecko = tinymce.isGecko,
bgneal@45 14460 isOpera = tinymce.isOpera,
bgneal@45 14461 each = tinymce.each,
bgneal@45 14462 extend = tinymce.extend,
bgneal@45 14463 TRUE = true,
bgneal@45 14464 FALSE = false;
bgneal@45 14465
bgneal@45 14466 function splitList(selection, dom, li) {
bgneal@45 14467 var listBlock, block;
bgneal@45 14468
bgneal@45 14469 // TODO: Fix so this doesn't use native IE logic
bgneal@45 14470 if (dom.isEmpty(li) && !isIE) {
bgneal@45 14471 listBlock = dom.getParent(li, 'ul,ol');
bgneal@45 14472
bgneal@45 14473 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
bgneal@45 14474 dom.split(listBlock, li);
bgneal@45 14475 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
bgneal@45 14476 dom.replace(block, li);
bgneal@45 14477 selection.select(block, 1);
bgneal@45 14478 }
bgneal@45 14479
bgneal@45 14480 return FALSE;
bgneal@45 14481 }
bgneal@45 14482
bgneal@45 14483 return TRUE;
bgneal@45 14484 };
bgneal@45 14485
bgneal@45 14486 tinymce.create('tinymce.ForceBlocks', {
bgneal@45 14487 ForceBlocks : function(ed) {
bgneal@45 14488 var t = this, s = ed.settings, elm;
bgneal@45 14489
bgneal@45 14490 t.editor = ed;
bgneal@45 14491 t.dom = ed.dom;
bgneal@45 14492 elm = (s.forced_root_block || 'p').toLowerCase();
bgneal@45 14493 s.element = elm.toUpperCase();
bgneal@45 14494
bgneal@45 14495 ed.onPreInit.add(t.setup, t);
bgneal@45 14496 },
bgneal@45 14497
bgneal@45 14498 setup : function() {
bgneal@45 14499 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
bgneal@45 14500
bgneal@45 14501 // Force root blocks
bgneal@45 14502 if (s.forced_root_block) {
bgneal@45 14503 function addRootBlocks() {
bgneal@45 14504 var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
bgneal@45 14505
bgneal@45 14506 if (!node || node.nodeType !== 1)
bgneal@45 14507 return;
bgneal@45 14508
bgneal@45 14509 // Check if node is wrapped in block
bgneal@45 14510 while (node != rootNode) {
bgneal@45 14511 if (blockElements[node.nodeName])
bgneal@45 14512 return;
bgneal@45 14513
bgneal@45 14514 node = node.parentNode;
bgneal@45 14515 }
bgneal@45 14516
bgneal@45 14517 // Get current selection
bgneal@45 14518 rng = selection.getRng();
bgneal@45 14519 if (rng.setStart) {
bgneal@45 14520 startContainer = rng.startContainer;
bgneal@45 14521 startOffset = rng.startOffset;
bgneal@45 14522 endContainer = rng.endContainer;
bgneal@45 14523 endOffset = rng.endOffset;
bgneal@45 14524 } else {
bgneal@45 14525 // Force control range into text range
bgneal@45 14526 if (rng.item) {
bgneal@45 14527 rng = ed.getDoc().body.createTextRange();
bgneal@45 14528 rng.moveToElementText(rng.item(0));
bgneal@45 14529 }
bgneal@45 14530
bgneal@45 14531 tmpRng = rng.duplicate();
bgneal@45 14532 tmpRng.collapse(true);
bgneal@45 14533 startOffset = tmpRng.move('character', offset) * -1;
bgneal@45 14534
bgneal@45 14535 if (!tmpRng.collapsed) {
bgneal@45 14536 tmpRng = rng.duplicate();
bgneal@45 14537 tmpRng.collapse(false);
bgneal@45 14538 endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
bgneal@45 14539 }
bgneal@45 14540 }
bgneal@45 14541
bgneal@45 14542 // Wrap non block elements and text nodes
bgneal@45 14543 for (node = rootNode.firstChild; node; node) {
bgneal@45 14544 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
bgneal@45 14545 if (!rootBlockNode) {
bgneal@45 14546 rootBlockNode = dom.create(s.forced_root_block);
bgneal@45 14547 node.parentNode.insertBefore(rootBlockNode, node);
bgneal@45 14548 }
bgneal@45 14549
bgneal@45 14550 tempNode = node;
bgneal@45 14551 node = node.nextSibling;
bgneal@45 14552 rootBlockNode.appendChild(tempNode);
bgneal@45 14553 } else {
bgneal@45 14554 rootBlockNode = null;
bgneal@45 14555 node = node.nextSibling;
bgneal@45 14556 }
bgneal@45 14557 }
bgneal@45 14558
bgneal@45 14559 if (rng.setStart) {
bgneal@45 14560 rng.setStart(startContainer, startOffset);
bgneal@45 14561 rng.setEnd(endContainer, endOffset);
bgneal@45 14562 selection.setRng(rng);
bgneal@45 14563 } else {
bgneal@45 14564 try {
bgneal@45 14565 rng = ed.getDoc().body.createTextRange();
bgneal@45 14566 rng.moveToElementText(rootNode);
bgneal@45 14567 rng.collapse(true);
bgneal@45 14568 rng.moveStart('character', startOffset);
bgneal@45 14569
bgneal@45 14570 if (endOffset > 0)
bgneal@45 14571 rng.moveEnd('character', endOffset);
bgneal@45 14572
bgneal@45 14573 rng.select();
bgneal@45 14574 } catch (ex) {
bgneal@45 14575 // Ignore
bgneal@45 14576 }
bgneal@45 14577 }
bgneal@45 14578
bgneal@45 14579 ed.nodeChanged();
bgneal@45 14580 };
bgneal@45 14581
bgneal@45 14582 ed.onKeyUp.add(addRootBlocks);
bgneal@45 14583 ed.onClick.add(addRootBlocks);
bgneal@45 14584 }
bgneal@45 14585
bgneal@45 14586 if (s.force_br_newlines) {
bgneal@45 14587 // Force IE to produce BRs on enter
bgneal@45 14588 if (isIE) {
bgneal@45 14589 ed.onKeyPress.add(function(ed, e) {
bgneal@45 14590 var n;
bgneal@45 14591
bgneal@45 14592 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
bgneal@45 14593 selection.setContent('<br id="__" /> ', {format : 'raw'});
bgneal@45 14594 n = dom.get('__');
bgneal@45 14595 n.removeAttribute('id');
bgneal@45 14596 selection.select(n);
bgneal@45 14597 selection.collapse();
bgneal@45 14598 return Event.cancel(e);
bgneal@45 14599 }
bgneal@45 14600 });
bgneal@45 14601 }
bgneal@45 14602 }
bgneal@45 14603
bgneal@45 14604 if (s.force_p_newlines) {
bgneal@45 14605 ed.onKeyPress.add(function(ed, e) {
bgneal@45 14606 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
bgneal@45 14607 Event.cancel(e);
bgneal@45 14608 });
bgneal@45 14609
bgneal@45 14610 if (isGecko) {
bgneal@45 14611 ed.onKeyDown.add(function(ed, e) {
bgneal@45 14612 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
bgneal@45 14613 t.backspaceDelete(e, e.keyCode == 8);
bgneal@45 14614 });
bgneal@45 14615 }
bgneal@45 14616 }
bgneal@45 14617
bgneal@45 14618 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
bgneal@45 14619 if (tinymce.isWebKit) {
bgneal@45 14620 function insertBr(ed) {
bgneal@45 14621 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
bgneal@45 14622
bgneal@45 14623 // Insert BR element
bgneal@45 14624 rng.insertNode(br = dom.create('br'));
bgneal@45 14625
bgneal@45 14626 // Place caret after BR
bgneal@45 14627 rng.setStartAfter(br);
bgneal@45 14628 rng.setEndAfter(br);
bgneal@45 14629 selection.setRng(rng);
bgneal@45 14630
bgneal@45 14631 // Could not place caret after BR then insert an nbsp entity and move the caret
bgneal@45 14632 if (selection.getSel().focusNode == br.previousSibling) {
bgneal@45 14633 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
bgneal@45 14634 selection.collapse(TRUE);
bgneal@45 14635 }
bgneal@45 14636
bgneal@45 14637 // Create a temporary DIV after the BR and get the position as it
bgneal@45 14638 // seems like getPos() returns 0 for text nodes and BR elements.
bgneal@45 14639 dom.insertAfter(div, br);
bgneal@45 14640 divYPos = dom.getPos(div).y;
bgneal@45 14641 dom.remove(div);
bgneal@45 14642
bgneal@45 14643 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
bgneal@45 14644 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
bgneal@45 14645 ed.getWin().scrollTo(0, divYPos);
bgneal@45 14646 };
bgneal@45 14647
bgneal@45 14648 ed.onKeyPress.add(function(ed, e) {
bgneal@45 14649 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
bgneal@45 14650 insertBr(ed);
bgneal@45 14651 Event.cancel(e);
bgneal@45 14652 }
bgneal@45 14653 });
bgneal@45 14654 }
bgneal@45 14655
bgneal@45 14656 // IE specific fixes
bgneal@45 14657 if (isIE) {
bgneal@45 14658 // Replaces IE:s auto generated paragraphs with the specified element name
bgneal@45 14659 if (s.element != 'P') {
bgneal@45 14660 ed.onKeyPress.add(function(ed, e) {
bgneal@45 14661 t.lastElm = selection.getNode().nodeName;
bgneal@45 14662 });
bgneal@45 14663
bgneal@45 14664 ed.onKeyUp.add(function(ed, e) {
bgneal@45 14665 var bl, n = selection.getNode(), b = ed.getBody();
bgneal@45 14666
bgneal@45 14667 if (b.childNodes.length === 1 && n.nodeName == 'P') {
bgneal@45 14668 n = dom.rename(n, s.element);
bgneal@45 14669 selection.select(n);
bgneal@45 14670 selection.collapse();
bgneal@45 14671 ed.nodeChanged();
bgneal@45 14672 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bgneal@45 14673 bl = dom.getParent(n, 'p');
bgneal@45 14674
bgneal@45 14675 if (bl) {
bgneal@45 14676 dom.rename(bl, s.element);
bgneal@45 14677 ed.nodeChanged();
bgneal@45 14678 }
bgneal@45 14679 }
bgneal@45 14680 });
bgneal@45 14681 }
bgneal@45 14682 }
bgneal@45 14683 },
bgneal@45 14684
bgneal@45 14685 getParentBlock : function(n) {
bgneal@45 14686 var d = this.dom;
bgneal@45 14687
bgneal@45 14688 return d.getParent(n, d.isBlock);
bgneal@45 14689 },
bgneal@45 14690
bgneal@45 14691 insertPara : function(e) {
bgneal@45 14692 var t = this, ed = t.editor, dom = ed.dom, selection = ed.selection, d = ed.getDoc(), se = ed.settings, s = selection.getSel(), r = selection.getRng(true), b = d.body;
bgneal@45 14693 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, ch, containerBlock, beforeCaretNode, afterCaretNode;
bgneal@45 14694
bgneal@45 14695 // Checks if the selection/caret is at the end of the specified block element
bgneal@45 14696 function isAtEnd(rng, par) {
bgneal@45 14697 var rng2 = rng.cloneRange();
bgneal@45 14698
bgneal@45 14699 rng2.setStart(rng.endContainer, rng.endOffset);
bgneal@45 14700 rng2.setEndAfter(par);
bgneal@45 14701
bgneal@45 14702 // 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
bgneal@45 14703 return dom.isEmpty(rng2.cloneContents());
bgneal@45 14704 };
bgneal@45 14705
bgneal@45 14706 function moveToCaretPosition(root, scroll) {
bgneal@45 14707 var walker, node, rng, y, vp;
bgneal@45 14708
bgneal@45 14709 rng = dom.createRng();
bgneal@45 14710
bgneal@45 14711 if (root.hasChildNodes()) {
bgneal@45 14712 walker = new tinymce.dom.TreeWalker(root, root);
bgneal@45 14713
bgneal@45 14714 while (node = walker.current()) {
bgneal@45 14715 if (node.nodeType == 3) {
bgneal@45 14716 rng.setStart(node, 0);
bgneal@45 14717 rng.setEnd(node, 0);
bgneal@45 14718 break;
bgneal@45 14719 }
bgneal@45 14720
bgneal@45 14721 if (/^(BR|IMG)$/.test(node.nodeName)) {
bgneal@45 14722 rng.setStartBefore(node);
bgneal@45 14723 rng.setEndBefore(node);
bgneal@45 14724 break;
bgneal@45 14725 }
bgneal@45 14726
bgneal@45 14727 node = walker.next();
bgneal@45 14728 }
bgneal@45 14729 } else {
bgneal@45 14730 rng.setStart(root, 0);
bgneal@45 14731 rng.setEnd(root, 0);
bgneal@45 14732 }
bgneal@45 14733
bgneal@45 14734 selection.setRng(rng);
bgneal@45 14735
bgneal@45 14736 if (scroll !== false) {
bgneal@45 14737 vp = dom.getViewPort(ed.getWin());
bgneal@45 14738
bgneal@45 14739 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
bgneal@45 14740 y = dom.getPos(root).y;
bgneal@45 14741
bgneal@45 14742 // Is element within viewport
bgneal@45 14743 if (y < vp.y || y + 25 > vp.y + vp.h) {
bgneal@45 14744 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
bgneal@45 14745
bgneal@45 14746 /*console.debug(
bgneal@45 14747 'Element: y=' + y + ', h=' + ch + ', ' +
bgneal@45 14748 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
bgneal@45 14749 );*/
bgneal@45 14750 }
bgneal@45 14751 }
bgneal@45 14752 };
bgneal@45 14753
bgneal@45 14754 function clean(node) {
bgneal@45 14755 if (node.nodeType == 3 && node.nodeValue.length == 0) {
bgneal@45 14756 dom.remove(node);
bgneal@45 14757 }
bgneal@45 14758
bgneal@45 14759 if (node.hasChildNodes()) {
bgneal@45 14760 for (var i = 0; i < node.childNodes.length; i++) {
bgneal@45 14761 clean(node.childNodes[i]);
bgneal@45 14762 }
bgneal@45 14763 }
bgneal@45 14764 };
bgneal@45 14765
bgneal@45 14766 ed.undoManager.beforeChange();
bgneal@45 14767
bgneal@45 14768 // If root blocks are forced then use Operas default behavior since it's really good
bgneal@45 14769 // Removed due to bug: #1853816
bgneal@45 14770 // if (se.forced_root_block && isOpera)
bgneal@45 14771 // return TRUE;
bgneal@45 14772
bgneal@45 14773 // Handle selection direction for browsers that support it
bgneal@45 14774 if (typeof s.anchorNode != "undefined") {
bgneal@45 14775 // Setup before range
bgneal@45 14776 rb = dom.createRng();
bgneal@45 14777
bgneal@45 14778 // If is before the first block element and in body, then move it into first block element
bgneal@45 14779 rb.setStart(s.anchorNode, s.anchorOffset);
bgneal@45 14780 rb.collapse(TRUE);
bgneal@45 14781
bgneal@45 14782 // Setup after range
bgneal@45 14783 ra = dom.createRng();
bgneal@45 14784
bgneal@45 14785 // If is before the first block element and in body, then move it into first block element
bgneal@45 14786 ra.setStart(s.focusNode, s.focusOffset);
bgneal@45 14787 ra.collapse(TRUE);
bgneal@45 14788
bgneal@45 14789 // Setup start/end points
bgneal@45 14790 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
bgneal@45 14791 sn = dir ? s.anchorNode : s.focusNode;
bgneal@45 14792 so = dir ? s.anchorOffset : s.focusOffset;
bgneal@45 14793 en = dir ? s.focusNode : s.anchorNode;
bgneal@45 14794 eo = dir ? s.focusOffset : s.anchorOffset;
bgneal@45 14795 } else {
bgneal@45 14796 rb = r.cloneRange();
bgneal@45 14797 rb.collapse(TRUE);
bgneal@45 14798
bgneal@45 14799 ra = r.cloneRange();
bgneal@45 14800 ra.collapse(FALSE);
bgneal@45 14801
bgneal@45 14802 sn = r.startContainer;
bgneal@45 14803 so = r.startOffset;
bgneal@45 14804 en = r.endContainer;
bgneal@45 14805 eo = r.endOffset;
bgneal@45 14806 }
bgneal@45 14807
bgneal@45 14808 // If selection is in empty table cell
bgneal@45 14809 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
bgneal@45 14810 sn.innerHTML = '';
bgneal@45 14811
bgneal@45 14812 // Create two new block elements
bgneal@45 14813 dom.add(sn, se.element, null, isIE ? '' : '<br />');
bgneal@45 14814 aft = dom.add(sn, se.element, null, isIE ? '' : '<br />');
bgneal@45 14815 moveToCaretPosition(aft);
bgneal@45 14816
bgneal@45 14817 return FALSE;
bgneal@45 14818 }
bgneal@45 14819
bgneal@45 14820 // If the caret is in an invalid location in FF we need to move it into the first block
bgneal@45 14821 if (sn == b && en == b && b.firstChild && dom.isBlock(b.firstChild)) {
bgneal@45 14822 sn = en = sn.firstChild;
bgneal@45 14823 so = eo = 0;
bgneal@45 14824 rb = dom.createRng();
bgneal@45 14825 rb.setStart(sn, 0);
bgneal@45 14826 ra = dom.createRng();
bgneal@45 14827 ra.setStart(en, 0);
bgneal@45 14828 }
bgneal@45 14829
bgneal@45 14830 // If the body is totally empty add a BR element this might happen on webkit
bgneal@45 14831 if (!b.hasChildNodes()) {
bgneal@45 14832 b.appendChild(dom.create('br'));
bgneal@45 14833 }
bgneal@45 14834
bgneal@45 14835 // Never use body as start or end node
bgneal@45 14836 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 14837 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
bgneal@45 14838 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 14839 en = en.nodeName == "BODY" ? en.firstChild : en;
bgneal@45 14840
bgneal@45 14841 // Get start and end blocks
bgneal@45 14842 sb = t.getParentBlock(sn);
bgneal@45 14843 eb = t.getParentBlock(en);
bgneal@45 14844 bn = sb ? sb.nodeName : se.element; // Get block name to create
bgneal@45 14845
bgneal@45 14846 // Break container blocks on enter in empty block element (experimental feature)
bgneal@45 14847 if (ed.settings.end_container_on_empty_block) {
bgneal@45 14848 containerBlock = dom.getParent(sb, 'hgroup,blockquote,section,article');
bgneal@45 14849 if (containerBlock && (dom.isEmpty(sb) || (sb.firstChild === sb.lastChild && (!sb.firstChild || sb.firstChild.nodeName == 'BR')))) {
bgneal@45 14850 dom.split(containerBlock, sb);
bgneal@45 14851 selection.select(sb, true);
bgneal@45 14852 selection.collapse(true);
bgneal@45 14853 return;
bgneal@45 14854 }
bgneal@45 14855 }
bgneal@45 14856
bgneal@45 14857 // Return inside list use default browser behavior
bgneal@45 14858 if (n = t.dom.getParent(sb, 'li')) {
bgneal@45 14859 if (n.nodeName == 'LI')
bgneal@45 14860 return splitList(selection, t.dom, n);
bgneal@45 14861
bgneal@45 14862 return TRUE;
bgneal@45 14863 }
bgneal@45 14864
bgneal@45 14865 // If caption or absolute layers then always generate new blocks within
bgneal@45 14866 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 14867 bn = se.element;
bgneal@45 14868 sb = null;
bgneal@45 14869 }
bgneal@45 14870
bgneal@45 14871 // If caption or absolute layers then always generate new blocks within
bgneal@45 14872 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 14873 bn = se.element;
bgneal@45 14874 eb = null;
bgneal@45 14875 }
bgneal@45 14876
bgneal@45 14877 // Use P instead
bgneal@45 14878 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bgneal@45 14879 bn = se.element;
bgneal@45 14880 sb = eb = null;
bgneal@45 14881 }
bgneal@45 14882
bgneal@45 14883 // Setup new before and after blocks
bgneal@45 14884 bef = (sb && sb.nodeName == bn) ? dom.clone(sb, false) : dom.create(bn);
bgneal@45 14885 aft = (eb && eb.nodeName == bn) ? dom.clone(eb, false) : dom.create(bn);
bgneal@45 14886
bgneal@45 14887 // Remove id from after clone
bgneal@45 14888 aft.removeAttribute('id');
bgneal@45 14889
bgneal@45 14890 // Is header and cursor is at the end, then force paragraph under
bgneal@45 14891 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb, dom)) {
bgneal@45 14892 aft = dom.create(se.element);
bgneal@45 14893
bgneal@45 14894 // Use header name as copy if we are in a hgroup
bgneal@45 14895 if (t.dom.getParent(sb, 'hgroup')) {
bgneal@45 14896 aft = dom.create(bn);
bgneal@45 14897 }
bgneal@45 14898 }
bgneal@45 14899
bgneal@45 14900 // Find start chop node
bgneal@45 14901 n = sc = sn;
bgneal@45 14902 do {
bgneal@45 14903 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 14904 break;
bgneal@45 14905
bgneal@45 14906 sc = n;
bgneal@45 14907 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
bgneal@45 14908
bgneal@45 14909 // Find end chop node
bgneal@45 14910 n = ec = en;
bgneal@45 14911 do {
bgneal@45 14912 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 14913 break;
bgneal@45 14914
bgneal@45 14915 ec = n;
bgneal@45 14916 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
bgneal@45 14917
bgneal@45 14918 // Place first chop part into before block element
bgneal@45 14919 if (sc.nodeName == bn)
bgneal@45 14920 rb.setStart(sc, 0);
bgneal@45 14921 else
bgneal@45 14922 rb.setStartBefore(sc);
bgneal@45 14923
bgneal@45 14924 rb.setEnd(sn, so);
bgneal@45 14925 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 14926
bgneal@45 14927 // Place secnd chop part within new block element
bgneal@45 14928 try {
bgneal@45 14929 ra.setEndAfter(ec);
bgneal@45 14930 } catch(ex) {
bgneal@45 14931 //console.debug(s.focusNode, s.focusOffset);
bgneal@45 14932 }
bgneal@45 14933
bgneal@45 14934 ra.setStart(en, eo);
bgneal@45 14935 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 14936
bgneal@45 14937 // Create range around everything
bgneal@45 14938 r = dom.createRng();
bgneal@45 14939 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
bgneal@45 14940 r.setStartBefore(sc.parentNode);
bgneal@45 14941 } else {
bgneal@45 14942 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
bgneal@45 14943 r.setStartBefore(rb.startContainer);
bgneal@45 14944 else
bgneal@45 14945 r.setStart(rb.startContainer, rb.startOffset);
bgneal@45 14946 }
bgneal@45 14947
bgneal@45 14948 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
bgneal@45 14949 r.setEndAfter(ec.parentNode);
bgneal@45 14950 else
bgneal@45 14951 r.setEnd(ra.endContainer, ra.endOffset);
bgneal@45 14952
bgneal@45 14953 if (!isIE || sb != eb || (sb && !dom.isEmpty(sb))) {
bgneal@45 14954 r.deleteContents();
bgneal@45 14955 }
bgneal@45 14956
bgneal@45 14957 // Remove range end point if it's empty
bgneal@45 14958 n = r.startContainer.childNodes[r.startOffset];
bgneal@45 14959 if (n && !n.hasChildNodes()) {
bgneal@45 14960 dom.remove(n);
bgneal@45 14961 }
bgneal@45 14962
bgneal@45 14963 // Never wrap blocks in blocks
bgneal@45 14964 if (bef.firstChild && bef.firstChild.nodeName == bn)
bgneal@45 14965 bef.innerHTML = bef.firstChild.innerHTML;
bgneal@45 14966
bgneal@45 14967 if (aft.firstChild && aft.firstChild.nodeName == bn)
bgneal@45 14968 aft.innerHTML = aft.firstChild.innerHTML;
bgneal@45 14969
bgneal@45 14970 function appendStyles(e, en) {
bgneal@45 14971 var nl = [], nn, n, i, padd;
bgneal@45 14972
bgneal@45 14973 // Use BR on W3C browsers and empty string on IE
bgneal@45 14974 padd = isIE ? '' : '<br />';
bgneal@45 14975
bgneal@45 14976 // Needs to be removed using removeChild since innerHTML adds &nbsp; on IE
bgneal@45 14977 while (e.firstChild) {
bgneal@45 14978 e.removeChild(e.firstChild);
bgneal@45 14979 }
bgneal@45 14980
bgneal@45 14981 // Make clones of style elements
bgneal@45 14982 if (se.keep_styles) {
bgneal@45 14983 n = en;
bgneal@45 14984 do {
bgneal@45 14985 // We only want style specific elements
bgneal@45 14986 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
bgneal@45 14987 nn = n.cloneNode(FALSE);
bgneal@45 14988 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
bgneal@45 14989 nl.push(nn);
bgneal@45 14990 }
bgneal@45 14991 } while (n = n.parentNode);
bgneal@45 14992 }
bgneal@45 14993
bgneal@45 14994 // Append style elements to aft
bgneal@45 14995 if (nl.length > 0) {
bgneal@45 14996 for (i = nl.length - 1, nn = e; i >= 0; i--)
bgneal@45 14997 nn = nn.appendChild(nl[i]);
bgneal@45 14998
bgneal@45 14999 // Padd most inner style element
bgneal@45 15000 nl[0].innerHTML = padd;
bgneal@45 15001 return nl[0]; // Move caret to most inner element
bgneal@45 15002 } else
bgneal@45 15003 e.innerHTML = padd;
bgneal@45 15004
bgneal@45 15005 return e;
bgneal@45 15006 };
bgneal@45 15007
bgneal@45 15008 // IE gets messed up if the elements has empty text nodes in them this might
bgneal@45 15009 // happen when the range spits at the end/beginning of a text node.
bgneal@45 15010 // @todo: The acutal bug should be fixed in the Range.js file this is a hot patch
bgneal@45 15011 if (isIE) {
bgneal@45 15012 clean(bef);
bgneal@45 15013 clean(aft);
bgneal@45 15014 }
bgneal@45 15015
bgneal@45 15016 // Padd empty blocks
bgneal@45 15017 if (dom.isEmpty(bef)) {
bgneal@45 15018 beforeCaretNode = appendStyles(bef, sn);
bgneal@45 15019 }
bgneal@45 15020
bgneal@45 15021 // Fill empty afterblook with current style
bgneal@45 15022 if (dom.isEmpty(aft))
bgneal@45 15023 afterCaretNode = appendStyles(aft, en);
bgneal@45 15024
bgneal@45 15025 // Opera needs this one backwards for older versions
bgneal@45 15026 if (isOpera && parseFloat(opera.version()) < 9.5) {
bgneal@45 15027 r.insertNode(bef);
bgneal@45 15028 r.insertNode(aft);
bgneal@45 15029 } else {
bgneal@45 15030 r.insertNode(aft);
bgneal@45 15031 r.insertNode(bef);
bgneal@45 15032 }
bgneal@45 15033
bgneal@45 15034 // IE doesn't render empty block elements unless you poke it with a selection
bgneal@45 15035 // So we need to detect old IE and then move the caret into that block to "render it"
bgneal@45 15036 if (selection.tridentSel && beforeCaretNode) {
bgneal@45 15037 moveToCaretPosition(beforeCaretNode, false);
bgneal@45 15038 }
bgneal@45 15039
bgneal@45 15040 moveToCaretPosition(afterCaretNode || aft);
bgneal@45 15041 ed.undoManager.add();
bgneal@45 15042
bgneal@45 15043 return FALSE;
bgneal@45 15044 },
bgneal@45 15045
bgneal@45 15046 backspaceDelete : function(e, bs) {
bgneal@45 15047 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, walker;
bgneal@45 15048
bgneal@45 15049 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
bgneal@45 15050 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
bgneal@45 15051 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
bgneal@45 15052
bgneal@45 15053 // Walk the dom backwards until we find a text node
bgneal@45 15054 for (n = sc.lastChild; n; n = walker.prev()) {
bgneal@45 15055 if (n.nodeType == 3) {
bgneal@45 15056 r.setStart(n, n.nodeValue.length);
bgneal@45 15057 r.collapse(true);
bgneal@45 15058 se.setRng(r);
bgneal@45 15059 return;
bgneal@45 15060 }
bgneal@45 15061 }
bgneal@45 15062 }
bgneal@45 15063
bgneal@45 15064 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
bgneal@45 15065 // This workaround removes the element by hand and moves the caret to the previous element
bgneal@45 15066 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
bgneal@45 15067 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
bgneal@45 15068 // Find previous block element
bgneal@45 15069 n = sc;
bgneal@45 15070 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
bgneal@45 15071
bgneal@45 15072 if (n) {
bgneal@45 15073 if (sc != b.firstChild) {
bgneal@45 15074 // Find last text node
bgneal@45 15075 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
bgneal@45 15076 while (tn = w.nextNode())
bgneal@45 15077 n = tn;
bgneal@45 15078
bgneal@45 15079 // Place caret at the end of last text node
bgneal@45 15080 r = ed.getDoc().createRange();
bgneal@45 15081 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 15082 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 15083 se.setRng(r);
bgneal@45 15084
bgneal@45 15085 // Remove the target container
bgneal@45 15086 ed.dom.remove(sc);
bgneal@45 15087 }
bgneal@45 15088
bgneal@45 15089 return Event.cancel(e);
bgneal@45 15090 }
bgneal@45 15091 }
bgneal@45 15092 }
bgneal@45 15093 }
bgneal@45 15094 });
bgneal@45 15095 })(tinymce);
bgneal@45 15096
bgneal@45 15097 (function(tinymce) {
bgneal@45 15098 // Shorten names
bgneal@45 15099 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
bgneal@45 15100
bgneal@45 15101 tinymce.create('tinymce.ControlManager', {
bgneal@45 15102 ControlManager : function(ed, s) {
bgneal@45 15103 var t = this, i;
bgneal@45 15104
bgneal@45 15105 s = s || {};
bgneal@45 15106 t.editor = ed;
bgneal@45 15107 t.controls = {};
bgneal@45 15108 t.onAdd = new tinymce.util.Dispatcher(t);
bgneal@45 15109 t.onPostRender = new tinymce.util.Dispatcher(t);
bgneal@45 15110 t.prefix = s.prefix || ed.id + '_';
bgneal@45 15111 t._cls = {};
bgneal@45 15112
bgneal@45 15113 t.onPostRender.add(function() {
bgneal@45 15114 each(t.controls, function(c) {
bgneal@45 15115 c.postRender();
bgneal@45 15116 });
bgneal@45 15117 });
bgneal@45 15118 },
bgneal@45 15119
bgneal@45 15120 get : function(id) {
bgneal@45 15121 return this.controls[this.prefix + id] || this.controls[id];
bgneal@45 15122 },
bgneal@45 15123
bgneal@45 15124 setActive : function(id, s) {
bgneal@45 15125 var c = null;
bgneal@45 15126
bgneal@45 15127 if (c = this.get(id))
bgneal@45 15128 c.setActive(s);
bgneal@45 15129
bgneal@45 15130 return c;
bgneal@45 15131 },
bgneal@45 15132
bgneal@45 15133 setDisabled : function(id, s) {
bgneal@45 15134 var c = null;
bgneal@45 15135
bgneal@45 15136 if (c = this.get(id))
bgneal@45 15137 c.setDisabled(s);
bgneal@45 15138
bgneal@45 15139 return c;
bgneal@45 15140 },
bgneal@45 15141
bgneal@45 15142 add : function(c) {
bgneal@45 15143 var t = this;
bgneal@45 15144
bgneal@45 15145 if (c) {
bgneal@45 15146 t.controls[c.id] = c;
bgneal@45 15147 t.onAdd.dispatch(c, t);
bgneal@45 15148 }
bgneal@45 15149
bgneal@45 15150 return c;
bgneal@45 15151 },
bgneal@45 15152
bgneal@45 15153 createControl : function(n) {
bgneal@45 15154 var c, t = this, ed = t.editor;
bgneal@45 15155
bgneal@45 15156 each(ed.plugins, function(p) {
bgneal@45 15157 if (p.createControl) {
bgneal@45 15158 c = p.createControl(n, t);
bgneal@45 15159
bgneal@45 15160 if (c)
bgneal@45 15161 return false;
bgneal@45 15162 }
bgneal@45 15163 });
bgneal@45 15164
bgneal@45 15165 switch (n) {
bgneal@45 15166 case "|":
bgneal@45 15167 case "separator":
bgneal@45 15168 return t.createSeparator();
bgneal@45 15169 }
bgneal@45 15170
bgneal@45 15171 if (!c && ed.buttons && (c = ed.buttons[n]))
bgneal@45 15172 return t.createButton(n, c);
bgneal@45 15173
bgneal@45 15174 return t.add(c);
bgneal@45 15175 },
bgneal@45 15176
bgneal@45 15177 createDropMenu : function(id, s, cc) {
bgneal@45 15178 var t = this, ed = t.editor, c, bm, v, cls;
bgneal@45 15179
bgneal@45 15180 s = extend({
bgneal@45 15181 'class' : 'mceDropDown',
bgneal@45 15182 constrain : ed.settings.constrain_menus
bgneal@45 15183 }, s);
bgneal@45 15184
bgneal@45 15185 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
bgneal@45 15186 if (v = ed.getParam('skin_variant'))
bgneal@45 15187 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
bgneal@45 15188
bgneal@45 15189 id = t.prefix + id;
bgneal@45 15190 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
bgneal@45 15191 c = t.controls[id] = new cls(id, s);
bgneal@45 15192 c.onAddItem.add(function(c, o) {
bgneal@45 15193 var s = o.settings;
bgneal@45 15194
bgneal@45 15195 s.title = ed.getLang(s.title, s.title);
bgneal@45 15196
bgneal@45 15197 if (!s.onclick) {
bgneal@45 15198 s.onclick = function(v) {
bgneal@45 15199 if (s.cmd)
bgneal@45 15200 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 15201 };
bgneal@45 15202 }
bgneal@45 15203 });
bgneal@45 15204
bgneal@45 15205 ed.onRemove.add(function() {
bgneal@45 15206 c.destroy();
bgneal@45 15207 });
bgneal@45 15208
bgneal@45 15209 // Fix for bug #1897785, #1898007
bgneal@45 15210 if (tinymce.isIE) {
bgneal@45 15211 c.onShowMenu.add(function() {
bgneal@45 15212 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@45 15213 ed.focus();
bgneal@45 15214
bgneal@45 15215 bm = ed.selection.getBookmark(1);
bgneal@45 15216 });
bgneal@45 15217
bgneal@45 15218 c.onHideMenu.add(function() {
bgneal@45 15219 if (bm) {
bgneal@45 15220 ed.selection.moveToBookmark(bm);
bgneal@45 15221 bm = 0;
bgneal@45 15222 }
bgneal@45 15223 });
bgneal@45 15224 }
bgneal@45 15225
bgneal@45 15226 return t.add(c);
bgneal@45 15227 },
bgneal@45 15228
bgneal@45 15229 createListBox : function(id, s, cc) {
bgneal@45 15230 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 15231
bgneal@45 15232 if (t.get(id))
bgneal@45 15233 return null;
bgneal@45 15234
bgneal@45 15235 s.title = ed.translate(s.title);
bgneal@45 15236 s.scope = s.scope || ed;
bgneal@45 15237
bgneal@45 15238 if (!s.onselect) {
bgneal@45 15239 s.onselect = function(v) {
bgneal@45 15240 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 15241 };
bgneal@45 15242 }
bgneal@45 15243
bgneal@45 15244 s = extend({
bgneal@45 15245 title : s.title,
bgneal@45 15246 'class' : 'mce_' + id,
bgneal@45 15247 scope : s.scope,
bgneal@45 15248 control_manager : t
bgneal@45 15249 }, s);
bgneal@45 15250
bgneal@45 15251 id = t.prefix + id;
bgneal@45 15252
bgneal@45 15253
bgneal@45 15254 function useNativeListForAccessibility(ed) {
bgneal@45 15255 return ed.settings.use_accessible_selects && !tinymce.isGecko
bgneal@45 15256 }
bgneal@45 15257
bgneal@45 15258 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
bgneal@45 15259 c = new tinymce.ui.NativeListBox(id, s);
bgneal@45 15260 else {
bgneal@45 15261 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
bgneal@45 15262 c = new cls(id, s, ed);
bgneal@45 15263 }
bgneal@45 15264
bgneal@45 15265 t.controls[id] = c;
bgneal@45 15266
bgneal@45 15267 // Fix focus problem in Safari
bgneal@45 15268 if (tinymce.isWebKit) {
bgneal@45 15269 c.onPostRender.add(function(c, n) {
bgneal@45 15270 // Store bookmark on mousedown
bgneal@45 15271 Event.add(n, 'mousedown', function() {
bgneal@45 15272 ed.bookmark = ed.selection.getBookmark(1);
bgneal@45 15273 });
bgneal@45 15274
bgneal@45 15275 // Restore on focus, since it might be lost
bgneal@45 15276 Event.add(n, 'focus', function() {
bgneal@45 15277 ed.selection.moveToBookmark(ed.bookmark);
bgneal@45 15278 ed.bookmark = null;
bgneal@45 15279 });
bgneal@45 15280 });
bgneal@45 15281 }
bgneal@45 15282
bgneal@45 15283 if (c.hideMenu)
bgneal@45 15284 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 15285
bgneal@45 15286 return t.add(c);
bgneal@45 15287 },
bgneal@45 15288
bgneal@45 15289 createButton : function(id, s, cc) {
bgneal@45 15290 var t = this, ed = t.editor, o, c, cls;
bgneal@45 15291
bgneal@45 15292 if (t.get(id))
bgneal@45 15293 return null;
bgneal@45 15294
bgneal@45 15295 s.title = ed.translate(s.title);
bgneal@45 15296 s.label = ed.translate(s.label);
bgneal@45 15297 s.scope = s.scope || ed;
bgneal@45 15298
bgneal@45 15299 if (!s.onclick && !s.menu_button) {
bgneal@45 15300 s.onclick = function() {
bgneal@45 15301 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 15302 };
bgneal@45 15303 }
bgneal@45 15304
bgneal@45 15305 s = extend({
bgneal@45 15306 title : s.title,
bgneal@45 15307 'class' : 'mce_' + id,
bgneal@45 15308 unavailable_prefix : ed.getLang('unavailable', ''),
bgneal@45 15309 scope : s.scope,
bgneal@45 15310 control_manager : t
bgneal@45 15311 }, s);
bgneal@45 15312
bgneal@45 15313 id = t.prefix + id;
bgneal@45 15314
bgneal@45 15315 if (s.menu_button) {
bgneal@45 15316 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
bgneal@45 15317 c = new cls(id, s, ed);
bgneal@45 15318 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 15319 } else {
bgneal@45 15320 cls = t._cls.button || tinymce.ui.Button;
bgneal@45 15321 c = new cls(id, s, ed);
bgneal@45 15322 }
bgneal@45 15323
bgneal@45 15324 return t.add(c);
bgneal@45 15325 },
bgneal@45 15326
bgneal@45 15327 createMenuButton : function(id, s, cc) {
bgneal@45 15328 s = s || {};
bgneal@45 15329 s.menu_button = 1;
bgneal@45 15330
bgneal@45 15331 return this.createButton(id, s, cc);
bgneal@45 15332 },
bgneal@45 15333
bgneal@45 15334 createSplitButton : function(id, s, cc) {
bgneal@45 15335 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 15336
bgneal@45 15337 if (t.get(id))
bgneal@45 15338 return null;
bgneal@45 15339
bgneal@45 15340 s.title = ed.translate(s.title);
bgneal@45 15341 s.scope = s.scope || ed;
bgneal@45 15342
bgneal@45 15343 if (!s.onclick) {
bgneal@45 15344 s.onclick = function(v) {
bgneal@45 15345 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 15346 };
bgneal@45 15347 }
bgneal@45 15348
bgneal@45 15349 if (!s.onselect) {
bgneal@45 15350 s.onselect = function(v) {
bgneal@45 15351 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 15352 };
bgneal@45 15353 }
bgneal@45 15354
bgneal@45 15355 s = extend({
bgneal@45 15356 title : s.title,
bgneal@45 15357 'class' : 'mce_' + id,
bgneal@45 15358 scope : s.scope,
bgneal@45 15359 control_manager : t
bgneal@45 15360 }, s);
bgneal@45 15361
bgneal@45 15362 id = t.prefix + id;
bgneal@45 15363 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
bgneal@45 15364 c = t.add(new cls(id, s, ed));
bgneal@45 15365 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 15366
bgneal@45 15367 return c;
bgneal@45 15368 },
bgneal@45 15369
bgneal@45 15370 createColorSplitButton : function(id, s, cc) {
bgneal@45 15371 var t = this, ed = t.editor, cmd, c, cls, bm;
bgneal@45 15372
bgneal@45 15373 if (t.get(id))
bgneal@45 15374 return null;
bgneal@45 15375
bgneal@45 15376 s.title = ed.translate(s.title);
bgneal@45 15377 s.scope = s.scope || ed;
bgneal@45 15378
bgneal@45 15379 if (!s.onclick) {
bgneal@45 15380 s.onclick = function(v) {
bgneal@45 15381 if (tinymce.isIE)
bgneal@45 15382 bm = ed.selection.getBookmark(1);
bgneal@45 15383
bgneal@45 15384 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 15385 };
bgneal@45 15386 }
bgneal@45 15387
bgneal@45 15388 if (!s.onselect) {
bgneal@45 15389 s.onselect = function(v) {
bgneal@45 15390 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 15391 };
bgneal@45 15392 }
bgneal@45 15393
bgneal@45 15394 s = extend({
bgneal@45 15395 title : s.title,
bgneal@45 15396 'class' : 'mce_' + id,
bgneal@45 15397 'menu_class' : ed.getParam('skin') + 'Skin',
bgneal@45 15398 scope : s.scope,
bgneal@45 15399 more_colors_title : ed.getLang('more_colors')
bgneal@45 15400 }, s);
bgneal@45 15401
bgneal@45 15402 id = t.prefix + id;
bgneal@45 15403 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
bgneal@45 15404 c = new cls(id, s, ed);
bgneal@45 15405 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 15406
bgneal@45 15407 // Remove the menu element when the editor is removed
bgneal@45 15408 ed.onRemove.add(function() {
bgneal@45 15409 c.destroy();
bgneal@45 15410 });
bgneal@45 15411
bgneal@45 15412 // Fix for bug #1897785, #1898007
bgneal@45 15413 if (tinymce.isIE) {
bgneal@45 15414 c.onShowMenu.add(function() {
bgneal@45 15415 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@45 15416 ed.focus();
bgneal@45 15417 bm = ed.selection.getBookmark(1);
bgneal@45 15418 });
bgneal@45 15419
bgneal@45 15420 c.onHideMenu.add(function() {
bgneal@45 15421 if (bm) {
bgneal@45 15422 ed.selection.moveToBookmark(bm);
bgneal@45 15423 bm = 0;
bgneal@45 15424 }
bgneal@45 15425 });
bgneal@45 15426 }
bgneal@45 15427
bgneal@45 15428 return t.add(c);
bgneal@45 15429 },
bgneal@45 15430
bgneal@45 15431 createToolbar : function(id, s, cc) {
bgneal@45 15432 var c, t = this, cls;
bgneal@45 15433
bgneal@45 15434 id = t.prefix + id;
bgneal@45 15435 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
bgneal@45 15436 c = new cls(id, s, t.editor);
bgneal@45 15437
bgneal@45 15438 if (t.get(id))
bgneal@45 15439 return null;
bgneal@45 15440
bgneal@45 15441 return t.add(c);
bgneal@45 15442 },
bgneal@45 15443
bgneal@45 15444 createToolbarGroup : function(id, s, cc) {
bgneal@45 15445 var c, t = this, cls;
bgneal@45 15446 id = t.prefix + id;
bgneal@45 15447 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
bgneal@45 15448 c = new cls(id, s, t.editor);
bgneal@45 15449
bgneal@45 15450 if (t.get(id))
bgneal@45 15451 return null;
bgneal@45 15452
bgneal@45 15453 return t.add(c);
bgneal@45 15454 },
bgneal@45 15455
bgneal@45 15456 createSeparator : function(cc) {
bgneal@45 15457 var cls = cc || this._cls.separator || tinymce.ui.Separator;
bgneal@45 15458
bgneal@45 15459 return new cls();
bgneal@45 15460 },
bgneal@45 15461
bgneal@45 15462 setControlType : function(n, c) {
bgneal@45 15463 return this._cls[n.toLowerCase()] = c;
bgneal@45 15464 },
bgneal@45 15465
bgneal@45 15466 destroy : function() {
bgneal@45 15467 each(this.controls, function(c) {
bgneal@45 15468 c.destroy();
bgneal@45 15469 });
bgneal@45 15470
bgneal@45 15471 this.controls = null;
bgneal@45 15472 }
bgneal@45 15473 });
bgneal@45 15474 })(tinymce);
bgneal@45 15475
bgneal@45 15476 (function(tinymce) {
bgneal@45 15477 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
bgneal@45 15478
bgneal@45 15479 tinymce.create('tinymce.WindowManager', {
bgneal@45 15480 WindowManager : function(ed) {
bgneal@45 15481 var t = this;
bgneal@45 15482
bgneal@45 15483 t.editor = ed;
bgneal@45 15484 t.onOpen = new Dispatcher(t);
bgneal@45 15485 t.onClose = new Dispatcher(t);
bgneal@45 15486 t.params = {};
bgneal@45 15487 t.features = {};
bgneal@45 15488 },
bgneal@45 15489
bgneal@45 15490 open : function(s, p) {
bgneal@45 15491 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
bgneal@45 15492
bgneal@45 15493 // Default some options
bgneal@45 15494 s = s || {};
bgneal@45 15495 p = p || {};
bgneal@45 15496 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
bgneal@45 15497 sh = isOpera ? vp.h : screen.height;
bgneal@45 15498 s.name = s.name || 'mc_' + new Date().getTime();
bgneal@45 15499 s.width = parseInt(s.width || 320);
bgneal@45 15500 s.height = parseInt(s.height || 240);
bgneal@45 15501 s.resizable = true;
bgneal@45 15502 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
bgneal@45 15503 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
bgneal@45 15504 p.inline = false;
bgneal@45 15505 p.mce_width = s.width;
bgneal@45 15506 p.mce_height = s.height;
bgneal@45 15507 p.mce_auto_focus = s.auto_focus;
bgneal@45 15508
bgneal@45 15509 if (mo) {
bgneal@45 15510 if (isIE) {
bgneal@45 15511 s.center = true;
bgneal@45 15512 s.help = false;
bgneal@45 15513 s.dialogWidth = s.width + 'px';
bgneal@45 15514 s.dialogHeight = s.height + 'px';
bgneal@45 15515 s.scroll = s.scrollbars || false;
bgneal@45 15516 }
bgneal@45 15517 }
bgneal@45 15518
bgneal@45 15519 // Build features string
bgneal@45 15520 each(s, function(v, k) {
bgneal@45 15521 if (tinymce.is(v, 'boolean'))
bgneal@45 15522 v = v ? 'yes' : 'no';
bgneal@45 15523
bgneal@45 15524 if (!/^(name|url)$/.test(k)) {
bgneal@45 15525 if (isIE && mo)
bgneal@45 15526 f += (f ? ';' : '') + k + ':' + v;
bgneal@45 15527 else
bgneal@45 15528 f += (f ? ',' : '') + k + '=' + v;
bgneal@45 15529 }
bgneal@45 15530 });
bgneal@45 15531
bgneal@45 15532 t.features = s;
bgneal@45 15533 t.params = p;
bgneal@45 15534 t.onOpen.dispatch(t, s, p);
bgneal@45 15535
bgneal@45 15536 u = s.url || s.file;
bgneal@45 15537 u = tinymce._addVer(u);
bgneal@45 15538
bgneal@45 15539 try {
bgneal@45 15540 if (isIE && mo) {
bgneal@45 15541 w = 1;
bgneal@45 15542 window.showModalDialog(u, window, f);
bgneal@45 15543 } else
bgneal@45 15544 w = window.open(u, s.name, f);
bgneal@45 15545 } catch (ex) {
bgneal@45 15546 // Ignore
bgneal@45 15547 }
bgneal@45 15548
bgneal@45 15549 if (!w)
bgneal@45 15550 alert(t.editor.getLang('popup_blocked'));
bgneal@45 15551 },
bgneal@45 15552
bgneal@45 15553 close : function(w) {
bgneal@45 15554 w.close();
bgneal@45 15555 this.onClose.dispatch(this);
bgneal@45 15556 },
bgneal@45 15557
bgneal@45 15558 createInstance : function(cl, a, b, c, d, e) {
bgneal@45 15559 var f = tinymce.resolve(cl);
bgneal@45 15560
bgneal@45 15561 return new f(a, b, c, d, e);
bgneal@45 15562 },
bgneal@45 15563
bgneal@45 15564 confirm : function(t, cb, s, w) {
bgneal@45 15565 w = w || window;
bgneal@45 15566
bgneal@45 15567 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
bgneal@45 15568 },
bgneal@45 15569
bgneal@45 15570 alert : function(tx, cb, s, w) {
bgneal@45 15571 var t = this;
bgneal@45 15572
bgneal@45 15573 w = w || window;
bgneal@45 15574 w.alert(t._decode(t.editor.getLang(tx, tx)));
bgneal@45 15575
bgneal@45 15576 if (cb)
bgneal@45 15577 cb.call(s || t);
bgneal@45 15578 },
bgneal@45 15579
bgneal@45 15580 resizeBy : function(dw, dh, win) {
bgneal@45 15581 win.resizeBy(dw, dh);
bgneal@45 15582 },
bgneal@45 15583
bgneal@45 15584 // Internal functions
bgneal@45 15585
bgneal@45 15586 _decode : function(s) {
bgneal@45 15587 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
bgneal@45 15588 }
bgneal@45 15589 });
bgneal@45 15590 }(tinymce));
bgneal@45 15591 (function(tinymce) {
bgneal@45 15592 tinymce.Formatter = function(ed) {
bgneal@45 15593 var formats = {},
bgneal@45 15594 each = tinymce.each,
bgneal@45 15595 dom = ed.dom,
bgneal@45 15596 selection = ed.selection,
bgneal@45 15597 TreeWalker = tinymce.dom.TreeWalker,
bgneal@45 15598 rangeUtils = new tinymce.dom.RangeUtils(dom),
bgneal@45 15599 isValid = ed.schema.isValidChild,
bgneal@45 15600 isBlock = dom.isBlock,
bgneal@45 15601 forcedRootBlock = ed.settings.forced_root_block,
bgneal@45 15602 nodeIndex = dom.nodeIndex,
bgneal@45 15603 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
bgneal@45 15604 MCE_ATTR_RE = /^(src|href|style)$/,
bgneal@45 15605 FALSE = false,
bgneal@45 15606 TRUE = true,
bgneal@45 15607 undefined;
bgneal@45 15608
bgneal@45 15609 // Returns the content editable state of a node
bgneal@45 15610 function getContentEditable(node) {
bgneal@45 15611 var contentEditable = node.getAttribute("data-mce-contenteditable");
bgneal@45 15612
bgneal@45 15613 // Check for fake content editable
bgneal@45 15614 if (contentEditable && contentEditable !== "inherit") {
bgneal@45 15615 return contentEditable;
bgneal@45 15616 }
bgneal@45 15617
bgneal@45 15618 // Check for real content editable
bgneal@45 15619 return node.contentEditable !== "inherit" ? node.contentEditable : null;
bgneal@45 15620 };
bgneal@45 15621
bgneal@45 15622 function isArray(obj) {
bgneal@45 15623 return obj instanceof Array;
bgneal@45 15624 };
bgneal@45 15625
bgneal@45 15626 function getParents(node, selector) {
bgneal@45 15627 return dom.getParents(node, selector, dom.getRoot());
bgneal@45 15628 };
bgneal@45 15629
bgneal@45 15630 function isCaretNode(node) {
bgneal@45 15631 return node.nodeType === 1 && node.id === '_mce_caret';
bgneal@45 15632 };
bgneal@45 15633
bgneal@45 15634 // Public functions
bgneal@45 15635
bgneal@45 15636 function get(name) {
bgneal@45 15637 return name ? formats[name] : formats;
bgneal@45 15638 };
bgneal@45 15639
bgneal@45 15640 function register(name, format) {
bgneal@45 15641 if (name) {
bgneal@45 15642 if (typeof(name) !== 'string') {
bgneal@45 15643 each(name, function(format, name) {
bgneal@45 15644 register(name, format);
bgneal@45 15645 });
bgneal@45 15646 } else {
bgneal@45 15647 // Force format into array and add it to internal collection
bgneal@45 15648 format = format.length ? format : [format];
bgneal@45 15649
bgneal@45 15650 each(format, function(format) {
bgneal@45 15651 // Set deep to false by default on selector formats this to avoid removing
bgneal@45 15652 // alignment on images inside paragraphs when alignment is changed on paragraphs
bgneal@45 15653 if (format.deep === undefined)
bgneal@45 15654 format.deep = !format.selector;
bgneal@45 15655
bgneal@45 15656 // Default to true
bgneal@45 15657 if (format.split === undefined)
bgneal@45 15658 format.split = !format.selector || format.inline;
bgneal@45 15659
bgneal@45 15660 // Default to true
bgneal@45 15661 if (format.remove === undefined && format.selector && !format.inline)
bgneal@45 15662 format.remove = 'none';
bgneal@45 15663
bgneal@45 15664 // Mark format as a mixed format inline + block level
bgneal@45 15665 if (format.selector && format.inline) {
bgneal@45 15666 format.mixed = true;
bgneal@45 15667 format.block_expand = true;
bgneal@45 15668 }
bgneal@45 15669
bgneal@45 15670 // Split classes if needed
bgneal@45 15671 if (typeof(format.classes) === 'string')
bgneal@45 15672 format.classes = format.classes.split(/\s+/);
bgneal@45 15673 });
bgneal@45 15674
bgneal@45 15675 formats[name] = format;
bgneal@45 15676 }
bgneal@45 15677 }
bgneal@45 15678 };
bgneal@45 15679
bgneal@45 15680 var getTextDecoration = function(node) {
bgneal@45 15681 var decoration;
bgneal@45 15682
bgneal@45 15683 ed.dom.getParent(node, function(n) {
bgneal@45 15684 decoration = ed.dom.getStyle(n, 'text-decoration');
bgneal@45 15685 return decoration && decoration !== 'none';
bgneal@45 15686 });
bgneal@45 15687
bgneal@45 15688 return decoration;
bgneal@45 15689 };
bgneal@45 15690
bgneal@45 15691 var processUnderlineAndColor = function(node) {
bgneal@45 15692 var textDecoration;
bgneal@45 15693 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
bgneal@45 15694 textDecoration = getTextDecoration(node.parentNode);
bgneal@45 15695 if (ed.dom.getStyle(node, 'color') && textDecoration) {
bgneal@45 15696 ed.dom.setStyle(node, 'text-decoration', textDecoration);
bgneal@45 15697 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
bgneal@45 15698 ed.dom.setStyle(node, 'text-decoration', null);
bgneal@45 15699 }
bgneal@45 15700 }
bgneal@45 15701 };
bgneal@45 15702
bgneal@45 15703 function apply(name, vars, node) {
bgneal@45 15704 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
bgneal@45 15705
bgneal@45 15706 function setElementFormat(elm, fmt) {
bgneal@45 15707 fmt = fmt || format;
bgneal@45 15708
bgneal@45 15709 if (elm) {
bgneal@45 15710 if (fmt.onformat) {
bgneal@45 15711 fmt.onformat(elm, fmt, vars, node);
bgneal@45 15712 }
bgneal@45 15713
bgneal@45 15714 each(fmt.styles, function(value, name) {
bgneal@45 15715 dom.setStyle(elm, name, replaceVars(value, vars));
bgneal@45 15716 });
bgneal@45 15717
bgneal@45 15718 each(fmt.attributes, function(value, name) {
bgneal@45 15719 dom.setAttrib(elm, name, replaceVars(value, vars));
bgneal@45 15720 });
bgneal@45 15721
bgneal@45 15722 each(fmt.classes, function(value) {
bgneal@45 15723 value = replaceVars(value, vars);
bgneal@45 15724
bgneal@45 15725 if (!dom.hasClass(elm, value))
bgneal@45 15726 dom.addClass(elm, value);
bgneal@45 15727 });
bgneal@45 15728 }
bgneal@45 15729 };
bgneal@45 15730 function adjustSelectionToVisibleSelection() {
bgneal@45 15731 function findSelectionEnd(start, end) {
bgneal@45 15732 var walker = new TreeWalker(end);
bgneal@45 15733 for (node = walker.current(); node; node = walker.prev()) {
bgneal@45 15734 if (node.childNodes.length > 1 || node == start) {
bgneal@45 15735 return node;
bgneal@45 15736 }
bgneal@45 15737 }
bgneal@45 15738 };
bgneal@45 15739
bgneal@45 15740 // Adjust selection so that a end container with a end offset of zero is not included in the selection
bgneal@45 15741 // as this isn't visible to the user.
bgneal@45 15742 var rng = ed.selection.getRng();
bgneal@45 15743 var start = rng.startContainer;
bgneal@45 15744 var end = rng.endContainer;
bgneal@45 15745
bgneal@45 15746 if (start != end && rng.endOffset == 0) {
bgneal@45 15747 var newEnd = findSelectionEnd(start, end);
bgneal@45 15748 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
bgneal@45 15749
bgneal@45 15750 rng.setEnd(newEnd, endOffset);
bgneal@45 15751 }
bgneal@45 15752
bgneal@45 15753 return rng;
bgneal@45 15754 }
bgneal@45 15755
bgneal@45 15756 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
bgneal@45 15757 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
bgneal@45 15758
bgneal@45 15759 // find the index of the first child list.
bgneal@45 15760 each(node.childNodes, function(n, index) {
bgneal@45 15761 if (n.nodeName === "UL" || n.nodeName === "OL") {
bgneal@45 15762 listIndex = index;
bgneal@45 15763 list = n;
bgneal@45 15764 return false;
bgneal@45 15765 }
bgneal@45 15766 });
bgneal@45 15767
bgneal@45 15768 // get the index of the bookmarks
bgneal@45 15769 each(node.childNodes, function(n, index) {
bgneal@45 15770 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
bgneal@45 15771 if (n.id == bookmark.id + "_start") {
bgneal@45 15772 startIndex = index;
bgneal@45 15773 } else if (n.id == bookmark.id + "_end") {
bgneal@45 15774 endIndex = index;
bgneal@45 15775 }
bgneal@45 15776 }
bgneal@45 15777 });
bgneal@45 15778
bgneal@45 15779 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
bgneal@45 15780 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
bgneal@45 15781 each(tinymce.grep(node.childNodes), process);
bgneal@45 15782 return 0;
bgneal@45 15783 } else {
bgneal@45 15784 currentWrapElm = dom.clone(wrapElm, FALSE);
bgneal@45 15785
bgneal@45 15786 // create a list of the nodes on the same side of the list as the selection
bgneal@45 15787 each(tinymce.grep(node.childNodes), function(n, index) {
bgneal@45 15788 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
bgneal@45 15789 nodes.push(n);
bgneal@45 15790 n.parentNode.removeChild(n);
bgneal@45 15791 }
bgneal@45 15792 });
bgneal@45 15793
bgneal@45 15794 // insert the wrapping element either before or after the list.
bgneal@45 15795 if (startIndex < listIndex) {
bgneal@45 15796 node.insertBefore(currentWrapElm, list);
bgneal@45 15797 } else if (startIndex > listIndex) {
bgneal@45 15798 node.insertBefore(currentWrapElm, list.nextSibling);
bgneal@45 15799 }
bgneal@45 15800
bgneal@45 15801 // add the new nodes to the list.
bgneal@45 15802 newWrappers.push(currentWrapElm);
bgneal@45 15803
bgneal@45 15804 each(nodes, function(node) {
bgneal@45 15805 currentWrapElm.appendChild(node);
bgneal@45 15806 });
bgneal@45 15807
bgneal@45 15808 return currentWrapElm;
bgneal@45 15809 }
bgneal@45 15810 };
bgneal@45 15811
bgneal@45 15812 function applyRngStyle(rng, bookmark, node_specific) {
bgneal@45 15813 var newWrappers = [], wrapName, wrapElm, contentEditable = true;
bgneal@45 15814
bgneal@45 15815 // Setup wrapper element
bgneal@45 15816 wrapName = format.inline || format.block;
bgneal@45 15817 wrapElm = dom.create(wrapName);
bgneal@45 15818 setElementFormat(wrapElm);
bgneal@45 15819
bgneal@45 15820 rangeUtils.walk(rng, function(nodes) {
bgneal@45 15821 var currentWrapElm;
bgneal@45 15822
bgneal@45 15823 function process(node) {
bgneal@45 15824 var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
bgneal@45 15825
bgneal@45 15826 lastContentEditable = contentEditable;
bgneal@45 15827 nodeName = node.nodeName.toLowerCase();
bgneal@45 15828 parentName = node.parentNode.nodeName.toLowerCase();
bgneal@45 15829
bgneal@45 15830 // Node has a contentEditable value
bgneal@45 15831 if (node.nodeType === 1 && getContentEditable(node)) {
bgneal@45 15832 lastContentEditable = contentEditable;
bgneal@45 15833 contentEditable = getContentEditable(node) === "true";
bgneal@45 15834 hasContentEditableState = true; // We don't want to wrap the container only it's children
bgneal@45 15835 }
bgneal@45 15836
bgneal@45 15837 // Stop wrapping on br elements
bgneal@45 15838 if (isEq(nodeName, 'br')) {
bgneal@45 15839 currentWrapElm = 0;
bgneal@45 15840
bgneal@45 15841 // Remove any br elements when we wrap things
bgneal@45 15842 if (format.block)
bgneal@45 15843 dom.remove(node);
bgneal@45 15844
bgneal@45 15845 return;
bgneal@45 15846 }
bgneal@45 15847
bgneal@45 15848 // If node is wrapper type
bgneal@45 15849 if (format.wrapper && matchNode(node, name, vars)) {
bgneal@45 15850 currentWrapElm = 0;
bgneal@45 15851 return;
bgneal@45 15852 }
bgneal@45 15853
bgneal@45 15854 // Can we rename the block
bgneal@45 15855 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
bgneal@45 15856 node = dom.rename(node, wrapName);
bgneal@45 15857 setElementFormat(node);
bgneal@45 15858 newWrappers.push(node);
bgneal@45 15859 currentWrapElm = 0;
bgneal@45 15860 return;
bgneal@45 15861 }
bgneal@45 15862
bgneal@45 15863 // Handle selector patterns
bgneal@45 15864 if (format.selector) {
bgneal@45 15865 // Look for matching formats
bgneal@45 15866 each(formatList, function(format) {
bgneal@45 15867 // Check collapsed state if it exists
bgneal@45 15868 if ('collapsed' in format && format.collapsed !== isCollapsed) {
bgneal@45 15869 return;
bgneal@45 15870 }
bgneal@45 15871
bgneal@45 15872 if (dom.is(node, format.selector) && !isCaretNode(node)) {
bgneal@45 15873 setElementFormat(node, format);
bgneal@45 15874 found = true;
bgneal@45 15875 }
bgneal@45 15876 });
bgneal@45 15877
bgneal@45 15878 // Continue processing if a selector match wasn't found and a inline element is defined
bgneal@45 15879 if (!format.inline || found) {
bgneal@45 15880 currentWrapElm = 0;
bgneal@45 15881 return;
bgneal@45 15882 }
bgneal@45 15883 }
bgneal@45 15884
bgneal@45 15885 // Is it valid to wrap this item
bgneal@45 15886 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
bgneal@45 15887 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
bgneal@45 15888 // Start wrapping
bgneal@45 15889 if (!currentWrapElm) {
bgneal@45 15890 // Wrap the node
bgneal@45 15891 currentWrapElm = dom.clone(wrapElm, FALSE);
bgneal@45 15892 node.parentNode.insertBefore(currentWrapElm, node);
bgneal@45 15893 newWrappers.push(currentWrapElm);
bgneal@45 15894 }
bgneal@45 15895
bgneal@45 15896 currentWrapElm.appendChild(node);
bgneal@45 15897 } else if (nodeName == 'li' && bookmark) {
bgneal@45 15898 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
bgneal@45 15899 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
bgneal@45 15900 } else {
bgneal@45 15901 // Start a new wrapper for possible children
bgneal@45 15902 currentWrapElm = 0;
bgneal@45 15903
bgneal@45 15904 each(tinymce.grep(node.childNodes), process);
bgneal@45 15905
bgneal@45 15906 if (hasContentEditableState) {
bgneal@45 15907 contentEditable = lastContentEditable; // Restore last contentEditable state from stack
bgneal@45 15908 }
bgneal@45 15909
bgneal@45 15910 // End the last wrapper
bgneal@45 15911 currentWrapElm = 0;
bgneal@45 15912 }
bgneal@45 15913 };
bgneal@45 15914
bgneal@45 15915 // Process siblings from range
bgneal@45 15916 each(nodes, process);
bgneal@45 15917 });
bgneal@45 15918
bgneal@45 15919 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
bgneal@45 15920 if (format.wrap_links === false) {
bgneal@45 15921 each(newWrappers, function(node) {
bgneal@45 15922 function process(node) {
bgneal@45 15923 var i, currentWrapElm, children;
bgneal@45 15924
bgneal@45 15925 if (node.nodeName === 'A') {
bgneal@45 15926 currentWrapElm = dom.clone(wrapElm, FALSE);
bgneal@45 15927 newWrappers.push(currentWrapElm);
bgneal@45 15928
bgneal@45 15929 children = tinymce.grep(node.childNodes);
bgneal@45 15930 for (i = 0; i < children.length; i++)
bgneal@45 15931 currentWrapElm.appendChild(children[i]);
bgneal@45 15932
bgneal@45 15933 node.appendChild(currentWrapElm);
bgneal@45 15934 }
bgneal@45 15935
bgneal@45 15936 each(tinymce.grep(node.childNodes), process);
bgneal@45 15937 };
bgneal@45 15938
bgneal@45 15939 process(node);
bgneal@45 15940 });
bgneal@45 15941 }
bgneal@45 15942
bgneal@45 15943 // Cleanup
bgneal@45 15944
bgneal@45 15945 each(newWrappers, function(node) {
bgneal@45 15946 var childCount;
bgneal@45 15947
bgneal@45 15948 function getChildCount(node) {
bgneal@45 15949 var count = 0;
bgneal@45 15950
bgneal@45 15951 each(node.childNodes, function(node) {
bgneal@45 15952 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
bgneal@45 15953 count++;
bgneal@45 15954 });
bgneal@45 15955
bgneal@45 15956 return count;
bgneal@45 15957 };
bgneal@45 15958
bgneal@45 15959 function mergeStyles(node) {
bgneal@45 15960 var child, clone;
bgneal@45 15961
bgneal@45 15962 each(node.childNodes, function(node) {
bgneal@45 15963 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
bgneal@45 15964 child = node;
bgneal@45 15965 return FALSE; // break loop
bgneal@45 15966 }
bgneal@45 15967 });
bgneal@45 15968
bgneal@45 15969 // If child was found and of the same type as the current node
bgneal@45 15970 if (child && matchName(child, format)) {
bgneal@45 15971 clone = dom.clone(child, FALSE);
bgneal@45 15972 setElementFormat(clone);
bgneal@45 15973
bgneal@45 15974 dom.replace(clone, node, TRUE);
bgneal@45 15975 dom.remove(child, 1);
bgneal@45 15976 }
bgneal@45 15977
bgneal@45 15978 return clone || node;
bgneal@45 15979 };
bgneal@45 15980
bgneal@45 15981 childCount = getChildCount(node);
bgneal@45 15982
bgneal@45 15983 // Remove empty nodes but only if there is multiple wrappers and they are not block
bgneal@45 15984 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
bgneal@45 15985 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
bgneal@45 15986 dom.remove(node, 1);
bgneal@45 15987 return;
bgneal@45 15988 }
bgneal@45 15989
bgneal@45 15990 if (format.inline || format.wrapper) {
bgneal@45 15991 // Merges the current node with it's children of similar type to reduce the number of elements
bgneal@45 15992 if (!format.exact && childCount === 1)
bgneal@45 15993 node = mergeStyles(node);
bgneal@45 15994
bgneal@45 15995 // Remove/merge children
bgneal@45 15996 each(formatList, function(format) {
bgneal@45 15997 // Merge all children of similar type will move styles from child to parent
bgneal@45 15998 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
bgneal@45 15999 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
bgneal@45 16000 each(dom.select(format.inline, node), function(child) {
bgneal@45 16001 var parent;
bgneal@45 16002
bgneal@45 16003 // When wrap_links is set to false we don't want
bgneal@45 16004 // to remove the format on children within links
bgneal@45 16005 if (format.wrap_links === false) {
bgneal@45 16006 parent = child.parentNode;
bgneal@45 16007
bgneal@45 16008 do {
bgneal@45 16009 if (parent.nodeName === 'A')
bgneal@45 16010 return;
bgneal@45 16011 } while (parent = parent.parentNode);
bgneal@45 16012 }
bgneal@45 16013
bgneal@45 16014 removeFormat(format, vars, child, format.exact ? child : null);
bgneal@45 16015 });
bgneal@45 16016 });
bgneal@45 16017
bgneal@45 16018 // Remove child if direct parent is of same type
bgneal@45 16019 if (matchNode(node.parentNode, name, vars)) {
bgneal@45 16020 dom.remove(node, 1);
bgneal@45 16021 node = 0;
bgneal@45 16022 return TRUE;
bgneal@45 16023 }
bgneal@45 16024
bgneal@45 16025 // Look for parent with similar style format
bgneal@45 16026 if (format.merge_with_parents) {
bgneal@45 16027 dom.getParent(node.parentNode, function(parent) {
bgneal@45 16028 if (matchNode(parent, name, vars)) {
bgneal@45 16029 dom.remove(node, 1);
bgneal@45 16030 node = 0;
bgneal@45 16031 return TRUE;
bgneal@45 16032 }
bgneal@45 16033 });
bgneal@45 16034 }
bgneal@45 16035
bgneal@45 16036 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
bgneal@45 16037 if (node && format.merge_siblings !== false) {
bgneal@45 16038 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
bgneal@45 16039 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
bgneal@45 16040 }
bgneal@45 16041 }
bgneal@45 16042 });
bgneal@45 16043 };
bgneal@45 16044
bgneal@45 16045 if (format) {
bgneal@45 16046 if (node) {
bgneal@45 16047 if (node.nodeType) {
bgneal@45 16048 rng = dom.createRng();
bgneal@45 16049 rng.setStartBefore(node);
bgneal@45 16050 rng.setEndAfter(node);
bgneal@45 16051 applyRngStyle(expandRng(rng, formatList), null, true);
bgneal@45 16052 } else {
bgneal@45 16053 applyRngStyle(node, null, true);
bgneal@45 16054 }
bgneal@45 16055 } else {
bgneal@45 16056 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
bgneal@45 16057 // Obtain selection node before selection is unselected by applyRngStyle()
bgneal@45 16058 var curSelNode = ed.selection.getNode();
bgneal@45 16059
bgneal@45 16060 // Apply formatting to selection
bgneal@45 16061 ed.selection.setRng(adjustSelectionToVisibleSelection());
bgneal@45 16062 bookmark = selection.getBookmark();
bgneal@45 16063 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
bgneal@45 16064
bgneal@45 16065 // Colored nodes should be underlined so that the color of the underline matches the text color.
bgneal@45 16066 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
bgneal@45 16067 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
bgneal@45 16068 processUnderlineAndColor(curSelNode);
bgneal@45 16069 }
bgneal@45 16070
bgneal@45 16071 selection.moveToBookmark(bookmark);
bgneal@45 16072 moveStart(selection.getRng(TRUE));
bgneal@45 16073 ed.nodeChanged();
bgneal@45 16074 } else
bgneal@45 16075 performCaretAction('apply', name, vars);
bgneal@45 16076 }
bgneal@45 16077 }
bgneal@45 16078 };
bgneal@45 16079
bgneal@45 16080 function remove(name, vars, node) {
bgneal@45 16081 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
bgneal@45 16082
bgneal@45 16083 // Merges the styles for each node
bgneal@45 16084 function process(node) {
bgneal@45 16085 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
bgneal@45 16086
bgneal@45 16087 // Node has a contentEditable value
bgneal@45 16088 if (node.nodeType === 1 && getContentEditable(node)) {
bgneal@45 16089 lastContentEditable = contentEditable;
bgneal@45 16090 contentEditable = getContentEditable(node) === "true";
bgneal@45 16091 hasContentEditableState = true; // We don't want to wrap the container only it's children
bgneal@45 16092 }
bgneal@45 16093
bgneal@45 16094 // Grab the children first since the nodelist might be changed
bgneal@45 16095 children = tinymce.grep(node.childNodes);
bgneal@45 16096
bgneal@45 16097 // Process current node
bgneal@45 16098 if (contentEditable && !hasContentEditableState) {
bgneal@45 16099 for (i = 0, l = formatList.length; i < l; i++) {
bgneal@45 16100 if (removeFormat(formatList[i], vars, node, node))
bgneal@45 16101 break;
bgneal@45 16102 }
bgneal@45 16103 }
bgneal@45 16104
bgneal@45 16105 // Process the children
bgneal@45 16106 if (format.deep) {
bgneal@45 16107 if (children.length) {
bgneal@45 16108 for (i = 0, l = children.length; i < l; i++)
bgneal@45 16109 process(children[i]);
bgneal@45 16110
bgneal@45 16111 if (hasContentEditableState) {
bgneal@45 16112 contentEditable = lastContentEditable; // Restore last contentEditable state from stack
bgneal@45 16113 }
bgneal@45 16114 }
bgneal@45 16115 }
bgneal@45 16116 };
bgneal@45 16117
bgneal@45 16118 function findFormatRoot(container) {
bgneal@45 16119 var formatRoot;
bgneal@45 16120
bgneal@45 16121 // Find format root
bgneal@45 16122 each(getParents(container.parentNode).reverse(), function(parent) {
bgneal@45 16123 var format;
bgneal@45 16124
bgneal@45 16125 // Find format root element
bgneal@45 16126 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
bgneal@45 16127 // Is the node matching the format we are looking for
bgneal@45 16128 format = matchNode(parent, name, vars);
bgneal@45 16129 if (format && format.split !== false)
bgneal@45 16130 formatRoot = parent;
bgneal@45 16131 }
bgneal@45 16132 });
bgneal@45 16133
bgneal@45 16134 return formatRoot;
bgneal@45 16135 };
bgneal@45 16136
bgneal@45 16137 function wrapAndSplit(format_root, container, target, split) {
bgneal@45 16138 var parent, clone, lastClone, firstClone, i, formatRootParent;
bgneal@45 16139
bgneal@45 16140 // Format root found then clone formats and split it
bgneal@45 16141 if (format_root) {
bgneal@45 16142 formatRootParent = format_root.parentNode;
bgneal@45 16143
bgneal@45 16144 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
bgneal@45 16145 clone = dom.clone(parent, FALSE);
bgneal@45 16146
bgneal@45 16147 for (i = 0; i < formatList.length; i++) {
bgneal@45 16148 if (removeFormat(formatList[i], vars, clone, clone)) {
bgneal@45 16149 clone = 0;
bgneal@45 16150 break;
bgneal@45 16151 }
bgneal@45 16152 }
bgneal@45 16153
bgneal@45 16154 // Build wrapper node
bgneal@45 16155 if (clone) {
bgneal@45 16156 if (lastClone)
bgneal@45 16157 clone.appendChild(lastClone);
bgneal@45 16158
bgneal@45 16159 if (!firstClone)
bgneal@45 16160 firstClone = clone;
bgneal@45 16161
bgneal@45 16162 lastClone = clone;
bgneal@45 16163 }
bgneal@45 16164 }
bgneal@45 16165
bgneal@45 16166 // Never split block elements if the format is mixed
bgneal@45 16167 if (split && (!format.mixed || !isBlock(format_root)))
bgneal@45 16168 container = dom.split(format_root, container);
bgneal@45 16169
bgneal@45 16170 // Wrap container in cloned formats
bgneal@45 16171 if (lastClone) {
bgneal@45 16172 target.parentNode.insertBefore(lastClone, target);
bgneal@45 16173 firstClone.appendChild(target);
bgneal@45 16174 }
bgneal@45 16175 }
bgneal@45 16176
bgneal@45 16177 return container;
bgneal@45 16178 };
bgneal@45 16179
bgneal@45 16180 function splitToFormatRoot(container) {
bgneal@45 16181 return wrapAndSplit(findFormatRoot(container), container, container, true);
bgneal@45 16182 };
bgneal@45 16183
bgneal@45 16184 function unwrap(start) {
bgneal@45 16185 var node = dom.get(start ? '_start' : '_end'),
bgneal@45 16186 out = node[start ? 'firstChild' : 'lastChild'];
bgneal@45 16187
bgneal@45 16188 // If the end is placed within the start the result will be removed
bgneal@45 16189 // So this checks if the out node is a bookmark node if it is it
bgneal@45 16190 // checks for another more suitable node
bgneal@45 16191 if (isBookmarkNode(out))
bgneal@45 16192 out = out[start ? 'firstChild' : 'lastChild'];
bgneal@45 16193
bgneal@45 16194 dom.remove(node, true);
bgneal@45 16195
bgneal@45 16196 return out;
bgneal@45 16197 };
bgneal@45 16198
bgneal@45 16199 function removeRngStyle(rng) {
bgneal@45 16200 var startContainer, endContainer;
bgneal@45 16201
bgneal@45 16202 rng = expandRng(rng, formatList, TRUE);
bgneal@45 16203
bgneal@45 16204 if (format.split) {
bgneal@45 16205 startContainer = getContainer(rng, TRUE);
bgneal@45 16206 endContainer = getContainer(rng);
bgneal@45 16207
bgneal@45 16208 if (startContainer != endContainer) {
bgneal@45 16209 // Wrap start/end nodes in span element since these might be cloned/moved
bgneal@45 16210 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
bgneal@45 16211 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
bgneal@45 16212
bgneal@45 16213 // Split start/end
bgneal@45 16214 splitToFormatRoot(startContainer);
bgneal@45 16215 splitToFormatRoot(endContainer);
bgneal@45 16216
bgneal@45 16217 // Unwrap start/end to get real elements again
bgneal@45 16218 startContainer = unwrap(TRUE);
bgneal@45 16219 endContainer = unwrap();
bgneal@45 16220 } else
bgneal@45 16221 startContainer = endContainer = splitToFormatRoot(startContainer);
bgneal@45 16222
bgneal@45 16223 // Update range positions since they might have changed after the split operations
bgneal@45 16224 rng.startContainer = startContainer.parentNode;
bgneal@45 16225 rng.startOffset = nodeIndex(startContainer);
bgneal@45 16226 rng.endContainer = endContainer.parentNode;
bgneal@45 16227 rng.endOffset = nodeIndex(endContainer) + 1;
bgneal@45 16228 }
bgneal@45 16229
bgneal@45 16230 // Remove items between start/end
bgneal@45 16231 rangeUtils.walk(rng, function(nodes) {
bgneal@45 16232 each(nodes, function(node) {
bgneal@45 16233 process(node);
bgneal@45 16234
bgneal@45 16235 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
bgneal@45 16236 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
bgneal@45 16237 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
bgneal@45 16238 }
bgneal@45 16239 });
bgneal@45 16240 });
bgneal@45 16241 };
bgneal@45 16242
bgneal@45 16243 // Handle node
bgneal@45 16244 if (node) {
bgneal@45 16245 if (node.nodeType) {
bgneal@45 16246 rng = dom.createRng();
bgneal@45 16247 rng.setStartBefore(node);
bgneal@45 16248 rng.setEndAfter(node);
bgneal@45 16249 removeRngStyle(rng);
bgneal@45 16250 } else {
bgneal@45 16251 removeRngStyle(node);
bgneal@45 16252 }
bgneal@45 16253
bgneal@45 16254 return;
bgneal@45 16255 }
bgneal@45 16256
bgneal@45 16257 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
bgneal@45 16258 bookmark = selection.getBookmark();
bgneal@45 16259 removeRngStyle(selection.getRng(TRUE));
bgneal@45 16260 selection.moveToBookmark(bookmark);
bgneal@45 16261
bgneal@45 16262 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
bgneal@45 16263 if (format.inline && match(name, vars, selection.getStart())) {
bgneal@45 16264 moveStart(selection.getRng(true));
bgneal@45 16265 }
bgneal@45 16266
bgneal@45 16267 ed.nodeChanged();
bgneal@45 16268 } else
bgneal@45 16269 performCaretAction('remove', name, vars);
bgneal@45 16270
bgneal@45 16271 // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width
bgneal@45 16272 if (tinymce.isWebKit) {
bgneal@45 16273 ed.execCommand('mceCleanup');
bgneal@45 16274 }
bgneal@45 16275 };
bgneal@45 16276
bgneal@45 16277 function toggle(name, vars, node) {
bgneal@45 16278 var fmt = get(name);
bgneal@45 16279
bgneal@45 16280 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
bgneal@45 16281 remove(name, vars, node);
bgneal@45 16282 else
bgneal@45 16283 apply(name, vars, node);
bgneal@45 16284 };
bgneal@45 16285
bgneal@45 16286 function matchNode(node, name, vars, similar) {
bgneal@45 16287 var formatList = get(name), format, i, classes;
bgneal@45 16288
bgneal@45 16289 function matchItems(node, format, item_name) {
bgneal@45 16290 var key, value, items = format[item_name], i;
bgneal@45 16291
bgneal@45 16292 // Custom match
bgneal@45 16293 if (format.onmatch) {
bgneal@45 16294 return format.onmatch(node, format, item_name);
bgneal@45 16295 }
bgneal@45 16296
bgneal@45 16297 // Check all items
bgneal@45 16298 if (items) {
bgneal@45 16299 // Non indexed object
bgneal@45 16300 if (items.length === undefined) {
bgneal@45 16301 for (key in items) {
bgneal@45 16302 if (items.hasOwnProperty(key)) {
bgneal@45 16303 if (item_name === 'attributes')
bgneal@45 16304 value = dom.getAttrib(node, key);
bgneal@45 16305 else
bgneal@45 16306 value = getStyle(node, key);
bgneal@45 16307
bgneal@45 16308 if (similar && !value && !format.exact)
bgneal@45 16309 return;
bgneal@45 16310
bgneal@45 16311 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
bgneal@45 16312 return;
bgneal@45 16313 }
bgneal@45 16314 }
bgneal@45 16315 } else {
bgneal@45 16316 // Only one match needed for indexed arrays
bgneal@45 16317 for (i = 0; i < items.length; i++) {
bgneal@45 16318 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
bgneal@45 16319 return format;
bgneal@45 16320 }
bgneal@45 16321 }
bgneal@45 16322 }
bgneal@45 16323
bgneal@45 16324 return format;
bgneal@45 16325 };
bgneal@45 16326
bgneal@45 16327 if (formatList && node) {
bgneal@45 16328 // Check each format in list
bgneal@45 16329 for (i = 0; i < formatList.length; i++) {
bgneal@45 16330 format = formatList[i];
bgneal@45 16331
bgneal@45 16332 // Name name, attributes, styles and classes
bgneal@45 16333 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
bgneal@45 16334 // Match classes
bgneal@45 16335 if (classes = format.classes) {
bgneal@45 16336 for (i = 0; i < classes.length; i++) {
bgneal@45 16337 if (!dom.hasClass(node, classes[i]))
bgneal@45 16338 return;
bgneal@45 16339 }
bgneal@45 16340 }
bgneal@45 16341
bgneal@45 16342 return format;
bgneal@45 16343 }
bgneal@45 16344 }
bgneal@45 16345 }
bgneal@45 16346 };
bgneal@45 16347
bgneal@45 16348 function match(name, vars, node) {
bgneal@45 16349 var startNode;
bgneal@45 16350
bgneal@45 16351 function matchParents(node) {
bgneal@45 16352 // Find first node with similar format settings
bgneal@45 16353 node = dom.getParent(node, function(node) {
bgneal@45 16354 return !!matchNode(node, name, vars, true);
bgneal@45 16355 });
bgneal@45 16356
bgneal@45 16357 // Do an exact check on the similar format element
bgneal@45 16358 return matchNode(node, name, vars);
bgneal@45 16359 };
bgneal@45 16360
bgneal@45 16361 // Check specified node
bgneal@45 16362 if (node)
bgneal@45 16363 return matchParents(node);
bgneal@45 16364
bgneal@45 16365 // Check selected node
bgneal@45 16366 node = selection.getNode();
bgneal@45 16367 if (matchParents(node))
bgneal@45 16368 return TRUE;
bgneal@45 16369
bgneal@45 16370 // Check start node if it's different
bgneal@45 16371 startNode = selection.getStart();
bgneal@45 16372 if (startNode != node) {
bgneal@45 16373 if (matchParents(startNode))
bgneal@45 16374 return TRUE;
bgneal@45 16375 }
bgneal@45 16376
bgneal@45 16377 return FALSE;
bgneal@45 16378 };
bgneal@45 16379
bgneal@45 16380 function matchAll(names, vars) {
bgneal@45 16381 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
bgneal@45 16382
bgneal@45 16383 // Check start of selection for formats
bgneal@45 16384 startElement = selection.getStart();
bgneal@45 16385 dom.getParent(startElement, function(node) {
bgneal@45 16386 var i, name;
bgneal@45 16387
bgneal@45 16388 for (i = 0; i < names.length; i++) {
bgneal@45 16389 name = names[i];
bgneal@45 16390
bgneal@45 16391 if (!checkedMap[name] && matchNode(node, name, vars)) {
bgneal@45 16392 checkedMap[name] = true;
bgneal@45 16393 matchedFormatNames.push(name);
bgneal@45 16394 }
bgneal@45 16395 }
bgneal@45 16396 });
bgneal@45 16397
bgneal@45 16398 return matchedFormatNames;
bgneal@45 16399 };
bgneal@45 16400
bgneal@45 16401 function canApply(name) {
bgneal@45 16402 var formatList = get(name), startNode, parents, i, x, selector;
bgneal@45 16403
bgneal@45 16404 if (formatList) {
bgneal@45 16405 startNode = selection.getStart();
bgneal@45 16406 parents = getParents(startNode);
bgneal@45 16407
bgneal@45 16408 for (x = formatList.length - 1; x >= 0; x--) {
bgneal@45 16409 selector = formatList[x].selector;
bgneal@45 16410
bgneal@45 16411 // Format is not selector based, then always return TRUE
bgneal@45 16412 if (!selector)
bgneal@45 16413 return TRUE;
bgneal@45 16414
bgneal@45 16415 for (i = parents.length - 1; i >= 0; i--) {
bgneal@45 16416 if (dom.is(parents[i], selector))
bgneal@45 16417 return TRUE;
bgneal@45 16418 }
bgneal@45 16419 }
bgneal@45 16420 }
bgneal@45 16421
bgneal@45 16422 return FALSE;
bgneal@45 16423 };
bgneal@45 16424
bgneal@45 16425 // Expose to public
bgneal@45 16426 tinymce.extend(this, {
bgneal@45 16427 get : get,
bgneal@45 16428 register : register,
bgneal@45 16429 apply : apply,
bgneal@45 16430 remove : remove,
bgneal@45 16431 toggle : toggle,
bgneal@45 16432 match : match,
bgneal@45 16433 matchAll : matchAll,
bgneal@45 16434 matchNode : matchNode,
bgneal@45 16435 canApply : canApply
bgneal@45 16436 });
bgneal@45 16437
bgneal@45 16438 // Private functions
bgneal@45 16439
bgneal@45 16440 function matchName(node, format) {
bgneal@45 16441 // Check for inline match
bgneal@45 16442 if (isEq(node, format.inline))
bgneal@45 16443 return TRUE;
bgneal@45 16444
bgneal@45 16445 // Check for block match
bgneal@45 16446 if (isEq(node, format.block))
bgneal@45 16447 return TRUE;
bgneal@45 16448
bgneal@45 16449 // Check for selector match
bgneal@45 16450 if (format.selector)
bgneal@45 16451 return dom.is(node, format.selector);
bgneal@45 16452 };
bgneal@45 16453
bgneal@45 16454 function isEq(str1, str2) {
bgneal@45 16455 str1 = str1 || '';
bgneal@45 16456 str2 = str2 || '';
bgneal@45 16457
bgneal@45 16458 str1 = '' + (str1.nodeName || str1);
bgneal@45 16459 str2 = '' + (str2.nodeName || str2);
bgneal@45 16460
bgneal@45 16461 return str1.toLowerCase() == str2.toLowerCase();
bgneal@45 16462 };
bgneal@45 16463
bgneal@45 16464 function getStyle(node, name) {
bgneal@45 16465 var styleVal = dom.getStyle(node, name);
bgneal@45 16466
bgneal@45 16467 // Force the format to hex
bgneal@45 16468 if (name == 'color' || name == 'backgroundColor')
bgneal@45 16469 styleVal = dom.toHex(styleVal);
bgneal@45 16470
bgneal@45 16471 // Opera will return bold as 700
bgneal@45 16472 if (name == 'fontWeight' && styleVal == 700)
bgneal@45 16473 styleVal = 'bold';
bgneal@45 16474
bgneal@45 16475 return '' + styleVal;
bgneal@45 16476 };
bgneal@45 16477
bgneal@45 16478 function replaceVars(value, vars) {
bgneal@45 16479 if (typeof(value) != "string")
bgneal@45 16480 value = value(vars);
bgneal@45 16481 else if (vars) {
bgneal@45 16482 value = value.replace(/%(\w+)/g, function(str, name) {
bgneal@45 16483 return vars[name] || str;
bgneal@45 16484 });
bgneal@45 16485 }
bgneal@45 16486
bgneal@45 16487 return value;
bgneal@45 16488 };
bgneal@45 16489
bgneal@45 16490 function isWhiteSpaceNode(node) {
bgneal@45 16491 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
bgneal@45 16492 };
bgneal@45 16493
bgneal@45 16494 function wrap(node, name, attrs) {
bgneal@45 16495 var wrapper = dom.create(name, attrs);
bgneal@45 16496
bgneal@45 16497 node.parentNode.insertBefore(wrapper, node);
bgneal@45 16498 wrapper.appendChild(node);
bgneal@45 16499
bgneal@45 16500 return wrapper;
bgneal@45 16501 };
bgneal@45 16502
bgneal@45 16503 function expandRng(rng, format, remove) {
bgneal@45 16504 var sibling, lastIdx, leaf,
bgneal@45 16505 startContainer = rng.startContainer,
bgneal@45 16506 startOffset = rng.startOffset,
bgneal@45 16507 endContainer = rng.endContainer,
bgneal@45 16508 endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint;
bgneal@45 16509
bgneal@45 16510 // This function walks up the tree if there is no siblings before/after the node
bgneal@45 16511 function findParentContainer(start) {
bgneal@45 16512 var container, parent, child, sibling, siblingName;
bgneal@45 16513
bgneal@45 16514 container = parent = start ? startContainer : endContainer;
bgneal@45 16515 siblingName = start ? 'previousSibling' : 'nextSibling';
bgneal@45 16516 root = dom.getRoot();
bgneal@45 16517
bgneal@45 16518 // If it's a text node and the offset is inside the text
bgneal@45 16519 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
bgneal@45 16520 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
bgneal@45 16521 return container;
bgneal@45 16522 }
bgneal@45 16523 }
bgneal@45 16524
bgneal@45 16525 for (;;) {
bgneal@45 16526 // Stop expanding on block elements
bgneal@45 16527 if (!format[0].block_expand && isBlock(parent))
bgneal@45 16528 return parent;
bgneal@45 16529
bgneal@45 16530 // Walk left/right
bgneal@45 16531 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
bgneal@45 16532 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
bgneal@45 16533 return parent;
bgneal@45 16534 }
bgneal@45 16535 }
bgneal@45 16536
bgneal@45 16537 // Check if we can move up are we at root level or body level
bgneal@45 16538 if (parent.parentNode == root) {
bgneal@45 16539 container = parent;
bgneal@45 16540 break;
bgneal@45 16541 }
bgneal@45 16542
bgneal@45 16543 parent = parent.parentNode;
bgneal@45 16544 }
bgneal@45 16545
bgneal@45 16546 return container;
bgneal@45 16547 };
bgneal@45 16548
bgneal@45 16549 // This function walks down the tree to find the leaf at the selection.
bgneal@45 16550 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
bgneal@45 16551 function findLeaf(node, offset) {
bgneal@45 16552 if (offset === undefined)
bgneal@45 16553 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
bgneal@45 16554 while (node && node.hasChildNodes()) {
bgneal@45 16555 node = node.childNodes[offset];
bgneal@45 16556 if (node)
bgneal@45 16557 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
bgneal@45 16558 }
bgneal@45 16559 return { node: node, offset: offset };
bgneal@45 16560 }
bgneal@45 16561
bgneal@45 16562 // If index based start position then resolve it
bgneal@45 16563 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
bgneal@45 16564 lastIdx = startContainer.childNodes.length - 1;
bgneal@45 16565 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
bgneal@45 16566
bgneal@45 16567 if (startContainer.nodeType == 3)
bgneal@45 16568 startOffset = 0;
bgneal@45 16569 }
bgneal@45 16570
bgneal@45 16571 // If index based end position then resolve it
bgneal@45 16572 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
bgneal@45 16573 lastIdx = endContainer.childNodes.length - 1;
bgneal@45 16574 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
bgneal@45 16575
bgneal@45 16576 if (endContainer.nodeType == 3)
bgneal@45 16577 endOffset = endContainer.nodeValue.length;
bgneal@45 16578 }
bgneal@45 16579
bgneal@45 16580 // Expands the node to the closes contentEditable false element if it exists
bgneal@45 16581 function findParentContentEditable(node) {
bgneal@45 16582 var parent = node;
bgneal@45 16583
bgneal@45 16584 while (parent) {
bgneal@45 16585 if (parent.nodeType === 1 && getContentEditable(parent)) {
bgneal@45 16586 return getContentEditable(parent) === "false" ? parent : node;
bgneal@45 16587 }
bgneal@45 16588
bgneal@45 16589 parent = parent.parentNode;
bgneal@45 16590 }
bgneal@45 16591
bgneal@45 16592 return node;
bgneal@45 16593 };
bgneal@45 16594
bgneal@45 16595 // Expand to closest contentEditable element
bgneal@45 16596 startContainer = findParentContentEditable(startContainer);
bgneal@45 16597 endContainer = findParentContentEditable(endContainer);
bgneal@45 16598
bgneal@45 16599 // Exclude bookmark nodes if possible
bgneal@45 16600 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
bgneal@45 16601 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
bgneal@45 16602 startContainer = startContainer.nextSibling || startContainer;
bgneal@45 16603
bgneal@45 16604 if (startContainer.nodeType == 3)
bgneal@45 16605 startOffset = 0;
bgneal@45 16606 }
bgneal@45 16607
bgneal@45 16608 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
bgneal@45 16609 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
bgneal@45 16610 endContainer = endContainer.previousSibling || endContainer;
bgneal@45 16611
bgneal@45 16612 if (endContainer.nodeType == 3)
bgneal@45 16613 endOffset = endContainer.length;
bgneal@45 16614 }
bgneal@45 16615
bgneal@45 16616 if (format[0].inline) {
bgneal@45 16617 if (rng.collapsed) {
bgneal@45 16618 function findWordEndPoint(container, offset, start) {
bgneal@45 16619 var walker, node, pos, lastTextNode;
bgneal@45 16620
bgneal@45 16621 function findSpace(node, offset) {
bgneal@45 16622 var pos, pos2, str = node.nodeValue;
bgneal@45 16623
bgneal@45 16624 if (typeof(offset) == "undefined") {
bgneal@45 16625 offset = start ? str.length : 0;
bgneal@45 16626 }
bgneal@45 16627
bgneal@45 16628 if (start) {
bgneal@45 16629 pos = str.lastIndexOf(' ', offset);
bgneal@45 16630 pos2 = str.lastIndexOf('\u00a0', offset);
bgneal@45 16631 pos = pos > pos2 ? pos : pos2;
bgneal@45 16632
bgneal@45 16633 // Include the space on remove to avoid tag soup
bgneal@45 16634 if (pos !== -1 && !remove) {
bgneal@45 16635 pos++;
bgneal@45 16636 }
bgneal@45 16637 } else {
bgneal@45 16638 pos = str.indexOf(' ', offset);
bgneal@45 16639 pos2 = str.indexOf('\u00a0', offset);
bgneal@45 16640 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
bgneal@45 16641 }
bgneal@45 16642
bgneal@45 16643 return pos;
bgneal@45 16644 };
bgneal@45 16645
bgneal@45 16646 if (container.nodeType === 3) {
bgneal@45 16647 pos = findSpace(container, offset);
bgneal@45 16648
bgneal@45 16649 if (pos !== -1) {
bgneal@45 16650 return {container : container, offset : pos};
bgneal@45 16651 }
bgneal@45 16652
bgneal@45 16653 lastTextNode = container;
bgneal@45 16654 }
bgneal@45 16655
bgneal@45 16656 // Walk the nodes inside the block
bgneal@45 16657 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
bgneal@45 16658 while (node = walker[start ? 'prev' : 'next']()) {
bgneal@45 16659 if (node.nodeType === 3) {
bgneal@45 16660 lastTextNode = node;
bgneal@45 16661 pos = findSpace(node);
bgneal@45 16662
bgneal@45 16663 if (pos !== -1) {
bgneal@45 16664 return {container : node, offset : pos};
bgneal@45 16665 }
bgneal@45 16666 } else if (isBlock(node)) {
bgneal@45 16667 break;
bgneal@45 16668 }
bgneal@45 16669 }
bgneal@45 16670
bgneal@45 16671 if (lastTextNode) {
bgneal@45 16672 if (start) {
bgneal@45 16673 offset = 0;
bgneal@45 16674 } else {
bgneal@45 16675 offset = lastTextNode.length;
bgneal@45 16676 }
bgneal@45 16677
bgneal@45 16678 return {container: lastTextNode, offset: offset};
bgneal@45 16679 }
bgneal@45 16680 }
bgneal@45 16681
bgneal@45 16682 // Expand left to closest word boundery
bgneal@45 16683 endPoint = findWordEndPoint(startContainer, startOffset, true);
bgneal@45 16684 if (endPoint) {
bgneal@45 16685 startContainer = endPoint.container;
bgneal@45 16686 startOffset = endPoint.offset;
bgneal@45 16687 }
bgneal@45 16688
bgneal@45 16689 // Expand right to closest word boundery
bgneal@45 16690 endPoint = findWordEndPoint(endContainer, endOffset);
bgneal@45 16691 if (endPoint) {
bgneal@45 16692 endContainer = endPoint.container;
bgneal@45 16693 endOffset = endPoint.offset;
bgneal@45 16694 }
bgneal@45 16695 }
bgneal@45 16696
bgneal@45 16697 // Avoid applying formatting to a trailing space.
bgneal@45 16698 leaf = findLeaf(endContainer, endOffset);
bgneal@45 16699 if (leaf.node) {
bgneal@45 16700 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
bgneal@45 16701 leaf = findLeaf(leaf.node.previousSibling);
bgneal@45 16702
bgneal@45 16703 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
bgneal@45 16704 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
bgneal@45 16705
bgneal@45 16706 if (leaf.offset > 1) {
bgneal@45 16707 endContainer = leaf.node;
bgneal@45 16708 endContainer.splitText(leaf.offset - 1);
bgneal@45 16709 } else if (leaf.node.previousSibling) {
bgneal@45 16710 // TODO: Figure out why this is in here
bgneal@45 16711 //endContainer = leaf.node.previousSibling;
bgneal@45 16712 }
bgneal@45 16713 }
bgneal@45 16714 }
bgneal@45 16715 }
bgneal@45 16716
bgneal@45 16717 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
bgneal@45 16718 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
bgneal@45 16719 // This will reduce the number of wrapper elements that needs to be created
bgneal@45 16720 // Move start point up the tree
bgneal@45 16721 if (format[0].inline || format[0].block_expand) {
bgneal@45 16722 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
bgneal@45 16723 startContainer = findParentContainer(true);
bgneal@45 16724 }
bgneal@45 16725
bgneal@45 16726 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
bgneal@45 16727 endContainer = findParentContainer();
bgneal@45 16728 }
bgneal@45 16729 }
bgneal@45 16730
bgneal@45 16731 // Expand start/end container to matching selector
bgneal@45 16732 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
bgneal@45 16733 function findSelectorEndPoint(container, sibling_name) {
bgneal@45 16734 var parents, i, y, curFormat;
bgneal@45 16735
bgneal@45 16736 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
bgneal@45 16737 container = container[sibling_name];
bgneal@45 16738
bgneal@45 16739 parents = getParents(container);
bgneal@45 16740 for (i = 0; i < parents.length; i++) {
bgneal@45 16741 for (y = 0; y < format.length; y++) {
bgneal@45 16742 curFormat = format[y];
bgneal@45 16743
bgneal@45 16744 // If collapsed state is set then skip formats that doesn't match that
bgneal@45 16745 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
bgneal@45 16746 continue;
bgneal@45 16747
bgneal@45 16748 if (dom.is(parents[i], curFormat.selector))
bgneal@45 16749 return parents[i];
bgneal@45 16750 }
bgneal@45 16751 }
bgneal@45 16752
bgneal@45 16753 return container;
bgneal@45 16754 };
bgneal@45 16755
bgneal@45 16756 // Find new startContainer/endContainer if there is better one
bgneal@45 16757 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
bgneal@45 16758 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
bgneal@45 16759 }
bgneal@45 16760
bgneal@45 16761 // Expand start/end container to matching block element or text node
bgneal@45 16762 if (format[0].block || format[0].selector) {
bgneal@45 16763 function findBlockEndPoint(container, sibling_name, sibling_name2) {
bgneal@45 16764 var node;
bgneal@45 16765
bgneal@45 16766 // Expand to block of similar type
bgneal@45 16767 if (!format[0].wrapper)
bgneal@45 16768 node = dom.getParent(container, format[0].block);
bgneal@45 16769
bgneal@45 16770 // Expand to first wrappable block element or any block element
bgneal@45 16771 if (!node)
bgneal@45 16772 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
bgneal@45 16773
bgneal@45 16774 // Exclude inner lists from wrapping
bgneal@45 16775 if (node && format[0].wrapper)
bgneal@45 16776 node = getParents(node, 'ul,ol').reverse()[0] || node;
bgneal@45 16777
bgneal@45 16778 // Didn't find a block element look for first/last wrappable element
bgneal@45 16779 if (!node) {
bgneal@45 16780 node = container;
bgneal@45 16781
bgneal@45 16782 while (node[sibling_name] && !isBlock(node[sibling_name])) {
bgneal@45 16783 node = node[sibling_name];
bgneal@45 16784
bgneal@45 16785 // Break on BR but include it will be removed later on
bgneal@45 16786 // we can't remove it now since we need to check if it can be wrapped
bgneal@45 16787 if (isEq(node, 'br'))
bgneal@45 16788 break;
bgneal@45 16789 }
bgneal@45 16790 }
bgneal@45 16791
bgneal@45 16792 return node || container;
bgneal@45 16793 };
bgneal@45 16794
bgneal@45 16795 // Find new startContainer/endContainer if there is better one
bgneal@45 16796 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
bgneal@45 16797 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
bgneal@45 16798
bgneal@45 16799 // Non block element then try to expand up the leaf
bgneal@45 16800 if (format[0].block) {
bgneal@45 16801 if (!isBlock(startContainer))
bgneal@45 16802 startContainer = findParentContainer(true);
bgneal@45 16803
bgneal@45 16804 if (!isBlock(endContainer))
bgneal@45 16805 endContainer = findParentContainer();
bgneal@45 16806 }
bgneal@45 16807 }
bgneal@45 16808
bgneal@45 16809 // Setup index for startContainer
bgneal@45 16810 if (startContainer.nodeType == 1) {
bgneal@45 16811 startOffset = nodeIndex(startContainer);
bgneal@45 16812 startContainer = startContainer.parentNode;
bgneal@45 16813 }
bgneal@45 16814
bgneal@45 16815 // Setup index for endContainer
bgneal@45 16816 if (endContainer.nodeType == 1) {
bgneal@45 16817 endOffset = nodeIndex(endContainer) + 1;
bgneal@45 16818 endContainer = endContainer.parentNode;
bgneal@45 16819 }
bgneal@45 16820
bgneal@45 16821 // Return new range like object
bgneal@45 16822 return {
bgneal@45 16823 startContainer : startContainer,
bgneal@45 16824 startOffset : startOffset,
bgneal@45 16825 endContainer : endContainer,
bgneal@45 16826 endOffset : endOffset
bgneal@45 16827 };
bgneal@45 16828 }
bgneal@45 16829
bgneal@45 16830 function removeFormat(format, vars, node, compare_node) {
bgneal@45 16831 var i, attrs, stylesModified;
bgneal@45 16832
bgneal@45 16833 // Check if node matches format
bgneal@45 16834 if (!matchName(node, format))
bgneal@45 16835 return FALSE;
bgneal@45 16836
bgneal@45 16837 // Should we compare with format attribs and styles
bgneal@45 16838 if (format.remove != 'all') {
bgneal@45 16839 // Remove styles
bgneal@45 16840 each(format.styles, function(value, name) {
bgneal@45 16841 value = replaceVars(value, vars);
bgneal@45 16842
bgneal@45 16843 // Indexed array
bgneal@45 16844 if (typeof(name) === 'number') {
bgneal@45 16845 name = value;
bgneal@45 16846 compare_node = 0;
bgneal@45 16847 }
bgneal@45 16848
bgneal@45 16849 if (!compare_node || isEq(getStyle(compare_node, name), value))
bgneal@45 16850 dom.setStyle(node, name, '');
bgneal@45 16851
bgneal@45 16852 stylesModified = 1;
bgneal@45 16853 });
bgneal@45 16854
bgneal@45 16855 // Remove style attribute if it's empty
bgneal@45 16856 if (stylesModified && dom.getAttrib(node, 'style') == '') {
bgneal@45 16857 node.removeAttribute('style');
bgneal@45 16858 node.removeAttribute('data-mce-style');
bgneal@45 16859 }
bgneal@45 16860
bgneal@45 16861 // Remove attributes
bgneal@45 16862 each(format.attributes, function(value, name) {
bgneal@45 16863 var valueOut;
bgneal@45 16864
bgneal@45 16865 value = replaceVars(value, vars);
bgneal@45 16866
bgneal@45 16867 // Indexed array
bgneal@45 16868 if (typeof(name) === 'number') {
bgneal@45 16869 name = value;
bgneal@45 16870 compare_node = 0;
bgneal@45 16871 }
bgneal@45 16872
bgneal@45 16873 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
bgneal@45 16874 // Keep internal classes
bgneal@45 16875 if (name == 'class') {
bgneal@45 16876 value = dom.getAttrib(node, name);
bgneal@45 16877 if (value) {
bgneal@45 16878 // Build new class value where everything is removed except the internal prefixed classes
bgneal@45 16879 valueOut = '';
bgneal@45 16880 each(value.split(/\s+/), function(cls) {
bgneal@45 16881 if (/mce\w+/.test(cls))
bgneal@45 16882 valueOut += (valueOut ? ' ' : '') + cls;
bgneal@45 16883 });
bgneal@45 16884
bgneal@45 16885 // We got some internal classes left
bgneal@45 16886 if (valueOut) {
bgneal@45 16887 dom.setAttrib(node, name, valueOut);
bgneal@45 16888 return;
bgneal@45 16889 }
bgneal@45 16890 }
bgneal@45 16891 }
bgneal@45 16892
bgneal@45 16893 // IE6 has a bug where the attribute doesn't get removed correctly
bgneal@45 16894 if (name == "class")
bgneal@45 16895 node.removeAttribute('className');
bgneal@45 16896
bgneal@45 16897 // Remove mce prefixed attributes
bgneal@45 16898 if (MCE_ATTR_RE.test(name))
bgneal@45 16899 node.removeAttribute('data-mce-' + name);
bgneal@45 16900
bgneal@45 16901 node.removeAttribute(name);
bgneal@45 16902 }
bgneal@45 16903 });
bgneal@45 16904
bgneal@45 16905 // Remove classes
bgneal@45 16906 each(format.classes, function(value) {
bgneal@45 16907 value = replaceVars(value, vars);
bgneal@45 16908
bgneal@45 16909 if (!compare_node || dom.hasClass(compare_node, value))
bgneal@45 16910 dom.removeClass(node, value);
bgneal@45 16911 });
bgneal@45 16912
bgneal@45 16913 // Check for non internal attributes
bgneal@45 16914 attrs = dom.getAttribs(node);
bgneal@45 16915 for (i = 0; i < attrs.length; i++) {
bgneal@45 16916 if (attrs[i].nodeName.indexOf('_') !== 0)
bgneal@45 16917 return FALSE;
bgneal@45 16918 }
bgneal@45 16919 }
bgneal@45 16920
bgneal@45 16921 // Remove the inline child if it's empty for example <b> or <span>
bgneal@45 16922 if (format.remove != 'none') {
bgneal@45 16923 removeNode(node, format);
bgneal@45 16924 return TRUE;
bgneal@45 16925 }
bgneal@45 16926 };
bgneal@45 16927
bgneal@45 16928 function removeNode(node, format) {
bgneal@45 16929 var parentNode = node.parentNode, rootBlockElm;
bgneal@45 16930
bgneal@45 16931 if (format.block) {
bgneal@45 16932 if (!forcedRootBlock) {
bgneal@45 16933 function find(node, next, inc) {
bgneal@45 16934 node = getNonWhiteSpaceSibling(node, next, inc);
bgneal@45 16935
bgneal@45 16936 return !node || (node.nodeName == 'BR' || isBlock(node));
bgneal@45 16937 };
bgneal@45 16938
bgneal@45 16939 // Append BR elements if needed before we remove the block
bgneal@45 16940 if (isBlock(node) && !isBlock(parentNode)) {
bgneal@45 16941 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
bgneal@45 16942 node.insertBefore(dom.create('br'), node.firstChild);
bgneal@45 16943
bgneal@45 16944 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
bgneal@45 16945 node.appendChild(dom.create('br'));
bgneal@45 16946 }
bgneal@45 16947 } else {
bgneal@45 16948 // Wrap the block in a forcedRootBlock if we are at the root of document
bgneal@45 16949 if (parentNode == dom.getRoot()) {
bgneal@45 16950 if (!format.list_block || !isEq(node, format.list_block)) {
bgneal@45 16951 each(tinymce.grep(node.childNodes), function(node) {
bgneal@45 16952 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
bgneal@45 16953 if (!rootBlockElm)
bgneal@45 16954 rootBlockElm = wrap(node, forcedRootBlock);
bgneal@45 16955 else
bgneal@45 16956 rootBlockElm.appendChild(node);
bgneal@45 16957 } else
bgneal@45 16958 rootBlockElm = 0;
bgneal@45 16959 });
bgneal@45 16960 }
bgneal@45 16961 }
bgneal@45 16962 }
bgneal@45 16963 }
bgneal@45 16964
bgneal@45 16965 // Never remove nodes that isn't the specified inline element if a selector is specified too
bgneal@45 16966 if (format.selector && format.inline && !isEq(format.inline, node))
bgneal@45 16967 return;
bgneal@45 16968
bgneal@45 16969 dom.remove(node, 1);
bgneal@45 16970 };
bgneal@45 16971
bgneal@45 16972 function getNonWhiteSpaceSibling(node, next, inc) {
bgneal@45 16973 if (node) {
bgneal@45 16974 next = next ? 'nextSibling' : 'previousSibling';
bgneal@45 16975
bgneal@45 16976 for (node = inc ? node : node[next]; node; node = node[next]) {
bgneal@45 16977 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
bgneal@45 16978 return node;
bgneal@45 16979 }
bgneal@45 16980 }
bgneal@45 16981 };
bgneal@45 16982
bgneal@45 16983 function isBookmarkNode(node) {
bgneal@45 16984 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
bgneal@45 16985 };
bgneal@45 16986
bgneal@45 16987 function mergeSiblings(prev, next) {
bgneal@45 16988 var marker, sibling, tmpSibling;
bgneal@45 16989
bgneal@45 16990 function compareElements(node1, node2) {
bgneal@45 16991 // Not the same name
bgneal@45 16992 if (node1.nodeName != node2.nodeName)
bgneal@45 16993 return FALSE;
bgneal@45 16994
bgneal@45 16995 function getAttribs(node) {
bgneal@45 16996 var attribs = {};
bgneal@45 16997
bgneal@45 16998 each(dom.getAttribs(node), function(attr) {
bgneal@45 16999 var name = attr.nodeName.toLowerCase();
bgneal@45 17000
bgneal@45 17001 // Don't compare internal attributes or style
bgneal@45 17002 if (name.indexOf('_') !== 0 && name !== 'style')
bgneal@45 17003 attribs[name] = dom.getAttrib(node, name);
bgneal@45 17004 });
bgneal@45 17005
bgneal@45 17006 return attribs;
bgneal@45 17007 };
bgneal@45 17008
bgneal@45 17009 function compareObjects(obj1, obj2) {
bgneal@45 17010 var value, name;
bgneal@45 17011
bgneal@45 17012 for (name in obj1) {
bgneal@45 17013 // Obj1 has item obj2 doesn't have
bgneal@45 17014 if (obj1.hasOwnProperty(name)) {
bgneal@45 17015 value = obj2[name];
bgneal@45 17016
bgneal@45 17017 // Obj2 doesn't have obj1 item
bgneal@45 17018 if (value === undefined)
bgneal@45 17019 return FALSE;
bgneal@45 17020
bgneal@45 17021 // Obj2 item has a different value
bgneal@45 17022 if (obj1[name] != value)
bgneal@45 17023 return FALSE;
bgneal@45 17024
bgneal@45 17025 // Delete similar value
bgneal@45 17026 delete obj2[name];
bgneal@45 17027 }
bgneal@45 17028 }
bgneal@45 17029
bgneal@45 17030 // Check if obj 2 has something obj 1 doesn't have
bgneal@45 17031 for (name in obj2) {
bgneal@45 17032 // Obj2 has item obj1 doesn't have
bgneal@45 17033 if (obj2.hasOwnProperty(name))
bgneal@45 17034 return FALSE;
bgneal@45 17035 }
bgneal@45 17036
bgneal@45 17037 return TRUE;
bgneal@45 17038 };
bgneal@45 17039
bgneal@45 17040 // Attribs are not the same
bgneal@45 17041 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
bgneal@45 17042 return FALSE;
bgneal@45 17043
bgneal@45 17044 // Styles are not the same
bgneal@45 17045 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
bgneal@45 17046 return FALSE;
bgneal@45 17047
bgneal@45 17048 return TRUE;
bgneal@45 17049 };
bgneal@45 17050
bgneal@45 17051 // Check if next/prev exists and that they are elements
bgneal@45 17052 if (prev && next) {
bgneal@45 17053 function findElementSibling(node, sibling_name) {
bgneal@45 17054 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
bgneal@45 17055 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
bgneal@45 17056 return node;
bgneal@45 17057
bgneal@45 17058 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@45 17059 return sibling;
bgneal@45 17060 }
bgneal@45 17061
bgneal@45 17062 return node;
bgneal@45 17063 };
bgneal@45 17064
bgneal@45 17065 // If previous sibling is empty then jump over it
bgneal@45 17066 prev = findElementSibling(prev, 'previousSibling');
bgneal@45 17067 next = findElementSibling(next, 'nextSibling');
bgneal@45 17068
bgneal@45 17069 // Compare next and previous nodes
bgneal@45 17070 if (compareElements(prev, next)) {
bgneal@45 17071 // Append nodes between
bgneal@45 17072 for (sibling = prev.nextSibling; sibling && sibling != next;) {
bgneal@45 17073 tmpSibling = sibling;
bgneal@45 17074 sibling = sibling.nextSibling;
bgneal@45 17075 prev.appendChild(tmpSibling);
bgneal@45 17076 }
bgneal@45 17077
bgneal@45 17078 // Remove next node
bgneal@45 17079 dom.remove(next);
bgneal@45 17080
bgneal@45 17081 // Move children into prev node
bgneal@45 17082 each(tinymce.grep(next.childNodes), function(node) {
bgneal@45 17083 prev.appendChild(node);
bgneal@45 17084 });
bgneal@45 17085
bgneal@45 17086 return prev;
bgneal@45 17087 }
bgneal@45 17088 }
bgneal@45 17089
bgneal@45 17090 return next;
bgneal@45 17091 };
bgneal@45 17092
bgneal@45 17093 function isTextBlock(name) {
bgneal@45 17094 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
bgneal@45 17095 };
bgneal@45 17096
bgneal@45 17097 function getContainer(rng, start) {
bgneal@45 17098 var container, offset, lastIdx, walker;
bgneal@45 17099
bgneal@45 17100 container = rng[start ? 'startContainer' : 'endContainer'];
bgneal@45 17101 offset = rng[start ? 'startOffset' : 'endOffset'];
bgneal@45 17102
bgneal@45 17103 if (container.nodeType == 1) {
bgneal@45 17104 lastIdx = container.childNodes.length - 1;
bgneal@45 17105
bgneal@45 17106 if (!start && offset)
bgneal@45 17107 offset--;
bgneal@45 17108
bgneal@45 17109 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
bgneal@45 17110 }
bgneal@45 17111
bgneal@45 17112 // If start text node is excluded then walk to the next node
bgneal@45 17113 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
bgneal@45 17114 container = new TreeWalker(container, ed.getBody()).next() || container;
bgneal@45 17115 }
bgneal@45 17116
bgneal@45 17117 // If end text node is excluded then walk to the previous node
bgneal@45 17118 if (container.nodeType === 3 && !start && offset == 0) {
bgneal@45 17119 container = new TreeWalker(container, ed.getBody()).prev() || container;
bgneal@45 17120 }
bgneal@45 17121
bgneal@45 17122 return container;
bgneal@45 17123 };
bgneal@45 17124
bgneal@45 17125 function performCaretAction(type, name, vars) {
bgneal@45 17126 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
bgneal@45 17127
bgneal@45 17128 // Creates a caret container bogus element
bgneal@45 17129 function createCaretContainer(fill) {
bgneal@45 17130 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
bgneal@45 17131
bgneal@45 17132 if (fill) {
bgneal@45 17133 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
bgneal@45 17134 }
bgneal@45 17135
bgneal@45 17136 return caretContainer;
bgneal@45 17137 };
bgneal@45 17138
bgneal@45 17139 function isCaretContainerEmpty(node, nodes) {
bgneal@45 17140 while (node) {
bgneal@45 17141 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
bgneal@45 17142 return false;
bgneal@45 17143 }
bgneal@45 17144
bgneal@45 17145 // Collect nodes
bgneal@45 17146 if (nodes && node.nodeType === 1) {
bgneal@45 17147 nodes.push(node);
bgneal@45 17148 }
bgneal@45 17149
bgneal@45 17150 node = node.firstChild;
bgneal@45 17151 }
bgneal@45 17152
bgneal@45 17153 return true;
bgneal@45 17154 };
bgneal@45 17155
bgneal@45 17156 // Returns any parent caret container element
bgneal@45 17157 function getParentCaretContainer(node) {
bgneal@45 17158 while (node) {
bgneal@45 17159 if (node.id === caretContainerId) {
bgneal@45 17160 return node;
bgneal@45 17161 }
bgneal@45 17162
bgneal@45 17163 node = node.parentNode;
bgneal@45 17164 }
bgneal@45 17165 };
bgneal@45 17166
bgneal@45 17167 // Finds the first text node in the specified node
bgneal@45 17168 function findFirstTextNode(node) {
bgneal@45 17169 var walker;
bgneal@45 17170
bgneal@45 17171 if (node) {
bgneal@45 17172 walker = new TreeWalker(node, node);
bgneal@45 17173
bgneal@45 17174 for (node = walker.current(); node; node = walker.next()) {
bgneal@45 17175 if (node.nodeType === 3) {
bgneal@45 17176 return node;
bgneal@45 17177 }
bgneal@45 17178 }
bgneal@45 17179 }
bgneal@45 17180 };
bgneal@45 17181
bgneal@45 17182 // Removes the caret container for the specified node or all on the current document
bgneal@45 17183 function removeCaretContainer(node, move_caret) {
bgneal@45 17184 var child, rng;
bgneal@45 17185
bgneal@45 17186 if (!node) {
bgneal@45 17187 node = getParentCaretContainer(selection.getStart());
bgneal@45 17188
bgneal@45 17189 if (!node) {
bgneal@45 17190 while (node = dom.get(caretContainerId)) {
bgneal@45 17191 removeCaretContainer(node, false);
bgneal@45 17192 }
bgneal@45 17193 }
bgneal@45 17194 } else {
bgneal@45 17195 rng = selection.getRng(true);
bgneal@45 17196
bgneal@45 17197 if (isCaretContainerEmpty(node)) {
bgneal@45 17198 if (move_caret !== false) {
bgneal@45 17199 rng.setStartBefore(node);
bgneal@45 17200 rng.setEndBefore(node);
bgneal@45 17201 }
bgneal@45 17202
bgneal@45 17203 dom.remove(node);
bgneal@45 17204 } else {
bgneal@45 17205 child = findFirstTextNode(node);
bgneal@45 17206
bgneal@45 17207 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
bgneal@45 17208 child = child.deleteData(0, 1);
bgneal@45 17209 }
bgneal@45 17210
bgneal@45 17211 dom.remove(node, 1);
bgneal@45 17212 }
bgneal@45 17213
bgneal@45 17214 selection.setRng(rng);
bgneal@45 17215 }
bgneal@45 17216 };
bgneal@45 17217
bgneal@45 17218 // Applies formatting to the caret postion
bgneal@45 17219 function applyCaretFormat() {
bgneal@45 17220 var rng, caretContainer, textNode, offset, bookmark, container, text;
bgneal@45 17221
bgneal@45 17222 rng = selection.getRng(true);
bgneal@45 17223 offset = rng.startOffset;
bgneal@45 17224 container = rng.startContainer;
bgneal@45 17225 text = container.nodeValue;
bgneal@45 17226
bgneal@45 17227 caretContainer = getParentCaretContainer(selection.getStart());
bgneal@45 17228 if (caretContainer) {
bgneal@45 17229 textNode = findFirstTextNode(caretContainer);
bgneal@45 17230 }
bgneal@45 17231
bgneal@45 17232 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
bgneal@45 17233 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
bgneal@45 17234 // Get bookmark of caret position
bgneal@45 17235 bookmark = selection.getBookmark();
bgneal@45 17236
bgneal@45 17237 // Collapse bookmark range (WebKit)
bgneal@45 17238 rng.collapse(true);
bgneal@45 17239
bgneal@45 17240 // Expand the range to the closest word and split it at those points
bgneal@45 17241 rng = expandRng(rng, get(name));
bgneal@45 17242 rng = rangeUtils.split(rng);
bgneal@45 17243
bgneal@45 17244 // Apply the format to the range
bgneal@45 17245 apply(name, vars, rng);
bgneal@45 17246
bgneal@45 17247 // Move selection back to caret position
bgneal@45 17248 selection.moveToBookmark(bookmark);
bgneal@45 17249 } else {
bgneal@45 17250 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
bgneal@45 17251 caretContainer = createCaretContainer(true);
bgneal@45 17252 textNode = caretContainer.firstChild;
bgneal@45 17253
bgneal@45 17254 rng.insertNode(caretContainer);
bgneal@45 17255 offset = 1;
bgneal@45 17256
bgneal@45 17257 apply(name, vars, caretContainer);
bgneal@45 17258 } else {
bgneal@45 17259 apply(name, vars, caretContainer);
bgneal@45 17260 }
bgneal@45 17261
bgneal@45 17262 // Move selection to text node
bgneal@45 17263 selection.setCursorLocation(textNode, offset);
bgneal@45 17264 }
bgneal@45 17265 };
bgneal@45 17266
bgneal@45 17267 function removeCaretFormat() {
bgneal@45 17268 var rng = selection.getRng(true), container, offset, bookmark,
bgneal@45 17269 hasContentAfter, node, formatNode, parents = [], i, caretContainer;
bgneal@45 17270
bgneal@45 17271 container = rng.startContainer;
bgneal@45 17272 offset = rng.startOffset;
bgneal@45 17273 node = container;
bgneal@45 17274
bgneal@45 17275 if (container.nodeType == 3) {
bgneal@45 17276 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
bgneal@45 17277 hasContentAfter = true;
bgneal@45 17278 }
bgneal@45 17279
bgneal@45 17280 node = node.parentNode;
bgneal@45 17281 }
bgneal@45 17282
bgneal@45 17283 while (node) {
bgneal@45 17284 if (matchNode(node, name, vars)) {
bgneal@45 17285 formatNode = node;
bgneal@45 17286 break;
bgneal@45 17287 }
bgneal@45 17288
bgneal@45 17289 if (node.nextSibling) {
bgneal@45 17290 hasContentAfter = true;
bgneal@45 17291 }
bgneal@45 17292
bgneal@45 17293 parents.push(node);
bgneal@45 17294 node = node.parentNode;
bgneal@45 17295 }
bgneal@45 17296
bgneal@45 17297 // Node doesn't have the specified format
bgneal@45 17298 if (!formatNode) {
bgneal@45 17299 return;
bgneal@45 17300 }
bgneal@45 17301
bgneal@45 17302 // Is there contents after the caret then remove the format on the element
bgneal@45 17303 if (hasContentAfter) {
bgneal@45 17304 // Get bookmark of caret position
bgneal@45 17305 bookmark = selection.getBookmark();
bgneal@45 17306
bgneal@45 17307 // Collapse bookmark range (WebKit)
bgneal@45 17308 rng.collapse(true);
bgneal@45 17309
bgneal@45 17310 // Expand the range to the closest word and split it at those points
bgneal@45 17311 rng = expandRng(rng, get(name), true);
bgneal@45 17312 rng = rangeUtils.split(rng);
bgneal@45 17313
bgneal@45 17314 // Remove the format from the range
bgneal@45 17315 remove(name, vars, rng);
bgneal@45 17316
bgneal@45 17317 // Move selection back to caret position
bgneal@45 17318 selection.moveToBookmark(bookmark);
bgneal@45 17319 } else {
bgneal@45 17320 caretContainer = createCaretContainer();
bgneal@45 17321
bgneal@45 17322 node = caretContainer;
bgneal@45 17323 for (i = parents.length - 1; i >= 0; i--) {
bgneal@45 17324 node.appendChild(dom.clone(parents[i], false));
bgneal@45 17325 node = node.firstChild;
bgneal@45 17326 }
bgneal@45 17327
bgneal@45 17328 // Insert invisible character into inner most format element
bgneal@45 17329 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
bgneal@45 17330 node = node.firstChild;
bgneal@45 17331
bgneal@45 17332 // Insert caret container after the formated node
bgneal@45 17333 dom.insertAfter(caretContainer, formatNode);
bgneal@45 17334
bgneal@45 17335 // Move selection to text node
bgneal@45 17336 selection.setCursorLocation(node, 1);
bgneal@45 17337 }
bgneal@45 17338 };
bgneal@45 17339
bgneal@45 17340 // Only bind the caret events once
bgneal@45 17341 if (!self._hasCaretEvents) {
bgneal@45 17342 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
bgneal@45 17343 ed.onBeforeGetContent.addToTop(function() {
bgneal@45 17344 var nodes = [], i;
bgneal@45 17345
bgneal@45 17346 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
bgneal@45 17347 // Mark children
bgneal@45 17348 i = nodes.length;
bgneal@45 17349 while (i--) {
bgneal@45 17350 dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
bgneal@45 17351 }
bgneal@45 17352 }
bgneal@45 17353 });
bgneal@45 17354
bgneal@45 17355 // Remove caret container on mouse up and on key up
bgneal@45 17356 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
bgneal@45 17357 ed[name].addToTop(function() {
bgneal@45 17358 removeCaretContainer();
bgneal@45 17359 });
bgneal@45 17360 });
bgneal@45 17361
bgneal@45 17362 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
bgneal@45 17363 ed.onKeyDown.addToTop(function(ed, e) {
bgneal@45 17364 var keyCode = e.keyCode;
bgneal@45 17365
bgneal@45 17366 if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
bgneal@45 17367 removeCaretContainer(getParentCaretContainer(selection.getStart()));
bgneal@45 17368 }
bgneal@45 17369 });
bgneal@45 17370
bgneal@45 17371 self._hasCaretEvents = true;
bgneal@45 17372 }
bgneal@45 17373
bgneal@45 17374 // Do apply or remove caret format
bgneal@45 17375 if (type == "apply") {
bgneal@45 17376 applyCaretFormat();
bgneal@45 17377 } else {
bgneal@45 17378 removeCaretFormat();
bgneal@45 17379 }
bgneal@45 17380 };
bgneal@45 17381
bgneal@45 17382 function moveStart(rng) {
bgneal@45 17383 var container = rng.startContainer,
bgneal@45 17384 offset = rng.startOffset,
bgneal@45 17385 walker, node, nodes, tmpNode;
bgneal@45 17386
bgneal@45 17387 // Convert text node into index if possible
bgneal@45 17388 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
bgneal@45 17389 container = container.parentNode;
bgneal@45 17390 offset = nodeIndex(container) + 1;
bgneal@45 17391 }
bgneal@45 17392
bgneal@45 17393 // Move startContainer/startOffset in to a suitable node
bgneal@45 17394 if (container.nodeType == 1) {
bgneal@45 17395 nodes = container.childNodes;
bgneal@45 17396 container = nodes[Math.min(offset, nodes.length - 1)];
bgneal@45 17397 walker = new TreeWalker(container);
bgneal@45 17398
bgneal@45 17399 // If offset is at end of the parent node walk to the next one
bgneal@45 17400 if (offset > nodes.length - 1)
bgneal@45 17401 walker.next();
bgneal@45 17402
bgneal@45 17403 for (node = walker.current(); node; node = walker.next()) {
bgneal@45 17404 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@45 17405 // IE has a "neat" feature where it moves the start node into the closest element
bgneal@45 17406 // we can avoid this by inserting an element before it and then remove it after we set the selection
bgneal@45 17407 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
bgneal@45 17408 node.parentNode.insertBefore(tmpNode, node);
bgneal@45 17409
bgneal@45 17410 // Set selection and remove tmpNode
bgneal@45 17411 rng.setStart(node, 0);
bgneal@45 17412 selection.setRng(rng);
bgneal@45 17413 dom.remove(tmpNode);
bgneal@45 17414
bgneal@45 17415 return;
bgneal@45 17416 }
bgneal@45 17417 }
bgneal@45 17418 }
bgneal@45 17419 };
bgneal@45 17420
bgneal@45 17421 };
bgneal@45 17422 })(tinymce);
bgneal@45 17423
bgneal@45 17424 tinymce.onAddEditor.add(function(tinymce, ed) {
bgneal@45 17425 var filters, fontSizes, dom, settings = ed.settings;
bgneal@45 17426
bgneal@45 17427 if (settings.inline_styles) {
bgneal@45 17428 fontSizes = tinymce.explode(settings.font_size_legacy_values);
bgneal@45 17429
bgneal@45 17430 function replaceWithSpan(node, styles) {
bgneal@45 17431 tinymce.each(styles, function(value, name) {
bgneal@45 17432 if (value)
bgneal@45 17433 dom.setStyle(node, name, value);
bgneal@45 17434 });
bgneal@45 17435
bgneal@45 17436 dom.rename(node, 'span');
bgneal@45 17437 };
bgneal@45 17438
bgneal@45 17439 filters = {
bgneal@45 17440 font : function(dom, node) {
bgneal@45 17441 replaceWithSpan(node, {
bgneal@45 17442 backgroundColor : node.style.backgroundColor,
bgneal@45 17443 color : node.color,
bgneal@45 17444 fontFamily : node.face,
bgneal@45 17445 fontSize : fontSizes[parseInt(node.size) - 1]
bgneal@45 17446 });
bgneal@45 17447 },
bgneal@45 17448
bgneal@45 17449 u : function(dom, node) {
bgneal@45 17450 replaceWithSpan(node, {
bgneal@45 17451 textDecoration : 'underline'
bgneal@45 17452 });
bgneal@45 17453 },
bgneal@45 17454
bgneal@45 17455 strike : function(dom, node) {
bgneal@45 17456 replaceWithSpan(node, {
bgneal@45 17457 textDecoration : 'line-through'
bgneal@45 17458 });
bgneal@45 17459 }
bgneal@45 17460 };
bgneal@45 17461
bgneal@45 17462 function convert(editor, params) {
bgneal@45 17463 dom = editor.dom;
bgneal@45 17464
bgneal@45 17465 if (settings.convert_fonts_to_spans) {
bgneal@45 17466 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
bgneal@45 17467 filters[node.nodeName.toLowerCase()](ed.dom, node);
bgneal@45 17468 });
bgneal@45 17469 }
bgneal@45 17470 };
bgneal@45 17471
bgneal@45 17472 ed.onPreProcess.add(convert);
bgneal@45 17473 ed.onSetContent.add(convert);
bgneal@45 17474
bgneal@45 17475 ed.onInit.add(function() {
bgneal@45 17476 ed.selection.onSetContent.add(convert);
bgneal@45 17477 });
bgneal@45 17478 }
bgneal@45 17479 });
bgneal@45 17480