annotate media/js/tiny_mce/tiny_mce_src.js @ 271:4746df47a538

Follow on to last rev (r292) for #126. Missed updating a shoutbox template. Also the repoze.timeago package uses UTC time by default. Change this to local time for now until we decide to switch over to UTC for everything.
author Brian Neal <bgneal@gmail.com>
date Sun, 26 Sep 2010 17:42:00 +0000
parents 6ed2932901fa
children
rev   line source
bgneal@183 1 (function(win) {
bgneal@183 2 var whiteSpaceRe = /^\s*|\s*$/g,
bgneal@183 3 undefined;
bgneal@183 4
bgneal@183 5 var tinymce = {
bgneal@183 6 majorVersion : '3',
bgneal@183 7
bgneal@247 8 minorVersion : '3.9',
bgneal@247 9
bgneal@247 10 releaseDate : '2010-09-08',
bgneal@183 11
bgneal@183 12 _init : function() {
bgneal@183 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
bgneal@183 14
bgneal@183 15 t.isOpera = win.opera && opera.buildNumber;
bgneal@183 16
bgneal@183 17 t.isWebKit = /WebKit/.test(ua);
bgneal@183 18
bgneal@183 19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
bgneal@183 20
bgneal@183 21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
bgneal@183 22
bgneal@183 23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
bgneal@183 24
bgneal@183 25 t.isMac = ua.indexOf('Mac') != -1;
bgneal@183 26
bgneal@183 27 t.isAir = /adobeair/i.test(ua);
bgneal@183 28
bgneal@217 29 t.isIDevice = /(iPad|iPhone)/.test(ua);
bgneal@217 30
bgneal@183 31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
bgneal@183 32 if (win.tinyMCEPreInit) {
bgneal@183 33 t.suffix = tinyMCEPreInit.suffix;
bgneal@183 34 t.baseURL = tinyMCEPreInit.base;
bgneal@183 35 t.query = tinyMCEPreInit.query;
bgneal@45 36 return;
bgneal@183 37 }
bgneal@183 38
bgneal@183 39 // Get suffix and base
bgneal@183 40 t.suffix = '';
bgneal@183 41
bgneal@183 42 // If base element found, add that infront of baseURL
bgneal@183 43 nl = d.getElementsByTagName('base');
bgneal@183 44 for (i=0; i<nl.length; i++) {
bgneal@183 45 if (v = nl[i].href) {
bgneal@183 46 // Host only value like http://site.com or http://site.com:8008
bgneal@183 47 if (/^https?:\/\/[^\/]+$/.test(v))
bgneal@183 48 v += '/';
bgneal@183 49
bgneal@183 50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
bgneal@183 51 }
bgneal@183 52 }
bgneal@183 53
bgneal@183 54 function getBase(n) {
bgneal@247 55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
bgneal@183 56 if (/_(src|dev)\.js/g.test(n.src))
bgneal@183 57 t.suffix = '_src';
bgneal@183 58
bgneal@183 59 if ((p = n.src.indexOf('?')) != -1)
bgneal@183 60 t.query = n.src.substring(p + 1);
bgneal@183 61
bgneal@183 62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
bgneal@183 63
bgneal@183 64 // If path to script is relative and a base href was found add that one infront
bgneal@183 65 // the src property will always be an absolute one on non IE browsers and IE 8
bgneal@183 66 // so this logic will basically only be executed on older IE versions
bgneal@183 67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
bgneal@183 68 t.baseURL = base + t.baseURL;
bgneal@183 69
bgneal@183 70 return t.baseURL;
bgneal@183 71 }
bgneal@183 72
bgneal@183 73 return null;
bgneal@183 74 };
bgneal@183 75
bgneal@183 76 // Check document
bgneal@183 77 nl = d.getElementsByTagName('script');
bgneal@45 78 for (i=0; i<nl.length; i++) {
bgneal@45 79 if (getBase(nl[i]))
bgneal@45 80 return;
bgneal@45 81 }
bgneal@183 82
bgneal@183 83 // Check head
bgneal@183 84 n = d.getElementsByTagName('head')[0];
bgneal@183 85 if (n) {
bgneal@183 86 nl = n.getElementsByTagName('script');
bgneal@183 87 for (i=0; i<nl.length; i++) {
bgneal@183 88 if (getBase(nl[i]))
bgneal@183 89 return;
bgneal@183 90 }
bgneal@183 91 }
bgneal@183 92
bgneal@183 93 return;
bgneal@183 94 },
bgneal@183 95
bgneal@183 96 is : function(o, t) {
bgneal@183 97 if (!t)
bgneal@183 98 return o !== undefined;
bgneal@183 99
bgneal@183 100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
bgneal@183 101 return true;
bgneal@183 102
bgneal@183 103 return typeof(o) == t;
bgneal@183 104 },
bgneal@183 105
bgneal@183 106 each : function(o, cb, s) {
bgneal@183 107 var n, l;
bgneal@183 108
bgneal@183 109 if (!o)
bgneal@183 110 return 0;
bgneal@183 111
bgneal@183 112 s = s || o;
bgneal@183 113
bgneal@183 114 if (o.length !== undefined) {
bgneal@183 115 // Indexed arrays, needed for Safari
bgneal@183 116 for (n=0, l = o.length; n < l; n++) {
bgneal@45 117 if (cb.call(s, o[n], n, o) === false)
bgneal@45 118 return 0;
bgneal@45 119 }
bgneal@45 120 } else {
bgneal@183 121 // Hashtables
bgneal@183 122 for (n in o) {
bgneal@183 123 if (o.hasOwnProperty(n)) {
bgneal@183 124 if (cb.call(s, o[n], n, o) === false)
bgneal@183 125 return 0;
bgneal@183 126 }
bgneal@183 127 }
bgneal@183 128 }
bgneal@183 129
bgneal@183 130 return 1;
bgneal@183 131 },
bgneal@183 132
bgneal@183 133
bgneal@183 134 map : function(a, f) {
bgneal@183 135 var o = [];
bgneal@183 136
bgneal@183 137 tinymce.each(a, function(v) {
bgneal@183 138 o.push(f(v));
bgneal@183 139 });
bgneal@183 140
bgneal@183 141 return o;
bgneal@183 142 },
bgneal@183 143
bgneal@183 144 grep : function(a, f) {
bgneal@183 145 var o = [];
bgneal@183 146
bgneal@183 147 tinymce.each(a, function(v) {
bgneal@183 148 if (!f || f(v))
bgneal@183 149 o.push(v);
bgneal@183 150 });
bgneal@183 151
bgneal@183 152 return o;
bgneal@183 153 },
bgneal@183 154
bgneal@183 155 inArray : function(a, v) {
bgneal@183 156 var i, l;
bgneal@183 157
bgneal@183 158 if (a) {
bgneal@183 159 for (i = 0, l = a.length; i < l; i++) {
bgneal@183 160 if (a[i] === v)
bgneal@183 161 return i;
bgneal@183 162 }
bgneal@183 163 }
bgneal@183 164
bgneal@183 165 return -1;
bgneal@183 166 },
bgneal@183 167
bgneal@183 168 extend : function(o, e) {
bgneal@183 169 var i, l, a = arguments;
bgneal@183 170
bgneal@183 171 for (i = 1, l = a.length; i < l; i++) {
bgneal@183 172 e = a[i];
bgneal@183 173
bgneal@183 174 tinymce.each(e, function(v, n) {
bgneal@183 175 if (v !== undefined)
bgneal@183 176 o[n] = v;
bgneal@183 177 });
bgneal@183 178 }
bgneal@183 179
bgneal@183 180 return o;
bgneal@183 181 },
bgneal@183 182
bgneal@183 183
bgneal@183 184 trim : function(s) {
bgneal@183 185 return (s ? '' + s : '').replace(whiteSpaceRe, '');
bgneal@183 186 },
bgneal@183 187
bgneal@183 188 create : function(s, p) {
bgneal@183 189 var t = this, sp, ns, cn, scn, c, de = 0;
bgneal@183 190
bgneal@183 191 // Parse : <prefix> <class>:<super class>
bgneal@183 192 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
bgneal@183 193 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
bgneal@183 194
bgneal@183 195 // Create namespace for new class
bgneal@183 196 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
bgneal@183 197
bgneal@183 198 // Class already exists
bgneal@183 199 if (ns[cn])
bgneal@183 200 return;
bgneal@183 201
bgneal@183 202 // Make pure static class
bgneal@183 203 if (s[2] == 'static') {
bgneal@183 204 ns[cn] = p;
bgneal@183 205
bgneal@183 206 if (this.onCreate)
bgneal@183 207 this.onCreate(s[2], s[3], ns[cn]);
bgneal@183 208
bgneal@183 209 return;
bgneal@183 210 }
bgneal@183 211
bgneal@183 212 // Create default constructor
bgneal@183 213 if (!p[cn]) {
bgneal@183 214 p[cn] = function() {};
bgneal@183 215 de = 1;
bgneal@183 216 }
bgneal@183 217
bgneal@183 218 // Add constructor and methods
bgneal@183 219 ns[cn] = p[cn];
bgneal@183 220 t.extend(ns[cn].prototype, p);
bgneal@183 221
bgneal@183 222 // Extend
bgneal@183 223 if (s[5]) {
bgneal@183 224 sp = t.resolve(s[5]).prototype;
bgneal@183 225 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
bgneal@183 226
bgneal@183 227 // Extend constructor
bgneal@183 228 c = ns[cn];
bgneal@183 229 if (de) {
bgneal@183 230 // Add passthrough constructor
bgneal@183 231 ns[cn] = function() {
bgneal@183 232 return sp[scn].apply(this, arguments);
bgneal@45 233 };
bgneal@45 234 } else {
bgneal@183 235 // Add inherit constructor
bgneal@183 236 ns[cn] = function() {
bgneal@183 237 this.parent = sp[scn];
bgneal@183 238 return c.apply(this, arguments);
bgneal@183 239 };
bgneal@183 240 }
bgneal@183 241 ns[cn].prototype[cn] = ns[cn];
bgneal@183 242
bgneal@183 243 // Add super methods
bgneal@183 244 t.each(sp, function(f, n) {
bgneal@183 245 ns[cn].prototype[n] = sp[n];
bgneal@183 246 });
bgneal@183 247
bgneal@183 248 // Add overridden methods
bgneal@183 249 t.each(p, function(f, n) {
bgneal@183 250 // Extend methods if needed
bgneal@183 251 if (sp[n]) {
bgneal@183 252 ns[cn].prototype[n] = function() {
bgneal@183 253 this.parent = sp[n];
bgneal@183 254 return f.apply(this, arguments);
bgneal@183 255 };
bgneal@183 256 } else {
bgneal@183 257 if (n != cn)
bgneal@183 258 ns[cn].prototype[n] = f;
bgneal@183 259 }
bgneal@183 260 });
bgneal@183 261 }
bgneal@183 262
bgneal@183 263 // Add static methods
bgneal@183 264 t.each(p['static'], function(f, n) {
bgneal@183 265 ns[cn][n] = f;
bgneal@183 266 });
bgneal@183 267
bgneal@183 268 if (this.onCreate)
bgneal@183 269 this.onCreate(s[2], s[3], ns[cn].prototype);
bgneal@183 270 },
bgneal@183 271
bgneal@183 272 walk : function(o, f, n, s) {
bgneal@183 273 s = s || this;
bgneal@183 274
bgneal@183 275 if (o) {
bgneal@183 276 if (n)
bgneal@183 277 o = o[n];
bgneal@183 278
bgneal@183 279 tinymce.each(o, function(o, i) {
bgneal@183 280 if (f.call(s, o, i, n) === false)
bgneal@183 281 return false;
bgneal@183 282
bgneal@183 283 tinymce.walk(o, f, n, s);
bgneal@183 284 });
bgneal@183 285 }
bgneal@183 286 },
bgneal@183 287
bgneal@183 288 createNS : function(n, o) {
bgneal@183 289 var i, v;
bgneal@183 290
bgneal@183 291 o = o || win;
bgneal@183 292
bgneal@183 293 n = n.split('.');
bgneal@183 294 for (i=0; i<n.length; i++) {
bgneal@183 295 v = n[i];
bgneal@183 296
bgneal@183 297 if (!o[v])
bgneal@183 298 o[v] = {};
bgneal@183 299
bgneal@183 300 o = o[v];
bgneal@183 301 }
bgneal@183 302
bgneal@183 303 return o;
bgneal@183 304 },
bgneal@183 305
bgneal@183 306 resolve : function(n, o) {
bgneal@183 307 var i, l;
bgneal@183 308
bgneal@183 309 o = o || win;
bgneal@183 310
bgneal@183 311 n = n.split('.');
bgneal@183 312 for (i = 0, l = n.length; i < l; i++) {
bgneal@183 313 o = o[n[i]];
bgneal@183 314
bgneal@183 315 if (!o)
bgneal@183 316 break;
bgneal@183 317 }
bgneal@183 318
bgneal@183 319 return o;
bgneal@183 320 },
bgneal@183 321
bgneal@183 322 addUnload : function(f, s) {
bgneal@183 323 var t = this;
bgneal@183 324
bgneal@183 325 f = {func : f, scope : s || this};
bgneal@183 326
bgneal@183 327 if (!t.unloads) {
bgneal@183 328 function unload() {
bgneal@183 329 var li = t.unloads, o, n;
bgneal@183 330
bgneal@183 331 if (li) {
bgneal@183 332 // Call unload handlers
bgneal@183 333 for (n in li) {
bgneal@183 334 o = li[n];
bgneal@183 335
bgneal@183 336 if (o && o.func)
bgneal@183 337 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
bgneal@183 338 }
bgneal@183 339
bgneal@183 340 // Detach unload function
bgneal@183 341 if (win.detachEvent) {
bgneal@183 342 win.detachEvent('onbeforeunload', fakeUnload);
bgneal@183 343 win.detachEvent('onunload', unload);
bgneal@183 344 } else if (win.removeEventListener)
bgneal@183 345 win.removeEventListener('unload', unload, false);
bgneal@183 346
bgneal@183 347 // Destroy references
bgneal@183 348 t.unloads = o = li = w = unload = 0;
bgneal@183 349
bgneal@183 350 // Run garbarge collector on IE
bgneal@183 351 if (win.CollectGarbage)
bgneal@183 352 CollectGarbage();
bgneal@183 353 }
bgneal@183 354 };
bgneal@183 355
bgneal@183 356 function fakeUnload() {
bgneal@183 357 var d = document;
bgneal@183 358
bgneal@183 359 // Is there things still loading, then do some magic
bgneal@183 360 if (d.readyState == 'interactive') {
bgneal@183 361 function stop() {
bgneal@183 362 // Prevent memory leak
bgneal@183 363 d.detachEvent('onstop', stop);
bgneal@183 364
bgneal@183 365 // Call unload handler
bgneal@183 366 if (unload)
bgneal@183 367 unload();
bgneal@183 368
bgneal@183 369 d = 0;
bgneal@183 370 };
bgneal@183 371
bgneal@183 372 // Fire unload when the currently loading page is stopped
bgneal@183 373 if (d)
bgneal@183 374 d.attachEvent('onstop', stop);
bgneal@183 375
bgneal@183 376 // Remove onstop listener after a while to prevent the unload function
bgneal@183 377 // to execute if the user presses cancel in an onbeforeunload
bgneal@183 378 // confirm dialog and then presses the browser stop button
bgneal@183 379 win.setTimeout(function() {
bgneal@183 380 if (d)
bgneal@183 381 d.detachEvent('onstop', stop);
bgneal@183 382 }, 0);
bgneal@183 383 }
bgneal@183 384 };
bgneal@183 385
bgneal@183 386 // Attach unload handler
bgneal@183 387 if (win.attachEvent) {
bgneal@183 388 win.attachEvent('onunload', unload);
bgneal@183 389 win.attachEvent('onbeforeunload', fakeUnload);
bgneal@183 390 } else if (win.addEventListener)
bgneal@183 391 win.addEventListener('unload', unload, false);
bgneal@183 392
bgneal@183 393 // Setup initial unload handler array
bgneal@183 394 t.unloads = [f];
bgneal@183 395 } else
bgneal@183 396 t.unloads.push(f);
bgneal@183 397
bgneal@183 398 return f;
bgneal@183 399 },
bgneal@183 400
bgneal@183 401 removeUnload : function(f) {
bgneal@183 402 var u = this.unloads, r = null;
bgneal@183 403
bgneal@183 404 tinymce.each(u, function(o, i) {
bgneal@183 405 if (o && o.func == f) {
bgneal@183 406 u.splice(i, 1);
bgneal@183 407 r = f;
bgneal@183 408 return false;
bgneal@183 409 }
bgneal@183 410 });
bgneal@183 411
bgneal@183 412 return r;
bgneal@183 413 },
bgneal@183 414
bgneal@183 415 explode : function(s, d) {
bgneal@183 416 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
bgneal@183 417 },
bgneal@183 418
bgneal@183 419 _addVer : function(u) {
bgneal@183 420 var v;
bgneal@183 421
bgneal@183 422 if (!this.query)
bgneal@183 423 return u;
bgneal@183 424
bgneal@183 425 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
bgneal@183 426
bgneal@183 427 if (u.indexOf('#') == -1)
bgneal@183 428 return u + v;
bgneal@183 429
bgneal@183 430 return u.replace('#', v + '#');
bgneal@45 431 }
bgneal@45 432
bgneal@183 433 };
bgneal@183 434
bgneal@183 435 // Initialize the API
bgneal@183 436 tinymce._init();
bgneal@183 437
bgneal@183 438 // Expose tinymce namespace to the global namespace (window)
bgneal@183 439 win.tinymce = win.tinyMCE = tinymce;
bgneal@183 440 })(window);
bgneal@183 441
bgneal@183 442
bgneal@45 443 tinymce.create('tinymce.util.Dispatcher', {
bgneal@45 444 scope : null,
bgneal@45 445 listeners : null,
bgneal@45 446
bgneal@45 447 Dispatcher : function(s) {
bgneal@45 448 this.scope = s || this;
bgneal@45 449 this.listeners = [];
bgneal@45 450 },
bgneal@45 451
bgneal@45 452 add : function(cb, s) {
bgneal@45 453 this.listeners.push({cb : cb, scope : s || this.scope});
bgneal@45 454
bgneal@45 455 return cb;
bgneal@45 456 },
bgneal@45 457
bgneal@45 458 addToTop : function(cb, s) {
bgneal@45 459 this.listeners.unshift({cb : cb, scope : s || this.scope});
bgneal@45 460
bgneal@45 461 return cb;
bgneal@45 462 },
bgneal@45 463
bgneal@45 464 remove : function(cb) {
bgneal@45 465 var l = this.listeners, o = null;
bgneal@45 466
bgneal@45 467 tinymce.each(l, function(c, i) {
bgneal@45 468 if (cb == c.cb) {
bgneal@45 469 o = cb;
bgneal@45 470 l.splice(i, 1);
bgneal@45 471 return false;
bgneal@45 472 }
bgneal@45 473 });
bgneal@45 474
bgneal@45 475 return o;
bgneal@45 476 },
bgneal@45 477
bgneal@45 478 dispatch : function() {
bgneal@45 479 var s, a = arguments, i, li = this.listeners, c;
bgneal@45 480
bgneal@45 481 // Needs to be a real loop since the listener count might change while looping
bgneal@45 482 // And this is also more efficient
bgneal@45 483 for (i = 0; i<li.length; i++) {
bgneal@45 484 c = li[i];
bgneal@45 485 s = c.cb.apply(c.scope, a);
bgneal@45 486
bgneal@45 487 if (s === false)
bgneal@45 488 break;
bgneal@45 489 }
bgneal@45 490
bgneal@45 491 return s;
bgneal@45 492 }
bgneal@45 493
bgneal@45 494 });
bgneal@183 495
bgneal@45 496 (function() {
bgneal@45 497 var each = tinymce.each;
bgneal@45 498
bgneal@45 499 tinymce.create('tinymce.util.URI', {
bgneal@45 500 URI : function(u, s) {
bgneal@45 501 var t = this, o, a, b;
bgneal@45 502
bgneal@183 503 // Trim whitespace
bgneal@183 504 u = tinymce.trim(u);
bgneal@183 505
bgneal@45 506 // Default settings
bgneal@45 507 s = t.settings = s || {};
bgneal@45 508
bgneal@45 509 // Strange app protocol or local anchor
bgneal@183 510 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
bgneal@45 511 t.source = u;
bgneal@45 512 return;
bgneal@45 513 }
bgneal@45 514
bgneal@45 515 // Absolute path with no host, fake host and protocol
bgneal@45 516 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
bgneal@45 517 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
bgneal@45 518
bgneal@183 519 // Relative path http:// or protocol relative //path
bgneal@183 520 if (!/^\w*:?\/\//.test(u))
bgneal@45 521 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
bgneal@45 522
bgneal@45 523 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
bgneal@45 524 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
bgneal@45 525 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
bgneal@45 526 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
bgneal@45 527 var s = u[i];
bgneal@45 528
bgneal@45 529 // Zope 3 workaround, they use @@something
bgneal@45 530 if (s)
bgneal@45 531 s = s.replace(/\(mce_at\)/g, '@@');
bgneal@45 532
bgneal@45 533 t[v] = s;
bgneal@45 534 });
bgneal@45 535
bgneal@45 536 if (b = s.base_uri) {
bgneal@45 537 if (!t.protocol)
bgneal@45 538 t.protocol = b.protocol;
bgneal@45 539
bgneal@45 540 if (!t.userInfo)
bgneal@45 541 t.userInfo = b.userInfo;
bgneal@45 542
bgneal@45 543 if (!t.port && t.host == 'mce_host')
bgneal@45 544 t.port = b.port;
bgneal@45 545
bgneal@45 546 if (!t.host || t.host == 'mce_host')
bgneal@45 547 t.host = b.host;
bgneal@45 548
bgneal@45 549 t.source = '';
bgneal@45 550 }
bgneal@45 551
bgneal@45 552 //t.path = t.path || '/';
bgneal@45 553 },
bgneal@45 554
bgneal@45 555 setPath : function(p) {
bgneal@45 556 var t = this;
bgneal@45 557
bgneal@45 558 p = /^(.*?)\/?(\w+)?$/.exec(p);
bgneal@45 559
bgneal@45 560 // Update path parts
bgneal@45 561 t.path = p[0];
bgneal@45 562 t.directory = p[1];
bgneal@45 563 t.file = p[2];
bgneal@45 564
bgneal@45 565 // Rebuild source
bgneal@45 566 t.source = '';
bgneal@45 567 t.getURI();
bgneal@45 568 },
bgneal@45 569
bgneal@45 570 toRelative : function(u) {
bgneal@45 571 var t = this, o;
bgneal@45 572
bgneal@45 573 if (u === "./")
bgneal@45 574 return u;
bgneal@45 575
bgneal@45 576 u = new tinymce.util.URI(u, {base_uri : t});
bgneal@45 577
bgneal@45 578 // Not on same domain/port or protocol
bgneal@45 579 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
bgneal@45 580 return u.getURI();
bgneal@45 581
bgneal@45 582 o = t.toRelPath(t.path, u.path);
bgneal@45 583
bgneal@45 584 // Add query
bgneal@45 585 if (u.query)
bgneal@45 586 o += '?' + u.query;
bgneal@45 587
bgneal@45 588 // Add anchor
bgneal@45 589 if (u.anchor)
bgneal@45 590 o += '#' + u.anchor;
bgneal@45 591
bgneal@45 592 return o;
bgneal@45 593 },
bgneal@45 594
bgneal@45 595 toAbsolute : function(u, nh) {
bgneal@45 596 var u = new tinymce.util.URI(u, {base_uri : this});
bgneal@45 597
bgneal@183 598 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
bgneal@45 599 },
bgneal@45 600
bgneal@45 601 toRelPath : function(base, path) {
bgneal@45 602 var items, bp = 0, out = '', i, l;
bgneal@45 603
bgneal@45 604 // Split the paths
bgneal@45 605 base = base.substring(0, base.lastIndexOf('/'));
bgneal@45 606 base = base.split('/');
bgneal@45 607 items = path.split('/');
bgneal@45 608
bgneal@45 609 if (base.length >= items.length) {
bgneal@45 610 for (i = 0, l = base.length; i < l; i++) {
bgneal@45 611 if (i >= items.length || base[i] != items[i]) {
bgneal@45 612 bp = i + 1;
bgneal@45 613 break;
bgneal@45 614 }
bgneal@45 615 }
bgneal@45 616 }
bgneal@45 617
bgneal@45 618 if (base.length < items.length) {
bgneal@45 619 for (i = 0, l = items.length; i < l; i++) {
bgneal@45 620 if (i >= base.length || base[i] != items[i]) {
bgneal@45 621 bp = i + 1;
bgneal@45 622 break;
bgneal@45 623 }
bgneal@45 624 }
bgneal@45 625 }
bgneal@45 626
bgneal@45 627 if (bp == 1)
bgneal@45 628 return path;
bgneal@45 629
bgneal@45 630 for (i = 0, l = base.length - (bp - 1); i < l; i++)
bgneal@45 631 out += "../";
bgneal@45 632
bgneal@45 633 for (i = bp - 1, l = items.length; i < l; i++) {
bgneal@45 634 if (i != bp - 1)
bgneal@45 635 out += "/" + items[i];
bgneal@45 636 else
bgneal@45 637 out += items[i];
bgneal@45 638 }
bgneal@45 639
bgneal@45 640 return out;
bgneal@45 641 },
bgneal@45 642
bgneal@45 643 toAbsPath : function(base, path) {
bgneal@183 644 var i, nb = 0, o = [], tr, outPath;
bgneal@45 645
bgneal@45 646 // Split paths
bgneal@45 647 tr = /\/$/.test(path) ? '/' : '';
bgneal@45 648 base = base.split('/');
bgneal@45 649 path = path.split('/');
bgneal@45 650
bgneal@45 651 // Remove empty chunks
bgneal@45 652 each(base, function(k) {
bgneal@45 653 if (k)
bgneal@45 654 o.push(k);
bgneal@45 655 });
bgneal@45 656
bgneal@45 657 base = o;
bgneal@45 658
bgneal@45 659 // Merge relURLParts chunks
bgneal@45 660 for (i = path.length - 1, o = []; i >= 0; i--) {
bgneal@45 661 // Ignore empty or .
bgneal@45 662 if (path[i].length == 0 || path[i] == ".")
bgneal@45 663 continue;
bgneal@45 664
bgneal@45 665 // Is parent
bgneal@45 666 if (path[i] == '..') {
bgneal@45 667 nb++;
bgneal@45 668 continue;
bgneal@45 669 }
bgneal@45 670
bgneal@45 671 // Move up
bgneal@45 672 if (nb > 0) {
bgneal@45 673 nb--;
bgneal@45 674 continue;
bgneal@45 675 }
bgneal@45 676
bgneal@45 677 o.push(path[i]);
bgneal@45 678 }
bgneal@45 679
bgneal@45 680 i = base.length - nb;
bgneal@45 681
bgneal@45 682 // If /a/b/c or /
bgneal@45 683 if (i <= 0)
bgneal@183 684 outPath = o.reverse().join('/');
bgneal@183 685 else
bgneal@183 686 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
bgneal@183 687
bgneal@183 688 // Add front / if it's needed
bgneal@183 689 if (outPath.indexOf('/') !== 0)
bgneal@183 690 outPath = '/' + outPath;
bgneal@183 691
bgneal@183 692 // Add traling / if it's needed
bgneal@183 693 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
bgneal@183 694 outPath += tr;
bgneal@183 695
bgneal@183 696 return outPath;
bgneal@45 697 },
bgneal@45 698
bgneal@45 699 getURI : function(nh) {
bgneal@45 700 var s, t = this;
bgneal@45 701
bgneal@45 702 // Rebuild source
bgneal@45 703 if (!t.source || nh) {
bgneal@45 704 s = '';
bgneal@45 705
bgneal@45 706 if (!nh) {
bgneal@45 707 if (t.protocol)
bgneal@45 708 s += t.protocol + '://';
bgneal@45 709
bgneal@45 710 if (t.userInfo)
bgneal@45 711 s += t.userInfo + '@';
bgneal@45 712
bgneal@45 713 if (t.host)
bgneal@45 714 s += t.host;
bgneal@45 715
bgneal@45 716 if (t.port)
bgneal@45 717 s += ':' + t.port;
bgneal@45 718 }
bgneal@45 719
bgneal@45 720 if (t.path)
bgneal@45 721 s += t.path;
bgneal@45 722
bgneal@45 723 if (t.query)
bgneal@45 724 s += '?' + t.query;
bgneal@45 725
bgneal@45 726 if (t.anchor)
bgneal@45 727 s += '#' + t.anchor;
bgneal@45 728
bgneal@45 729 t.source = s;
bgneal@45 730 }
bgneal@45 731
bgneal@45 732 return t.source;
bgneal@45 733 }
bgneal@183 734 });
bgneal@45 735 })();
bgneal@183 736
bgneal@45 737 (function() {
bgneal@45 738 var each = tinymce.each;
bgneal@45 739
bgneal@45 740 tinymce.create('static tinymce.util.Cookie', {
bgneal@45 741 getHash : function(n) {
bgneal@45 742 var v = this.get(n), h;
bgneal@45 743
bgneal@45 744 if (v) {
bgneal@45 745 each(v.split('&'), function(v) {
bgneal@45 746 v = v.split('=');
bgneal@45 747 h = h || {};
bgneal@45 748 h[unescape(v[0])] = unescape(v[1]);
bgneal@45 749 });
bgneal@45 750 }
bgneal@45 751
bgneal@45 752 return h;
bgneal@45 753 },
bgneal@45 754
bgneal@45 755 setHash : function(n, v, e, p, d, s) {
bgneal@45 756 var o = '';
bgneal@45 757
bgneal@45 758 each(v, function(v, k) {
bgneal@45 759 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
bgneal@45 760 });
bgneal@45 761
bgneal@45 762 this.set(n, o, e, p, d, s);
bgneal@45 763 },
bgneal@45 764
bgneal@45 765 get : function(n) {
bgneal@45 766 var c = document.cookie, e, p = n + "=", b;
bgneal@45 767
bgneal@45 768 // Strict mode
bgneal@45 769 if (!c)
bgneal@45 770 return;
bgneal@45 771
bgneal@45 772 b = c.indexOf("; " + p);
bgneal@45 773
bgneal@45 774 if (b == -1) {
bgneal@45 775 b = c.indexOf(p);
bgneal@45 776
bgneal@45 777 if (b != 0)
bgneal@45 778 return null;
bgneal@45 779 } else
bgneal@45 780 b += 2;
bgneal@45 781
bgneal@45 782 e = c.indexOf(";", b);
bgneal@45 783
bgneal@45 784 if (e == -1)
bgneal@45 785 e = c.length;
bgneal@45 786
bgneal@45 787 return unescape(c.substring(b + p.length, e));
bgneal@45 788 },
bgneal@45 789
bgneal@45 790 set : function(n, v, e, p, d, s) {
bgneal@45 791 document.cookie = n + "=" + escape(v) +
bgneal@45 792 ((e) ? "; expires=" + e.toGMTString() : "") +
bgneal@45 793 ((p) ? "; path=" + escape(p) : "") +
bgneal@45 794 ((d) ? "; domain=" + d : "") +
bgneal@45 795 ((s) ? "; secure" : "");
bgneal@45 796 },
bgneal@45 797
bgneal@45 798 remove : function(n, p) {
bgneal@45 799 var d = new Date();
bgneal@45 800
bgneal@45 801 d.setTime(d.getTime() - 1000);
bgneal@45 802
bgneal@45 803 this.set(n, '', d, p, d);
bgneal@45 804 }
bgneal@183 805 });
bgneal@45 806 })();
bgneal@183 807
bgneal@45 808 tinymce.create('static tinymce.util.JSON', {
bgneal@45 809 serialize : function(o) {
bgneal@45 810 var i, v, s = tinymce.util.JSON.serialize, t;
bgneal@45 811
bgneal@45 812 if (o == null)
bgneal@45 813 return 'null';
bgneal@45 814
bgneal@45 815 t = typeof o;
bgneal@45 816
bgneal@45 817 if (t == 'string') {
bgneal@45 818 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
bgneal@45 819
bgneal@45 820 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
bgneal@45 821 i = v.indexOf(b);
bgneal@45 822
bgneal@45 823 if (i + 1)
bgneal@45 824 return '\\' + v.charAt(i + 1);
bgneal@45 825
bgneal@45 826 a = b.charCodeAt().toString(16);
bgneal@45 827
bgneal@45 828 return '\\u' + '0000'.substring(a.length) + a;
bgneal@45 829 }) + '"';
bgneal@45 830 }
bgneal@45 831
bgneal@45 832 if (t == 'object') {
bgneal@45 833 if (o.hasOwnProperty && o instanceof Array) {
bgneal@45 834 for (i=0, v = '['; i<o.length; i++)
bgneal@45 835 v += (i > 0 ? ',' : '') + s(o[i]);
bgneal@45 836
bgneal@45 837 return v + ']';
bgneal@45 838 }
bgneal@45 839
bgneal@45 840 v = '{';
bgneal@45 841
bgneal@45 842 for (i in o)
bgneal@45 843 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
bgneal@45 844
bgneal@45 845 return v + '}';
bgneal@45 846 }
bgneal@45 847
bgneal@45 848 return '' + o;
bgneal@45 849 },
bgneal@45 850
bgneal@45 851 parse : function(s) {
bgneal@45 852 try {
bgneal@45 853 return eval('(' + s + ')');
bgneal@45 854 } catch (ex) {
bgneal@45 855 // Ignore
bgneal@45 856 }
bgneal@45 857 }
bgneal@45 858
bgneal@45 859 });
bgneal@183 860
bgneal@45 861 tinymce.create('static tinymce.util.XHR', {
bgneal@45 862 send : function(o) {
bgneal@45 863 var x, t, w = window, c = 0;
bgneal@45 864
bgneal@45 865 // Default settings
bgneal@45 866 o.scope = o.scope || this;
bgneal@45 867 o.success_scope = o.success_scope || o.scope;
bgneal@45 868 o.error_scope = o.error_scope || o.scope;
bgneal@45 869 o.async = o.async === false ? false : true;
bgneal@45 870 o.data = o.data || '';
bgneal@45 871
bgneal@45 872 function get(s) {
bgneal@45 873 x = 0;
bgneal@45 874
bgneal@45 875 try {
bgneal@45 876 x = new ActiveXObject(s);
bgneal@45 877 } catch (ex) {
bgneal@45 878 }
bgneal@45 879
bgneal@45 880 return x;
bgneal@45 881 };
bgneal@45 882
bgneal@45 883 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
bgneal@45 884
bgneal@45 885 if (x) {
bgneal@45 886 if (x.overrideMimeType)
bgneal@45 887 x.overrideMimeType(o.content_type);
bgneal@45 888
bgneal@45 889 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
bgneal@45 890
bgneal@45 891 if (o.content_type)
bgneal@45 892 x.setRequestHeader('Content-Type', o.content_type);
bgneal@45 893
bgneal@183 894 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bgneal@183 895
bgneal@45 896 x.send(o.data);
bgneal@45 897
bgneal@45 898 function ready() {
bgneal@45 899 if (!o.async || x.readyState == 4 || c++ > 10000) {
bgneal@45 900 if (o.success && c < 10000 && x.status == 200)
bgneal@45 901 o.success.call(o.success_scope, '' + x.responseText, x, o);
bgneal@45 902 else if (o.error)
bgneal@45 903 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
bgneal@45 904
bgneal@45 905 x = null;
bgneal@45 906 } else
bgneal@45 907 w.setTimeout(ready, 10);
bgneal@45 908 };
bgneal@45 909
bgneal@45 910 // Syncronous request
bgneal@45 911 if (!o.async)
bgneal@45 912 return ready();
bgneal@45 913
bgneal@45 914 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
bgneal@45 915 t = w.setTimeout(ready, 10);
bgneal@45 916 }
bgneal@183 917 }
bgneal@45 918 });
bgneal@183 919
bgneal@45 920 (function() {
bgneal@45 921 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
bgneal@45 922
bgneal@45 923 tinymce.create('tinymce.util.JSONRequest', {
bgneal@45 924 JSONRequest : function(s) {
bgneal@45 925 this.settings = extend({
bgneal@45 926 }, s);
bgneal@45 927 this.count = 0;
bgneal@45 928 },
bgneal@45 929
bgneal@45 930 send : function(o) {
bgneal@45 931 var ecb = o.error, scb = o.success;
bgneal@45 932
bgneal@45 933 o = extend(this.settings, o);
bgneal@45 934
bgneal@45 935 o.success = function(c, x) {
bgneal@45 936 c = JSON.parse(c);
bgneal@45 937
bgneal@45 938 if (typeof(c) == 'undefined') {
bgneal@45 939 c = {
bgneal@45 940 error : 'JSON Parse error.'
bgneal@45 941 };
bgneal@45 942 }
bgneal@45 943
bgneal@45 944 if (c.error)
bgneal@45 945 ecb.call(o.error_scope || o.scope, c.error, x);
bgneal@45 946 else
bgneal@45 947 scb.call(o.success_scope || o.scope, c.result);
bgneal@45 948 };
bgneal@45 949
bgneal@45 950 o.error = function(ty, x) {
bgneal@45 951 ecb.call(o.error_scope || o.scope, ty, x);
bgneal@45 952 };
bgneal@45 953
bgneal@45 954 o.data = JSON.serialize({
bgneal@45 955 id : o.id || 'c' + (this.count++),
bgneal@45 956 method : o.method,
bgneal@45 957 params : o.params
bgneal@45 958 });
bgneal@45 959
bgneal@45 960 // JSON content type for Ruby on rails. Bug: #1883287
bgneal@45 961 o.content_type = 'application/json';
bgneal@45 962
bgneal@45 963 XHR.send(o);
bgneal@45 964 },
bgneal@45 965
bgneal@45 966 'static' : {
bgneal@45 967 sendRPC : function(o) {
bgneal@45 968 return new tinymce.util.JSONRequest().send(o);
bgneal@45 969 }
bgneal@45 970 }
bgneal@183 971 });
bgneal@183 972 }());
bgneal@183 973 (function(tinymce) {
bgneal@45 974 // Shorten names
bgneal@183 975 var each = tinymce.each,
bgneal@183 976 is = tinymce.is,
bgneal@183 977 isWebKit = tinymce.isWebKit,
bgneal@183 978 isIE = tinymce.isIE,
bgneal@183 979 blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,
bgneal@183 980 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
bgneal@183 981 mceAttribs = makeMap('src,href,style,coords,shape'),
bgneal@183 982 encodedChars = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'},
bgneal@183 983 encodeCharsRe = /[<>&\"]/g,
bgneal@183 984 simpleSelectorRe = /^([a-z0-9],?)+$/i,
bgneal@183 985 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
bgneal@183 986 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
bgneal@183 987
bgneal@183 988 function makeMap(str) {
bgneal@183 989 var map = {}, i;
bgneal@183 990
bgneal@183 991 str = str.split(',');
bgneal@183 992 for (i = str.length; i >= 0; i--)
bgneal@183 993 map[str[i]] = 1;
bgneal@183 994
bgneal@183 995 return map;
bgneal@183 996 };
bgneal@45 997
bgneal@45 998 tinymce.create('tinymce.dom.DOMUtils', {
bgneal@45 999 doc : null,
bgneal@45 1000 root : null,
bgneal@45 1001 files : null,
bgneal@45 1002 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
bgneal@45 1003 props : {
bgneal@45 1004 "for" : "htmlFor",
bgneal@45 1005 "class" : "className",
bgneal@45 1006 className : "className",
bgneal@45 1007 checked : "checked",
bgneal@45 1008 disabled : "disabled",
bgneal@45 1009 maxlength : "maxLength",
bgneal@45 1010 readonly : "readOnly",
bgneal@45 1011 selected : "selected",
bgneal@45 1012 value : "value",
bgneal@45 1013 id : "id",
bgneal@45 1014 name : "name",
bgneal@45 1015 type : "type"
bgneal@45 1016 },
bgneal@45 1017
bgneal@45 1018 DOMUtils : function(d, s) {
bgneal@183 1019 var t = this, globalStyle;
bgneal@45 1020
bgneal@45 1021 t.doc = d;
bgneal@45 1022 t.win = window;
bgneal@45 1023 t.files = {};
bgneal@45 1024 t.cssFlicker = false;
bgneal@45 1025 t.counter = 0;
bgneal@45 1026 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
bgneal@45 1027 t.stdMode = d.documentMode === 8;
bgneal@45 1028
bgneal@183 1029 t.settings = s = tinymce.extend({
bgneal@45 1030 keep_values : false,
bgneal@45 1031 hex_colors : 1,
bgneal@45 1032 process_html : 1
bgneal@45 1033 }, s);
bgneal@45 1034
bgneal@45 1035 // Fix IE6SP2 flicker and check it failed for pre SP2
bgneal@45 1036 if (tinymce.isIE6) {
bgneal@45 1037 try {
bgneal@45 1038 d.execCommand('BackgroundImageCache', false, true);
bgneal@45 1039 } catch (e) {
bgneal@45 1040 t.cssFlicker = true;
bgneal@45 1041 }
bgneal@45 1042 }
bgneal@45 1043
bgneal@183 1044 // Build styles list
bgneal@183 1045 if (s.valid_styles) {
bgneal@183 1046 t._styles = {};
bgneal@183 1047
bgneal@183 1048 // Convert styles into a rule list
bgneal@183 1049 each(s.valid_styles, function(value, key) {
bgneal@183 1050 t._styles[key] = tinymce.explode(value);
bgneal@183 1051 });
bgneal@183 1052 }
bgneal@183 1053
bgneal@45 1054 tinymce.addUnload(t.destroy, t);
bgneal@45 1055 },
bgneal@45 1056
bgneal@45 1057 getRoot : function() {
bgneal@45 1058 var t = this, s = t.settings;
bgneal@45 1059
bgneal@45 1060 return (s && t.get(s.root_element)) || t.doc.body;
bgneal@45 1061 },
bgneal@45 1062
bgneal@45 1063 getViewPort : function(w) {
bgneal@45 1064 var d, b;
bgneal@45 1065
bgneal@45 1066 w = !w ? this.win : w;
bgneal@45 1067 d = w.document;
bgneal@45 1068 b = this.boxModel ? d.documentElement : d.body;
bgneal@45 1069
bgneal@45 1070 // Returns viewport size excluding scrollbars
bgneal@45 1071 return {
bgneal@45 1072 x : w.pageXOffset || b.scrollLeft,
bgneal@45 1073 y : w.pageYOffset || b.scrollTop,
bgneal@45 1074 w : w.innerWidth || b.clientWidth,
bgneal@45 1075 h : w.innerHeight || b.clientHeight
bgneal@45 1076 };
bgneal@45 1077 },
bgneal@45 1078
bgneal@45 1079 getRect : function(e) {
bgneal@45 1080 var p, t = this, sr;
bgneal@45 1081
bgneal@45 1082 e = t.get(e);
bgneal@45 1083 p = t.getPos(e);
bgneal@45 1084 sr = t.getSize(e);
bgneal@45 1085
bgneal@45 1086 return {
bgneal@45 1087 x : p.x,
bgneal@45 1088 y : p.y,
bgneal@45 1089 w : sr.w,
bgneal@45 1090 h : sr.h
bgneal@45 1091 };
bgneal@45 1092 },
bgneal@45 1093
bgneal@45 1094 getSize : function(e) {
bgneal@45 1095 var t = this, w, h;
bgneal@45 1096
bgneal@45 1097 e = t.get(e);
bgneal@45 1098 w = t.getStyle(e, 'width');
bgneal@45 1099 h = t.getStyle(e, 'height');
bgneal@45 1100
bgneal@45 1101 // Non pixel value, then force offset/clientWidth
bgneal@45 1102 if (w.indexOf('px') === -1)
bgneal@45 1103 w = 0;
bgneal@45 1104
bgneal@45 1105 // Non pixel value, then force offset/clientWidth
bgneal@45 1106 if (h.indexOf('px') === -1)
bgneal@45 1107 h = 0;
bgneal@45 1108
bgneal@45 1109 return {
bgneal@45 1110 w : parseInt(w) || e.offsetWidth || e.clientWidth,
bgneal@45 1111 h : parseInt(h) || e.offsetHeight || e.clientHeight
bgneal@45 1112 };
bgneal@45 1113 },
bgneal@45 1114
bgneal@45 1115 getParent : function(n, f, r) {
bgneal@45 1116 return this.getParents(n, f, r, false);
bgneal@45 1117 },
bgneal@45 1118
bgneal@45 1119 getParents : function(n, f, r, c) {
bgneal@45 1120 var t = this, na, se = t.settings, o = [];
bgneal@45 1121
bgneal@45 1122 n = t.get(n);
bgneal@45 1123 c = c === undefined;
bgneal@45 1124
bgneal@45 1125 if (se.strict_root)
bgneal@45 1126 r = r || t.getRoot();
bgneal@45 1127
bgneal@45 1128 // Wrap node name as func
bgneal@45 1129 if (is(f, 'string')) {
bgneal@45 1130 na = f;
bgneal@45 1131
bgneal@45 1132 if (f === '*') {
bgneal@45 1133 f = function(n) {return n.nodeType == 1;};
bgneal@45 1134 } else {
bgneal@45 1135 f = function(n) {
bgneal@45 1136 return t.is(n, na);
bgneal@45 1137 };
bgneal@45 1138 }
bgneal@45 1139 }
bgneal@45 1140
bgneal@45 1141 while (n) {
bgneal@183 1142 if (n == r || !n.nodeType || n.nodeType === 9)
bgneal@45 1143 break;
bgneal@45 1144
bgneal@45 1145 if (!f || f(n)) {
bgneal@45 1146 if (c)
bgneal@45 1147 o.push(n);
bgneal@45 1148 else
bgneal@45 1149 return n;
bgneal@45 1150 }
bgneal@45 1151
bgneal@45 1152 n = n.parentNode;
bgneal@45 1153 }
bgneal@45 1154
bgneal@45 1155 return c ? o : null;
bgneal@45 1156 },
bgneal@45 1157
bgneal@45 1158 get : function(e) {
bgneal@45 1159 var n;
bgneal@45 1160
bgneal@45 1161 if (e && this.doc && typeof(e) == 'string') {
bgneal@45 1162 n = e;
bgneal@45 1163 e = this.doc.getElementById(e);
bgneal@45 1164
bgneal@45 1165 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
bgneal@45 1166 if (e && e.id !== n)
bgneal@45 1167 return this.doc.getElementsByName(n)[1];
bgneal@45 1168 }
bgneal@45 1169
bgneal@45 1170 return e;
bgneal@45 1171 },
bgneal@45 1172
bgneal@183 1173 getNext : function(node, selector) {
bgneal@183 1174 return this._findSib(node, selector, 'nextSibling');
bgneal@183 1175 },
bgneal@183 1176
bgneal@183 1177 getPrev : function(node, selector) {
bgneal@183 1178 return this._findSib(node, selector, 'previousSibling');
bgneal@183 1179 },
bgneal@183 1180
bgneal@45 1181
bgneal@45 1182 select : function(pa, s) {
bgneal@45 1183 var t = this;
bgneal@45 1184
bgneal@45 1185 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
bgneal@45 1186 },
bgneal@45 1187
bgneal@183 1188 is : function(n, selector) {
bgneal@183 1189 var i;
bgneal@183 1190
bgneal@183 1191 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
bgneal@183 1192 if (n.length === undefined) {
bgneal@183 1193 // Simple all selector
bgneal@183 1194 if (selector === '*')
bgneal@183 1195 return n.nodeType == 1;
bgneal@183 1196
bgneal@183 1197 // Simple selector just elements
bgneal@183 1198 if (simpleSelectorRe.test(selector)) {
bgneal@183 1199 selector = selector.toLowerCase().split(/,/);
bgneal@183 1200 n = n.nodeName.toLowerCase();
bgneal@183 1201
bgneal@183 1202 for (i = selector.length - 1; i >= 0; i--) {
bgneal@183 1203 if (selector[i] == n)
bgneal@183 1204 return true;
bgneal@183 1205 }
bgneal@183 1206
bgneal@183 1207 return false;
bgneal@183 1208 }
bgneal@183 1209 }
bgneal@183 1210
bgneal@183 1211 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
bgneal@183 1212 },
bgneal@183 1213
bgneal@45 1214
bgneal@45 1215 add : function(p, n, a, h, c) {
bgneal@45 1216 var t = this;
bgneal@45 1217
bgneal@45 1218 return this.run(p, function(p) {
bgneal@45 1219 var e, k;
bgneal@45 1220
bgneal@45 1221 e = is(n, 'string') ? t.doc.createElement(n) : n;
bgneal@45 1222 t.setAttribs(e, a);
bgneal@45 1223
bgneal@45 1224 if (h) {
bgneal@45 1225 if (h.nodeType)
bgneal@45 1226 e.appendChild(h);
bgneal@45 1227 else
bgneal@45 1228 t.setHTML(e, h);
bgneal@45 1229 }
bgneal@45 1230
bgneal@45 1231 return !c ? p.appendChild(e) : e;
bgneal@45 1232 });
bgneal@45 1233 },
bgneal@45 1234
bgneal@45 1235 create : function(n, a, h) {
bgneal@45 1236 return this.add(this.doc.createElement(n), n, a, h, 1);
bgneal@45 1237 },
bgneal@45 1238
bgneal@45 1239 createHTML : function(n, a, h) {
bgneal@45 1240 var o = '', t = this, k;
bgneal@45 1241
bgneal@45 1242 o += '<' + n;
bgneal@45 1243
bgneal@45 1244 for (k in a) {
bgneal@45 1245 if (a.hasOwnProperty(k))
bgneal@45 1246 o += ' ' + k + '="' + t.encode(a[k]) + '"';
bgneal@45 1247 }
bgneal@45 1248
bgneal@45 1249 if (tinymce.is(h))
bgneal@45 1250 return o + '>' + h + '</' + n + '>';
bgneal@45 1251
bgneal@45 1252 return o + ' />';
bgneal@45 1253 },
bgneal@45 1254
bgneal@183 1255 remove : function(node, keep_children) {
bgneal@183 1256 return this.run(node, function(node) {
bgneal@183 1257 var parent, child;
bgneal@183 1258
bgneal@183 1259 parent = node.parentNode;
bgneal@183 1260
bgneal@183 1261 if (!parent)
bgneal@45 1262 return null;
bgneal@45 1263
bgneal@183 1264 if (keep_children) {
bgneal@183 1265 while (child = node.firstChild) {
bgneal@183 1266 // IE 8 will crash if you don't remove completely empty text nodes
bgneal@217 1267 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
bgneal@183 1268 parent.insertBefore(child, node);
bgneal@183 1269 else
bgneal@183 1270 node.removeChild(child);
bgneal@183 1271 }
bgneal@183 1272 }
bgneal@183 1273
bgneal@183 1274 return parent.removeChild(node);
bgneal@183 1275 });
bgneal@183 1276 },
bgneal@45 1277
bgneal@45 1278 setStyle : function(n, na, v) {
bgneal@45 1279 var t = this;
bgneal@45 1280
bgneal@45 1281 return t.run(n, function(e) {
bgneal@45 1282 var s, i;
bgneal@45 1283
bgneal@45 1284 s = e.style;
bgneal@45 1285
bgneal@45 1286 // Camelcase it, if needed
bgneal@45 1287 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 1288 return b.toUpperCase();
bgneal@45 1289 });
bgneal@45 1290
bgneal@45 1291 // Default px suffix on these
bgneal@45 1292 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
bgneal@45 1293 v += 'px';
bgneal@45 1294
bgneal@45 1295 switch (na) {
bgneal@45 1296 case 'opacity':
bgneal@45 1297 // IE specific opacity
bgneal@45 1298 if (isIE) {
bgneal@45 1299 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
bgneal@45 1300
bgneal@45 1301 if (!n.currentStyle || !n.currentStyle.hasLayout)
bgneal@45 1302 s.display = 'inline-block';
bgneal@45 1303 }
bgneal@45 1304
bgneal@45 1305 // Fix for older browsers
bgneal@45 1306 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
bgneal@45 1307 break;
bgneal@45 1308
bgneal@45 1309 case 'float':
bgneal@45 1310 isIE ? s.styleFloat = v : s.cssFloat = v;
bgneal@45 1311 break;
bgneal@45 1312
bgneal@45 1313 default:
bgneal@45 1314 s[na] = v || '';
bgneal@45 1315 }
bgneal@45 1316
bgneal@45 1317 // Force update of the style data
bgneal@45 1318 if (t.settings.update_styles)
bgneal@183 1319 t.setAttrib(e, '_mce_style');
bgneal@45 1320 });
bgneal@45 1321 },
bgneal@45 1322
bgneal@45 1323 getStyle : function(n, na, c) {
bgneal@45 1324 n = this.get(n);
bgneal@45 1325
bgneal@45 1326 if (!n)
bgneal@45 1327 return false;
bgneal@45 1328
bgneal@45 1329 // Gecko
bgneal@45 1330 if (this.doc.defaultView && c) {
bgneal@45 1331 // Remove camelcase
bgneal@45 1332 na = na.replace(/[A-Z]/g, function(a){
bgneal@45 1333 return '-' + a;
bgneal@45 1334 });
bgneal@45 1335
bgneal@45 1336 try {
bgneal@45 1337 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
bgneal@45 1338 } catch (ex) {
bgneal@45 1339 // Old safari might fail
bgneal@45 1340 return null;
bgneal@45 1341 }
bgneal@45 1342 }
bgneal@45 1343
bgneal@45 1344 // Camelcase it, if needed
bgneal@45 1345 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 1346 return b.toUpperCase();
bgneal@45 1347 });
bgneal@45 1348
bgneal@45 1349 if (na == 'float')
bgneal@45 1350 na = isIE ? 'styleFloat' : 'cssFloat';
bgneal@45 1351
bgneal@45 1352 // IE & Opera
bgneal@45 1353 if (n.currentStyle && c)
bgneal@45 1354 return n.currentStyle[na];
bgneal@45 1355
bgneal@45 1356 return n.style[na];
bgneal@45 1357 },
bgneal@45 1358
bgneal@45 1359 setStyles : function(e, o) {
bgneal@45 1360 var t = this, s = t.settings, ol;
bgneal@45 1361
bgneal@45 1362 ol = s.update_styles;
bgneal@45 1363 s.update_styles = 0;
bgneal@45 1364
bgneal@45 1365 each(o, function(v, n) {
bgneal@45 1366 t.setStyle(e, n, v);
bgneal@45 1367 });
bgneal@45 1368
bgneal@45 1369 // Update style info
bgneal@45 1370 s.update_styles = ol;
bgneal@45 1371 if (s.update_styles)
bgneal@45 1372 t.setAttrib(e, s.cssText);
bgneal@45 1373 },
bgneal@45 1374
bgneal@45 1375 setAttrib : function(e, n, v) {
bgneal@45 1376 var t = this;
bgneal@45 1377
bgneal@45 1378 // Whats the point
bgneal@45 1379 if (!e || !n)
bgneal@45 1380 return;
bgneal@45 1381
bgneal@45 1382 // Strict XML mode
bgneal@45 1383 if (t.settings.strict)
bgneal@45 1384 n = n.toLowerCase();
bgneal@45 1385
bgneal@45 1386 return this.run(e, function(e) {
bgneal@45 1387 var s = t.settings;
bgneal@45 1388
bgneal@45 1389 switch (n) {
bgneal@45 1390 case "style":
bgneal@45 1391 if (!is(v, 'string')) {
bgneal@45 1392 each(v, function(v, n) {
bgneal@45 1393 t.setStyle(e, n, v);
bgneal@45 1394 });
bgneal@45 1395
bgneal@45 1396 return;
bgneal@45 1397 }
bgneal@45 1398
bgneal@45 1399 // No mce_style for elements with these since they might get resized by the user
bgneal@45 1400 if (s.keep_values) {
bgneal@45 1401 if (v && !t._isRes(v))
bgneal@183 1402 e.setAttribute('_mce_style', v, 2);
bgneal@45 1403 else
bgneal@183 1404 e.removeAttribute('_mce_style', 2);
bgneal@45 1405 }
bgneal@45 1406
bgneal@45 1407 e.style.cssText = v;
bgneal@45 1408 break;
bgneal@45 1409
bgneal@45 1410 case "class":
bgneal@45 1411 e.className = v || ''; // Fix IE null bug
bgneal@45 1412 break;
bgneal@45 1413
bgneal@45 1414 case "src":
bgneal@45 1415 case "href":
bgneal@45 1416 if (s.keep_values) {
bgneal@45 1417 if (s.url_converter)
bgneal@45 1418 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
bgneal@45 1419
bgneal@183 1420 t.setAttrib(e, '_mce_' + n, v, 2);
bgneal@45 1421 }
bgneal@45 1422
bgneal@45 1423 break;
bgneal@45 1424
bgneal@45 1425 case "shape":
bgneal@183 1426 e.setAttribute('_mce_style', v);
bgneal@45 1427 break;
bgneal@45 1428 }
bgneal@45 1429
bgneal@45 1430 if (is(v) && v !== null && v.length !== 0)
bgneal@45 1431 e.setAttribute(n, '' + v, 2);
bgneal@45 1432 else
bgneal@45 1433 e.removeAttribute(n, 2);
bgneal@45 1434 });
bgneal@45 1435 },
bgneal@45 1436
bgneal@45 1437 setAttribs : function(e, o) {
bgneal@45 1438 var t = this;
bgneal@45 1439
bgneal@45 1440 return this.run(e, function(e) {
bgneal@45 1441 each(o, function(v, n) {
bgneal@45 1442 t.setAttrib(e, n, v);
bgneal@45 1443 });
bgneal@45 1444 });
bgneal@45 1445 },
bgneal@45 1446
bgneal@45 1447 getAttrib : function(e, n, dv) {
bgneal@45 1448 var v, t = this;
bgneal@45 1449
bgneal@45 1450 e = t.get(e);
bgneal@45 1451
bgneal@45 1452 if (!e || e.nodeType !== 1)
bgneal@45 1453 return false;
bgneal@45 1454
bgneal@45 1455 if (!is(dv))
bgneal@45 1456 dv = '';
bgneal@45 1457
bgneal@45 1458 // Try the mce variant for these
bgneal@45 1459 if (/^(src|href|style|coords|shape)$/.test(n)) {
bgneal@183 1460 v = e.getAttribute("_mce_" + n);
bgneal@45 1461
bgneal@45 1462 if (v)
bgneal@45 1463 return v;
bgneal@45 1464 }
bgneal@45 1465
bgneal@45 1466 if (isIE && t.props[n]) {
bgneal@45 1467 v = e[t.props[n]];
bgneal@45 1468 v = v && v.nodeValue ? v.nodeValue : v;
bgneal@45 1469 }
bgneal@45 1470
bgneal@45 1471 if (!v)
bgneal@45 1472 v = e.getAttribute(n, 2);
bgneal@45 1473
bgneal@183 1474 // Check boolean attribs
bgneal@183 1475 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
bgneal@183 1476 if (e[t.props[n]] === true && v === '')
bgneal@183 1477 return n;
bgneal@183 1478
bgneal@183 1479 return v ? n : '';
bgneal@183 1480 }
bgneal@183 1481
bgneal@183 1482 // Inner input elements will override attributes on form elements
bgneal@183 1483 if (e.nodeName === "FORM" && e.getAttributeNode(n))
bgneal@183 1484 return e.getAttributeNode(n).nodeValue;
bgneal@183 1485
bgneal@45 1486 if (n === 'style') {
bgneal@45 1487 v = v || e.style.cssText;
bgneal@45 1488
bgneal@45 1489 if (v) {
bgneal@183 1490 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
bgneal@45 1491
bgneal@45 1492 if (t.settings.keep_values && !t._isRes(v))
bgneal@183 1493 e.setAttribute('_mce_style', v);
bgneal@45 1494 }
bgneal@45 1495 }
bgneal@45 1496
bgneal@45 1497 // Remove Apple and WebKit stuff
bgneal@45 1498 if (isWebKit && n === "class" && v)
bgneal@45 1499 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
bgneal@45 1500
bgneal@45 1501 // Handle IE issues
bgneal@45 1502 if (isIE) {
bgneal@45 1503 switch (n) {
bgneal@45 1504 case 'rowspan':
bgneal@45 1505 case 'colspan':
bgneal@45 1506 // IE returns 1 as default value
bgneal@45 1507 if (v === 1)
bgneal@45 1508 v = '';
bgneal@45 1509
bgneal@45 1510 break;
bgneal@45 1511
bgneal@45 1512 case 'size':
bgneal@45 1513 // IE returns +0 as default value for size
bgneal@45 1514 if (v === '+0' || v === 20 || v === 0)
bgneal@45 1515 v = '';
bgneal@45 1516
bgneal@45 1517 break;
bgneal@45 1518
bgneal@45 1519 case 'width':
bgneal@45 1520 case 'height':
bgneal@45 1521 case 'vspace':
bgneal@45 1522 case 'checked':
bgneal@45 1523 case 'disabled':
bgneal@45 1524 case 'readonly':
bgneal@45 1525 if (v === 0)
bgneal@45 1526 v = '';
bgneal@45 1527
bgneal@45 1528 break;
bgneal@45 1529
bgneal@45 1530 case 'hspace':
bgneal@45 1531 // IE returns -1 as default value
bgneal@45 1532 if (v === -1)
bgneal@45 1533 v = '';
bgneal@45 1534
bgneal@45 1535 break;
bgneal@45 1536
bgneal@45 1537 case 'maxlength':
bgneal@45 1538 case 'tabindex':
bgneal@45 1539 // IE returns default value
bgneal@45 1540 if (v === 32768 || v === 2147483647 || v === '32768')
bgneal@45 1541 v = '';
bgneal@45 1542
bgneal@45 1543 break;
bgneal@45 1544
bgneal@45 1545 case 'multiple':
bgneal@45 1546 case 'compact':
bgneal@45 1547 case 'noshade':
bgneal@45 1548 case 'nowrap':
bgneal@45 1549 if (v === 65535)
bgneal@45 1550 return n;
bgneal@45 1551
bgneal@45 1552 return dv;
bgneal@45 1553
bgneal@45 1554 case 'shape':
bgneal@45 1555 v = v.toLowerCase();
bgneal@45 1556 break;
bgneal@45 1557
bgneal@45 1558 default:
bgneal@45 1559 // IE has odd anonymous function for event attributes
bgneal@45 1560 if (n.indexOf('on') === 0 && v)
bgneal@183 1561 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
bgneal@45 1562 }
bgneal@45 1563 }
bgneal@45 1564
bgneal@45 1565 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
bgneal@45 1566 },
bgneal@45 1567
bgneal@183 1568 getPos : function(n, ro) {
bgneal@45 1569 var t = this, x = 0, y = 0, e, d = t.doc, r;
bgneal@45 1570
bgneal@45 1571 n = t.get(n);
bgneal@183 1572 ro = ro || d.body;
bgneal@183 1573
bgneal@183 1574 if (n) {
bgneal@183 1575 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
bgneal@183 1576 if (isIE && !t.stdMode) {
bgneal@183 1577 n = n.getBoundingClientRect();
bgneal@183 1578 e = t.boxModel ? d.documentElement : d.body;
bgneal@183 1579 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
bgneal@183 1580 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
bgneal@183 1581
bgneal@183 1582 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
bgneal@183 1583 }
bgneal@183 1584
bgneal@183 1585 r = n;
bgneal@183 1586 while (r && r != ro && r.nodeType) {
bgneal@183 1587 x += r.offsetLeft || 0;
bgneal@183 1588 y += r.offsetTop || 0;
bgneal@183 1589 r = r.offsetParent;
bgneal@183 1590 }
bgneal@183 1591
bgneal@183 1592 r = n.parentNode;
bgneal@183 1593 while (r && r != ro && r.nodeType) {
bgneal@45 1594 x -= r.scrollLeft || 0;
bgneal@45 1595 y -= r.scrollTop || 0;
bgneal@183 1596 r = r.parentNode;
bgneal@183 1597 }
bgneal@45 1598 }
bgneal@45 1599
bgneal@45 1600 return {x : x, y : y};
bgneal@45 1601 },
bgneal@45 1602
bgneal@45 1603 parseStyle : function(st) {
bgneal@45 1604 var t = this, s = t.settings, o = {};
bgneal@45 1605
bgneal@45 1606 if (!st)
bgneal@45 1607 return o;
bgneal@45 1608
bgneal@45 1609 function compress(p, s, ot) {
bgneal@45 1610 var t, r, b, l;
bgneal@45 1611
bgneal@45 1612 // Get values and check it it needs compressing
bgneal@45 1613 t = o[p + '-top' + s];
bgneal@45 1614 if (!t)
bgneal@45 1615 return;
bgneal@45 1616
bgneal@45 1617 r = o[p + '-right' + s];
bgneal@45 1618 if (t != r)
bgneal@45 1619 return;
bgneal@45 1620
bgneal@45 1621 b = o[p + '-bottom' + s];
bgneal@45 1622 if (r != b)
bgneal@45 1623 return;
bgneal@45 1624
bgneal@45 1625 l = o[p + '-left' + s];
bgneal@45 1626 if (b != l)
bgneal@45 1627 return;
bgneal@45 1628
bgneal@45 1629 // Compress
bgneal@45 1630 o[ot] = l;
bgneal@45 1631 delete o[p + '-top' + s];
bgneal@45 1632 delete o[p + '-right' + s];
bgneal@45 1633 delete o[p + '-bottom' + s];
bgneal@45 1634 delete o[p + '-left' + s];
bgneal@45 1635 };
bgneal@45 1636
bgneal@45 1637 function compress2(ta, a, b, c) {
bgneal@45 1638 var t;
bgneal@45 1639
bgneal@45 1640 t = o[a];
bgneal@45 1641 if (!t)
bgneal@45 1642 return;
bgneal@45 1643
bgneal@45 1644 t = o[b];
bgneal@45 1645 if (!t)
bgneal@45 1646 return;
bgneal@45 1647
bgneal@45 1648 t = o[c];
bgneal@45 1649 if (!t)
bgneal@45 1650 return;
bgneal@45 1651
bgneal@45 1652 // Compress
bgneal@45 1653 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
bgneal@45 1654 delete o[a];
bgneal@45 1655 delete o[b];
bgneal@45 1656 delete o[c];
bgneal@45 1657 };
bgneal@45 1658
bgneal@45 1659 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
bgneal@45 1660
bgneal@45 1661 each(st.split(';'), function(v) {
bgneal@45 1662 var sv, ur = [];
bgneal@45 1663
bgneal@45 1664 if (v) {
bgneal@45 1665 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
bgneal@45 1666 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
bgneal@45 1667 v = v.split(':');
bgneal@45 1668 sv = tinymce.trim(v[1]);
bgneal@45 1669 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
bgneal@45 1670
bgneal@45 1671 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
bgneal@45 1672 return t.toHex(v);
bgneal@45 1673 });
bgneal@45 1674
bgneal@45 1675 if (s.url_converter) {
bgneal@45 1676 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
bgneal@45 1677 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
bgneal@45 1678 });
bgneal@45 1679 }
bgneal@45 1680
bgneal@45 1681 o[tinymce.trim(v[0]).toLowerCase()] = sv;
bgneal@45 1682 }
bgneal@45 1683 });
bgneal@45 1684
bgneal@45 1685 compress("border", "", "border");
bgneal@45 1686 compress("border", "-width", "border-width");
bgneal@45 1687 compress("border", "-color", "border-color");
bgneal@45 1688 compress("border", "-style", "border-style");
bgneal@45 1689 compress("padding", "", "padding");
bgneal@45 1690 compress("margin", "", "margin");
bgneal@45 1691 compress2('border', 'border-width', 'border-style', 'border-color');
bgneal@45 1692
bgneal@45 1693 if (isIE) {
bgneal@45 1694 // Remove pointless border
bgneal@45 1695 if (o.border == 'medium none')
bgneal@45 1696 o.border = '';
bgneal@45 1697 }
bgneal@45 1698
bgneal@45 1699 return o;
bgneal@45 1700 },
bgneal@45 1701
bgneal@183 1702 serializeStyle : function(o, name) {
bgneal@183 1703 var t = this, s = '';
bgneal@183 1704
bgneal@183 1705 function add(v, k) {
bgneal@45 1706 if (k && v) {
bgneal@183 1707 // Remove browser specific styles like -moz- or -webkit-
bgneal@183 1708 if (k.indexOf('-') === 0)
bgneal@45 1709 return;
bgneal@45 1710
bgneal@45 1711 switch (k) {
bgneal@183 1712 case 'font-weight':
bgneal@183 1713 // Opera will output bold as 700
bgneal@183 1714 if (v == 700)
bgneal@183 1715 v = 'bold';
bgneal@183 1716
bgneal@183 1717 break;
bgneal@183 1718
bgneal@45 1719 case 'color':
bgneal@45 1720 case 'background-color':
bgneal@45 1721 v = v.toLowerCase();
bgneal@45 1722 break;
bgneal@45 1723 }
bgneal@45 1724
bgneal@45 1725 s += (s ? ' ' : '') + k + ': ' + v + ';';
bgneal@45 1726 }
bgneal@183 1727 };
bgneal@183 1728
bgneal@183 1729 // Validate style output
bgneal@183 1730 if (name && t._styles) {
bgneal@183 1731 each(t._styles['*'], function(name) {
bgneal@183 1732 add(o[name], name);
bgneal@183 1733 });
bgneal@183 1734
bgneal@183 1735 each(t._styles[name.toLowerCase()], function(name) {
bgneal@183 1736 add(o[name], name);
bgneal@183 1737 });
bgneal@183 1738 } else
bgneal@183 1739 each(o, add);
bgneal@45 1740
bgneal@45 1741 return s;
bgneal@45 1742 },
bgneal@45 1743
bgneal@45 1744 loadCSS : function(u) {
bgneal@183 1745 var t = this, d = t.doc, head;
bgneal@45 1746
bgneal@45 1747 if (!u)
bgneal@45 1748 u = '';
bgneal@45 1749
bgneal@183 1750 head = t.select('head')[0];
bgneal@183 1751
bgneal@45 1752 each(u.split(','), function(u) {
bgneal@183 1753 var link;
bgneal@183 1754
bgneal@45 1755 if (t.files[u])
bgneal@45 1756 return;
bgneal@45 1757
bgneal@45 1758 t.files[u] = true;
bgneal@183 1759 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
bgneal@183 1760
bgneal@183 1761 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
bgneal@183 1762 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
bgneal@183 1763 // It's ugly but it seems to work fine.
bgneal@183 1764 if (isIE && d.documentMode) {
bgneal@183 1765 link.onload = function() {
bgneal@183 1766 d.recalc();
bgneal@183 1767 link.onload = null;
bgneal@183 1768 };
bgneal@183 1769 }
bgneal@183 1770
bgneal@183 1771 head.appendChild(link);
bgneal@183 1772 });
bgneal@183 1773 },
bgneal@45 1774
bgneal@45 1775 addClass : function(e, c) {
bgneal@45 1776 return this.run(e, function(e) {
bgneal@45 1777 var o;
bgneal@45 1778
bgneal@45 1779 if (!c)
bgneal@45 1780 return 0;
bgneal@45 1781
bgneal@45 1782 if (this.hasClass(e, c))
bgneal@45 1783 return e.className;
bgneal@45 1784
bgneal@45 1785 o = this.removeClass(e, c);
bgneal@45 1786
bgneal@45 1787 return e.className = (o != '' ? (o + ' ') : '') + c;
bgneal@45 1788 });
bgneal@45 1789 },
bgneal@45 1790
bgneal@45 1791 removeClass : function(e, c) {
bgneal@45 1792 var t = this, re;
bgneal@45 1793
bgneal@45 1794 return t.run(e, function(e) {
bgneal@45 1795 var v;
bgneal@45 1796
bgneal@45 1797 if (t.hasClass(e, c)) {
bgneal@45 1798 if (!re)
bgneal@45 1799 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
bgneal@45 1800
bgneal@45 1801 v = e.className.replace(re, ' ');
bgneal@183 1802 v = tinymce.trim(v != ' ' ? v : '');
bgneal@183 1803
bgneal@183 1804 e.className = v;
bgneal@183 1805
bgneal@183 1806 // Empty class attr
bgneal@183 1807 if (!v) {
bgneal@183 1808 e.removeAttribute('class');
bgneal@183 1809 e.removeAttribute('className');
bgneal@183 1810 }
bgneal@183 1811
bgneal@183 1812 return v;
bgneal@45 1813 }
bgneal@45 1814
bgneal@45 1815 return e.className;
bgneal@45 1816 });
bgneal@45 1817 },
bgneal@45 1818
bgneal@45 1819 hasClass : function(n, c) {
bgneal@45 1820 n = this.get(n);
bgneal@45 1821
bgneal@45 1822 if (!n || !c)
bgneal@45 1823 return false;
bgneal@45 1824
bgneal@45 1825 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
bgneal@45 1826 },
bgneal@45 1827
bgneal@45 1828 show : function(e) {
bgneal@45 1829 return this.setStyle(e, 'display', 'block');
bgneal@45 1830 },
bgneal@45 1831
bgneal@45 1832 hide : function(e) {
bgneal@45 1833 return this.setStyle(e, 'display', 'none');
bgneal@45 1834 },
bgneal@45 1835
bgneal@45 1836 isHidden : function(e) {
bgneal@45 1837 e = this.get(e);
bgneal@45 1838
bgneal@45 1839 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
bgneal@45 1840 },
bgneal@45 1841
bgneal@45 1842 uniqueId : function(p) {
bgneal@45 1843 return (!p ? 'mce_' : p) + (this.counter++);
bgneal@45 1844 },
bgneal@45 1845
bgneal@45 1846 setHTML : function(e, h) {
bgneal@45 1847 var t = this;
bgneal@45 1848
bgneal@45 1849 return this.run(e, function(e) {
bgneal@45 1850 var x, i, nl, n, p, x;
bgneal@45 1851
bgneal@45 1852 h = t.processHTML(h);
bgneal@45 1853
bgneal@45 1854 if (isIE) {
bgneal@45 1855 function set() {
bgneal@183 1856 // Remove all child nodes
bgneal@183 1857 while (e.firstChild)
bgneal@183 1858 e.firstChild.removeNode();
bgneal@183 1859
bgneal@45 1860 try {
bgneal@45 1861 // IE will remove comments from the beginning
bgneal@45 1862 // unless you padd the contents with something
bgneal@45 1863 e.innerHTML = '<br />' + h;
bgneal@45 1864 e.removeChild(e.firstChild);
bgneal@45 1865 } catch (ex) {
bgneal@45 1866 // 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 1867 // This seems to fix this problem
bgneal@45 1868
bgneal@45 1869 // Create new div with HTML contents and a BR infront to keep comments
bgneal@45 1870 x = t.create('div');
bgneal@45 1871 x.innerHTML = '<br />' + h;
bgneal@45 1872
bgneal@45 1873 // Add all children from div to target
bgneal@45 1874 each (x.childNodes, function(n, i) {
bgneal@45 1875 // Skip br element
bgneal@45 1876 if (i)
bgneal@45 1877 e.appendChild(n);
bgneal@45 1878 });
bgneal@45 1879 }
bgneal@45 1880 };
bgneal@45 1881
bgneal@45 1882 // IE has a serious bug when it comes to paragraphs it can produce an invalid
bgneal@45 1883 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
bgneal@45 1884 // It seems to be that IE doesn't like a root block element placed inside another root block element
bgneal@45 1885 if (t.settings.fix_ie_paragraphs)
bgneal@183 1886 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true">&nbsp;</p>');
bgneal@45 1887
bgneal@45 1888 set();
bgneal@45 1889
bgneal@45 1890 if (t.settings.fix_ie_paragraphs) {
bgneal@45 1891 // Check for odd paragraphs this is a sign of a broken DOM
bgneal@45 1892 nl = e.getElementsByTagName("p");
bgneal@45 1893 for (i = nl.length - 1, x = 0; i >= 0; i--) {
bgneal@45 1894 n = nl[i];
bgneal@45 1895
bgneal@45 1896 if (!n.hasChildNodes()) {
bgneal@183 1897 if (!n._mce_keep) {
bgneal@45 1898 x = 1; // Is broken
bgneal@45 1899 break;
bgneal@45 1900 }
bgneal@45 1901
bgneal@183 1902 n.removeAttribute('_mce_keep');
bgneal@45 1903 }
bgneal@45 1904 }
bgneal@45 1905 }
bgneal@45 1906
bgneal@45 1907 // Time to fix the madness IE left us
bgneal@45 1908 if (x) {
bgneal@45 1909 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
bgneal@45 1910 // after we use innerHTML we can fix the DOM tree
bgneal@183 1911 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
bgneal@217 1912 h = h.replace(/<\/p>/gi, '</div>');
bgneal@45 1913
bgneal@45 1914 // Set the new HTML with DIVs
bgneal@45 1915 set();
bgneal@45 1916
bgneal@183 1917 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
bgneal@45 1918 // This is needed since IE has a annoying bug see above for details
bgneal@45 1919 // This is a slow process but it has to be done. :(
bgneal@45 1920 if (t.settings.fix_ie_paragraphs) {
bgneal@45 1921 nl = e.getElementsByTagName("DIV");
bgneal@45 1922 for (i = nl.length - 1; i >= 0; i--) {
bgneal@45 1923 n = nl[i];
bgneal@45 1924
bgneal@45 1925 // Is it a temp div
bgneal@183 1926 if (n._mce_tmp) {
bgneal@45 1927 // Create new paragraph
bgneal@45 1928 p = t.doc.createElement('p');
bgneal@45 1929
bgneal@45 1930 // Copy all attributes
bgneal@45 1931 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
bgneal@45 1932 var v;
bgneal@45 1933
bgneal@183 1934 if (b !== '_mce_tmp') {
bgneal@45 1935 v = n.getAttribute(b);
bgneal@45 1936
bgneal@45 1937 if (!v && b === 'class')
bgneal@45 1938 v = n.className;
bgneal@45 1939
bgneal@45 1940 p.setAttribute(b, v);
bgneal@45 1941 }
bgneal@45 1942 });
bgneal@45 1943
bgneal@45 1944 // Append all children to new paragraph
bgneal@45 1945 for (x = 0; x<n.childNodes.length; x++)
bgneal@45 1946 p.appendChild(n.childNodes[x].cloneNode(true));
bgneal@45 1947
bgneal@45 1948 // Replace div with new paragraph
bgneal@45 1949 n.swapNode(p);
bgneal@45 1950 }
bgneal@45 1951 }
bgneal@45 1952 }
bgneal@45 1953 }
bgneal@45 1954 } else
bgneal@45 1955 e.innerHTML = h;
bgneal@45 1956
bgneal@45 1957 return h;
bgneal@45 1958 });
bgneal@45 1959 },
bgneal@45 1960
bgneal@45 1961 processHTML : function(h) {
bgneal@183 1962 var t = this, s = t.settings, codeBlocks = [];
bgneal@45 1963
bgneal@45 1964 if (!s.process_html)
bgneal@45 1965 return h;
bgneal@45 1966
bgneal@183 1967 if (isIE) {
bgneal@45 1968 h = h.replace(/&apos;/g, '&#39;'); // IE can't handle apos
bgneal@45 1969 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
bgneal@45 1970 }
bgneal@45 1971
bgneal@45 1972 // Fix some issues
bgneal@45 1973 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
bgneal@45 1974
bgneal@183 1975 // Store away src and href in _mce_src and mce_href since browsers mess them up
bgneal@45 1976 if (s.keep_values) {
bgneal@45 1977 // Wrap scripts and styles in comments for serialization purposes
bgneal@183 1978 if (/<script|noscript|style/i.test(h)) {
bgneal@45 1979 function trim(s) {
bgneal@45 1980 // Remove prefix and suffix code for element
bgneal@45 1981 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
bgneal@45 1982 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
bgneal@45 1983 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
bgneal@45 1984 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
bgneal@45 1985
bgneal@45 1986 return s;
bgneal@45 1987 };
bgneal@45 1988
bgneal@183 1989 // Wrap the script contents in CDATA and keep them from executing
bgneal@183 1990 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
bgneal@45 1991 // Force type attribute
bgneal@183 1992 if (!attribs)
bgneal@183 1993 attribs = ' type="text/javascript"';
bgneal@183 1994
bgneal@183 1995 // Convert the src attribute of the scripts
bgneal@183 1996 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
bgneal@183 1997 if (s.url_converter)
bgneal@183 1998 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
bgneal@183 1999
bgneal@183 2000 return '_mce_src="' + url + '"';
bgneal@183 2001 });
bgneal@183 2002
bgneal@183 2003 // Wrap text contents
bgneal@183 2004 if (tinymce.trim(text)) {
bgneal@183 2005 codeBlocks.push(trim(text));
bgneal@183 2006 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
bgneal@183 2007 }
bgneal@183 2008
bgneal@183 2009 return '<mce:script' + attribs + '>' + text + '</mce:script>';
bgneal@45 2010 });
bgneal@45 2011
bgneal@183 2012 // Wrap style elements
bgneal@183 2013 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
bgneal@183 2014 // Wrap text contents
bgneal@183 2015 if (text) {
bgneal@183 2016 codeBlocks.push(trim(text));
bgneal@183 2017 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
bgneal@183 2018 }
bgneal@183 2019
bgneal@183 2020 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
bgneal@45 2021 });
bgneal@183 2022
bgneal@183 2023 // Wrap noscript elements
bgneal@183 2024 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@183 2025 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '&#45;&#45;') + '--></mce:noscript>';
bgneal@183 2026 });
bgneal@45 2027 }
bgneal@45 2028
bgneal@45 2029 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
bgneal@45 2030
bgneal@183 2031 // This function processes the attributes in the HTML string to force boolean
bgneal@183 2032 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
bgneal@183 2033 function processTags(html) {
bgneal@183 2034 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
bgneal@183 2035 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
bgneal@183 2036 var mceValue;
bgneal@183 2037
bgneal@183 2038 name = name.toLowerCase();
bgneal@183 2039 value = value || val2 || val3 || "";
bgneal@183 2040
bgneal@183 2041 // Treat boolean attributes
bgneal@183 2042 if (boolAttrs[name]) {
bgneal@183 2043 // false or 0 is treated as a missing attribute
bgneal@183 2044 if (value === 'false' || value === '0')
bgneal@183 2045 return;
bgneal@183 2046
bgneal@183 2047 return name + '="' + name + '"';
bgneal@45 2048 }
bgneal@45 2049
bgneal@183 2050 // Is attribute one that needs special treatment
bgneal@183 2051 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
bgneal@183 2052 mceValue = t.decode(value);
bgneal@183 2053
bgneal@183 2054 // Convert URLs to relative/absolute ones
bgneal@183 2055 if (s.url_converter && (name == "src" || name == "href"))
bgneal@183 2056 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
bgneal@183 2057
bgneal@183 2058 // Process styles lowercases them and compresses them
bgneal@183 2059 if (name == 'style')
bgneal@183 2060 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
bgneal@183 2061
bgneal@183 2062 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
bgneal@45 2063 }
bgneal@183 2064
bgneal@183 2065 return match;
bgneal@183 2066 }) + end + '>';
bgneal@183 2067 });
bgneal@183 2068 };
bgneal@183 2069
bgneal@183 2070 h = processTags(h);
bgneal@183 2071
bgneal@183 2072 // Restore script blocks
bgneal@183 2073 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
bgneal@183 2074 return codeBlocks[idx];
bgneal@45 2075 });
bgneal@45 2076 }
bgneal@45 2077
bgneal@45 2078 return h;
bgneal@45 2079 },
bgneal@45 2080
bgneal@45 2081 getOuterHTML : function(e) {
bgneal@45 2082 var d;
bgneal@45 2083
bgneal@45 2084 e = this.get(e);
bgneal@45 2085
bgneal@45 2086 if (!e)
bgneal@45 2087 return null;
bgneal@45 2088
bgneal@45 2089 if (e.outerHTML !== undefined)
bgneal@45 2090 return e.outerHTML;
bgneal@45 2091
bgneal@45 2092 d = (e.ownerDocument || this.doc).createElement("body");
bgneal@45 2093 d.appendChild(e.cloneNode(true));
bgneal@45 2094
bgneal@45 2095 return d.innerHTML;
bgneal@45 2096 },
bgneal@45 2097
bgneal@45 2098 setOuterHTML : function(e, h, d) {
bgneal@45 2099 var t = this;
bgneal@45 2100
bgneal@183 2101 function setHTML(e, h, d) {
bgneal@183 2102 var n, tp;
bgneal@183 2103
bgneal@183 2104 tp = d.createElement("body");
bgneal@183 2105 tp.innerHTML = h;
bgneal@183 2106
bgneal@183 2107 n = tp.lastChild;
bgneal@183 2108 while (n) {
bgneal@183 2109 t.insertAfter(n.cloneNode(true), e);
bgneal@183 2110 n = n.previousSibling;
bgneal@183 2111 }
bgneal@183 2112
bgneal@183 2113 t.remove(e);
bgneal@183 2114 };
bgneal@183 2115
bgneal@45 2116 return this.run(e, function(e) {
bgneal@45 2117 e = t.get(e);
bgneal@183 2118
bgneal@183 2119 // Only set HTML on elements
bgneal@183 2120 if (e.nodeType == 1) {
bgneal@183 2121 d = d || e.ownerDocument || t.doc;
bgneal@183 2122
bgneal@183 2123 if (isIE) {
bgneal@183 2124 try {
bgneal@183 2125 // Try outerHTML for IE it sometimes produces an unknown runtime error
bgneal@183 2126 if (isIE && e.nodeType == 1)
bgneal@183 2127 e.outerHTML = h;
bgneal@183 2128 else
bgneal@183 2129 setHTML(e, h, d);
bgneal@183 2130 } catch (ex) {
bgneal@183 2131 // Fix for unknown runtime error
bgneal@183 2132 setHTML(e, h, d);
bgneal@183 2133 }
bgneal@183 2134 } else
bgneal@183 2135 setHTML(e, h, d);
bgneal@45 2136 }
bgneal@45 2137 });
bgneal@45 2138 },
bgneal@45 2139
bgneal@45 2140 decode : function(s) {
bgneal@45 2141 var e, n, v;
bgneal@45 2142
bgneal@45 2143 // Look for entities to decode
bgneal@183 2144 if (/&[\w#]+;/.test(s)) {
bgneal@45 2145 // Decode the entities using a div element not super efficient but less code
bgneal@45 2146 e = this.doc.createElement("div");
bgneal@45 2147 e.innerHTML = s;
bgneal@45 2148 n = e.firstChild;
bgneal@45 2149 v = '';
bgneal@45 2150
bgneal@45 2151 if (n) {
bgneal@45 2152 do {
bgneal@45 2153 v += n.nodeValue;
bgneal@183 2154 } while (n = n.nextSibling);
bgneal@45 2155 }
bgneal@45 2156
bgneal@45 2157 return v || s;
bgneal@45 2158 }
bgneal@45 2159
bgneal@45 2160 return s;
bgneal@45 2161 },
bgneal@45 2162
bgneal@183 2163 encode : function(str) {
bgneal@183 2164 return ('' + str).replace(encodeCharsRe, function(chr) {
bgneal@183 2165 return encodedChars[chr];
bgneal@183 2166 });
bgneal@183 2167 },
bgneal@183 2168
bgneal@183 2169 insertAfter : function(node, reference_node) {
bgneal@183 2170 reference_node = this.get(reference_node);
bgneal@183 2171
bgneal@183 2172 return this.run(node, function(node) {
bgneal@183 2173 var parent, nextSibling;
bgneal@183 2174
bgneal@183 2175 parent = reference_node.parentNode;
bgneal@183 2176 nextSibling = reference_node.nextSibling;
bgneal@183 2177
bgneal@183 2178 if (nextSibling)
bgneal@183 2179 parent.insertBefore(node, nextSibling);
bgneal@45 2180 else
bgneal@183 2181 parent.appendChild(node);
bgneal@183 2182
bgneal@183 2183 return node;
bgneal@183 2184 });
bgneal@183 2185 },
bgneal@45 2186
bgneal@45 2187 isBlock : function(n) {
bgneal@45 2188 if (n.nodeType && n.nodeType !== 1)
bgneal@45 2189 return false;
bgneal@45 2190
bgneal@45 2191 n = n.nodeName || n;
bgneal@45 2192
bgneal@183 2193 return blockRe.test(n);
bgneal@183 2194 },
bgneal@45 2195
bgneal@45 2196 replace : function(n, o, k) {
bgneal@45 2197 var t = this;
bgneal@45 2198
bgneal@45 2199 if (is(o, 'array'))
bgneal@45 2200 n = n.cloneNode(true);
bgneal@45 2201
bgneal@45 2202 return t.run(o, function(o) {
bgneal@45 2203 if (k) {
bgneal@183 2204 each(tinymce.grep(o.childNodes), function(c) {
bgneal@183 2205 n.appendChild(c);
bgneal@45 2206 });
bgneal@45 2207 }
bgneal@45 2208
bgneal@45 2209 return o.parentNode.replaceChild(n, o);
bgneal@45 2210 });
bgneal@45 2211 },
bgneal@45 2212
bgneal@183 2213 rename : function(elm, name) {
bgneal@183 2214 var t = this, newElm;
bgneal@183 2215
bgneal@183 2216 if (elm.nodeName != name.toUpperCase()) {
bgneal@183 2217 // Rename block element
bgneal@183 2218 newElm = t.create(name);
bgneal@183 2219
bgneal@183 2220 // Copy attribs to new block
bgneal@183 2221 each(t.getAttribs(elm), function(attr_node) {
bgneal@183 2222 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
bgneal@183 2223 });
bgneal@183 2224
bgneal@183 2225 // Replace block
bgneal@183 2226 t.replace(newElm, elm, 1);
bgneal@183 2227 }
bgneal@183 2228
bgneal@183 2229 return newElm || elm;
bgneal@183 2230 },
bgneal@45 2231
bgneal@45 2232 findCommonAncestor : function(a, b) {
bgneal@45 2233 var ps = a, pe;
bgneal@45 2234
bgneal@45 2235 while (ps) {
bgneal@45 2236 pe = b;
bgneal@45 2237
bgneal@45 2238 while (pe && ps != pe)
bgneal@45 2239 pe = pe.parentNode;
bgneal@45 2240
bgneal@45 2241 if (ps == pe)
bgneal@45 2242 break;
bgneal@45 2243
bgneal@45 2244 ps = ps.parentNode;
bgneal@45 2245 }
bgneal@45 2246
bgneal@45 2247 if (!ps && a.ownerDocument)
bgneal@45 2248 return a.ownerDocument.documentElement;
bgneal@45 2249
bgneal@45 2250 return ps;
bgneal@45 2251 },
bgneal@45 2252
bgneal@45 2253 toHex : function(s) {
bgneal@45 2254 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
bgneal@45 2255
bgneal@45 2256 function hex(s) {
bgneal@45 2257 s = parseInt(s).toString(16);
bgneal@45 2258
bgneal@45 2259 return s.length > 1 ? s : '0' + s; // 0 -> 00
bgneal@45 2260 };
bgneal@45 2261
bgneal@45 2262 if (c) {
bgneal@45 2263 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
bgneal@45 2264
bgneal@45 2265 return s;
bgneal@45 2266 }
bgneal@45 2267
bgneal@45 2268 return s;
bgneal@45 2269 },
bgneal@45 2270
bgneal@45 2271 getClasses : function() {
bgneal@45 2272 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
bgneal@45 2273
bgneal@45 2274 if (t.classes)
bgneal@45 2275 return t.classes;
bgneal@45 2276
bgneal@45 2277 function addClasses(s) {
bgneal@45 2278 // IE style imports
bgneal@45 2279 each(s.imports, function(r) {
bgneal@45 2280 addClasses(r);
bgneal@45 2281 });
bgneal@45 2282
bgneal@45 2283 each(s.cssRules || s.rules, function(r) {
bgneal@45 2284 // Real type or fake it on IE
bgneal@45 2285 switch (r.type || 1) {
bgneal@45 2286 // Rule
bgneal@45 2287 case 1:
bgneal@45 2288 if (r.selectorText) {
bgneal@45 2289 each(r.selectorText.split(','), function(v) {
bgneal@45 2290 v = v.replace(/^\s*|\s*$|^\s\./g, "");
bgneal@45 2291
bgneal@45 2292 // Is internal or it doesn't contain a class
bgneal@45 2293 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
bgneal@45 2294 return;
bgneal@45 2295
bgneal@45 2296 // Remove everything but class name
bgneal@45 2297 ov = v;
bgneal@45 2298 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
bgneal@45 2299
bgneal@45 2300 // Filter classes
bgneal@45 2301 if (f && !(v = f(v, ov)))
bgneal@45 2302 return;
bgneal@45 2303
bgneal@45 2304 if (!lo[v]) {
bgneal@45 2305 cl.push({'class' : v});
bgneal@45 2306 lo[v] = 1;
bgneal@45 2307 }
bgneal@45 2308 });
bgneal@45 2309 }
bgneal@45 2310 break;
bgneal@45 2311
bgneal@45 2312 // Import
bgneal@45 2313 case 3:
bgneal@45 2314 addClasses(r.styleSheet);
bgneal@45 2315 break;
bgneal@45 2316 }
bgneal@45 2317 });
bgneal@45 2318 };
bgneal@45 2319
bgneal@45 2320 try {
bgneal@45 2321 each(t.doc.styleSheets, addClasses);
bgneal@45 2322 } catch (ex) {
bgneal@45 2323 // Ignore
bgneal@45 2324 }
bgneal@45 2325
bgneal@45 2326 if (cl.length > 0)
bgneal@45 2327 t.classes = cl;
bgneal@45 2328
bgneal@45 2329 return cl;
bgneal@45 2330 },
bgneal@45 2331
bgneal@45 2332 run : function(e, f, s) {
bgneal@45 2333 var t = this, o;
bgneal@45 2334
bgneal@45 2335 if (t.doc && typeof(e) === 'string')
bgneal@45 2336 e = t.get(e);
bgneal@45 2337
bgneal@45 2338 if (!e)
bgneal@45 2339 return false;
bgneal@45 2340
bgneal@45 2341 s = s || this;
bgneal@45 2342 if (!e.nodeType && (e.length || e.length === 0)) {
bgneal@45 2343 o = [];
bgneal@45 2344
bgneal@45 2345 each(e, function(e, i) {
bgneal@45 2346 if (e) {
bgneal@45 2347 if (typeof(e) == 'string')
bgneal@45 2348 e = t.doc.getElementById(e);
bgneal@45 2349
bgneal@45 2350 o.push(f.call(s, e, i));
bgneal@45 2351 }
bgneal@45 2352 });
bgneal@45 2353
bgneal@45 2354 return o;
bgneal@45 2355 }
bgneal@45 2356
bgneal@45 2357 return f.call(s, e);
bgneal@45 2358 },
bgneal@45 2359
bgneal@45 2360 getAttribs : function(n) {
bgneal@45 2361 var o;
bgneal@45 2362
bgneal@45 2363 n = this.get(n);
bgneal@45 2364
bgneal@45 2365 if (!n)
bgneal@45 2366 return [];
bgneal@45 2367
bgneal@45 2368 if (isIE) {
bgneal@45 2369 o = [];
bgneal@45 2370
bgneal@45 2371 // Object will throw exception in IE
bgneal@45 2372 if (n.nodeName == 'OBJECT')
bgneal@45 2373 return n.attributes;
bgneal@45 2374
bgneal@183 2375 // IE doesn't keep the selected attribute if you clone option elements
bgneal@183 2376 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
bgneal@183 2377 o.push({specified : 1, nodeName : 'selected'});
bgneal@183 2378
bgneal@45 2379 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
bgneal@183 2380 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
bgneal@183 2381 o.push({specified : 1, nodeName : a});
bgneal@45 2382 });
bgneal@45 2383
bgneal@45 2384 return o;
bgneal@45 2385 }
bgneal@45 2386
bgneal@45 2387 return n.attributes;
bgneal@45 2388 },
bgneal@45 2389
bgneal@45 2390 destroy : function(s) {
bgneal@45 2391 var t = this;
bgneal@45 2392
bgneal@183 2393 if (t.events)
bgneal@183 2394 t.events.destroy();
bgneal@183 2395
bgneal@183 2396 t.win = t.doc = t.root = t.events = null;
bgneal@45 2397
bgneal@45 2398 // Manual destroy then remove unload handler
bgneal@45 2399 if (!s)
bgneal@45 2400 tinymce.removeUnload(t.destroy);
bgneal@45 2401 },
bgneal@45 2402
bgneal@45 2403 createRng : function() {
bgneal@45 2404 var d = this.doc;
bgneal@45 2405
bgneal@45 2406 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
bgneal@45 2407 },
bgneal@45 2408
bgneal@183 2409 nodeIndex : function(node, normalized) {
bgneal@183 2410 var idx = 0, lastNodeType, lastNode, nodeType;
bgneal@183 2411
bgneal@183 2412 if (node) {
bgneal@183 2413 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
bgneal@183 2414 nodeType = node.nodeType;
bgneal@183 2415
bgneal@217 2416 // Normalize text nodes
bgneal@217 2417 if (normalized && nodeType == 3) {
bgneal@217 2418 if (nodeType == lastNodeType || !node.nodeValue.length)
bgneal@217 2419 continue;
bgneal@217 2420 }
bgneal@217 2421
bgneal@217 2422 idx++;
bgneal@183 2423 lastNodeType = nodeType;
bgneal@183 2424 }
bgneal@183 2425 }
bgneal@183 2426
bgneal@183 2427 return idx;
bgneal@183 2428 },
bgneal@183 2429
bgneal@45 2430 split : function(pe, e, re) {
bgneal@45 2431 var t = this, r = t.createRng(), bef, aft, pa;
bgneal@45 2432
bgneal@183 2433 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
bgneal@183 2434 // but we don't want that in our code since it serves no purpose for the end user
bgneal@45 2435 // For example if this is chopped:
bgneal@45 2436 // <p>text 1<span><b>CHOP</b></span>text 2</p>
bgneal@45 2437 // would produce:
bgneal@45 2438 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
bgneal@45 2439 // this function will then trim of empty edges and produce:
bgneal@45 2440 // <p>text 1</p><b>CHOP</b><p>text 2</p>
bgneal@183 2441 function trim(node) {
bgneal@183 2442 var i, children = node.childNodes;
bgneal@183 2443
bgneal@183 2444 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
bgneal@183 2445 return;
bgneal@183 2446
bgneal@183 2447 for (i = children.length - 1; i >= 0; i--)
bgneal@183 2448 trim(children[i]);
bgneal@183 2449
bgneal@183 2450 if (node.nodeType != 9) {
bgneal@183 2451 // Keep non whitespace text nodes
bgneal@183 2452 if (node.nodeType == 3 && node.nodeValue.length > 0)
bgneal@183 2453 return;
bgneal@183 2454
bgneal@183 2455 if (node.nodeType == 1) {
bgneal@183 2456 // If the only child is a bookmark then move it up
bgneal@183 2457 children = node.childNodes;
bgneal@183 2458 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
bgneal@183 2459 node.parentNode.insertBefore(children[0], node);
bgneal@183 2460
bgneal@183 2461 // Keep non empty elements or img, hr etc
bgneal@183 2462 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
bgneal@183 2463 return;
bgneal@183 2464 }
bgneal@183 2465
bgneal@183 2466 t.remove(node);
bgneal@183 2467 }
bgneal@183 2468
bgneal@183 2469 return node;
bgneal@45 2470 };
bgneal@45 2471
bgneal@45 2472 if (pe && e) {
bgneal@45 2473 // Get before chunk
bgneal@183 2474 r.setStart(pe.parentNode, t.nodeIndex(pe));
bgneal@183 2475 r.setEnd(e.parentNode, t.nodeIndex(e));
bgneal@45 2476 bef = r.extractContents();
bgneal@45 2477
bgneal@45 2478 // Get after chunk
bgneal@45 2479 r = t.createRng();
bgneal@183 2480 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
bgneal@183 2481 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
bgneal@45 2482 aft = r.extractContents();
bgneal@45 2483
bgneal@183 2484 // Insert before chunk
bgneal@45 2485 pa = pe.parentNode;
bgneal@183 2486 pa.insertBefore(trim(bef), pe);
bgneal@183 2487
bgneal@183 2488 // Insert middle chunk
bgneal@45 2489 if (re)
bgneal@45 2490 pa.replaceChild(re, e);
bgneal@45 2491 else
bgneal@45 2492 pa.insertBefore(e, pe);
bgneal@45 2493
bgneal@183 2494 // Insert after chunk
bgneal@183 2495 pa.insertBefore(trim(aft), pe);
bgneal@45 2496 t.remove(pe);
bgneal@45 2497
bgneal@45 2498 return re || e;
bgneal@45 2499 }
bgneal@45 2500 },
bgneal@45 2501
bgneal@183 2502 bind : function(target, name, func, scope) {
bgneal@183 2503 var t = this;
bgneal@183 2504
bgneal@183 2505 if (!t.events)
bgneal@183 2506 t.events = new tinymce.dom.EventUtils();
bgneal@183 2507
bgneal@183 2508 return t.events.add(target, name, func, scope || this);
bgneal@183 2509 },
bgneal@183 2510
bgneal@183 2511 unbind : function(target, name, func) {
bgneal@183 2512 var t = this;
bgneal@183 2513
bgneal@183 2514 if (!t.events)
bgneal@183 2515 t.events = new tinymce.dom.EventUtils();
bgneal@183 2516
bgneal@183 2517 return t.events.remove(target, name, func);
bgneal@183 2518 },
bgneal@183 2519
bgneal@183 2520
bgneal@183 2521 _findSib : function(node, selector, name) {
bgneal@183 2522 var t = this, f = selector;
bgneal@183 2523
bgneal@183 2524 if (node) {
bgneal@183 2525 // If expression make a function of it using is
bgneal@183 2526 if (is(f, 'string')) {
bgneal@183 2527 f = function(node) {
bgneal@183 2528 return t.is(node, selector);
bgneal@183 2529 };
bgneal@183 2530 }
bgneal@183 2531
bgneal@183 2532 // Loop all siblings
bgneal@183 2533 for (node = node[name]; node; node = node[name]) {
bgneal@183 2534 if (f(node))
bgneal@183 2535 return node;
bgneal@183 2536 }
bgneal@183 2537 }
bgneal@183 2538
bgneal@183 2539 return null;
bgneal@183 2540 },
bgneal@183 2541
bgneal@45 2542 _isRes : function(c) {
bgneal@45 2543 // Is live resizble element
bgneal@45 2544 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
bgneal@45 2545 }
bgneal@45 2546
bgneal@45 2547 /*
bgneal@45 2548 walk : function(n, f, s) {
bgneal@45 2549 var d = this.doc, w;
bgneal@45 2550
bgneal@45 2551 if (d.createTreeWalker) {
bgneal@45 2552 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
bgneal@45 2553
bgneal@45 2554 while ((n = w.nextNode()) != null)
bgneal@45 2555 f.call(s || this, n);
bgneal@45 2556 } else
bgneal@45 2557 tinymce.walk(n, f, 'childNodes', s);
bgneal@45 2558 }
bgneal@45 2559 */
bgneal@45 2560
bgneal@45 2561 /*
bgneal@45 2562 toRGB : function(s) {
bgneal@45 2563 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
bgneal@45 2564
bgneal@45 2565 if (c) {
bgneal@45 2566 // #FFF -> #FFFFFF
bgneal@45 2567 if (!is(c[3]))
bgneal@45 2568 c[3] = c[2] = c[1];
bgneal@45 2569
bgneal@45 2570 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
bgneal@45 2571 }
bgneal@45 2572
bgneal@45 2573 return s;
bgneal@45 2574 }
bgneal@45 2575 */
bgneal@183 2576 });
bgneal@183 2577
bgneal@45 2578 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
bgneal@45 2579 })(tinymce);
bgneal@183 2580
bgneal@45 2581 (function(ns) {
bgneal@45 2582 // Range constructor
bgneal@45 2583 function Range(dom) {
bgneal@183 2584 var t = this,
bgneal@183 2585 doc = dom.doc,
bgneal@183 2586 EXTRACT = 0,
bgneal@183 2587 CLONE = 1,
bgneal@183 2588 DELETE = 2,
bgneal@183 2589 TRUE = true,
bgneal@183 2590 FALSE = false,
bgneal@183 2591 START_OFFSET = 'startOffset',
bgneal@183 2592 START_CONTAINER = 'startContainer',
bgneal@183 2593 END_CONTAINER = 'endContainer',
bgneal@183 2594 END_OFFSET = 'endOffset',
bgneal@183 2595 extend = tinymce.extend,
bgneal@183 2596 nodeIndex = dom.nodeIndex;
bgneal@183 2597
bgneal@183 2598 extend(t, {
bgneal@45 2599 // Inital states
bgneal@183 2600 startContainer : doc,
bgneal@45 2601 startOffset : 0,
bgneal@183 2602 endContainer : doc,
bgneal@45 2603 endOffset : 0,
bgneal@183 2604 collapsed : TRUE,
bgneal@183 2605 commonAncestorContainer : doc,
bgneal@45 2606
bgneal@45 2607 // Range constants
bgneal@45 2608 START_TO_START : 0,
bgneal@45 2609 START_TO_END : 1,
bgneal@45 2610 END_TO_END : 2,
bgneal@183 2611 END_TO_START : 3,
bgneal@183 2612
bgneal@183 2613 // Public methods
bgneal@183 2614 setStart : setStart,
bgneal@183 2615 setEnd : setEnd,
bgneal@183 2616 setStartBefore : setStartBefore,
bgneal@183 2617 setStartAfter : setStartAfter,
bgneal@183 2618 setEndBefore : setEndBefore,
bgneal@183 2619 setEndAfter : setEndAfter,
bgneal@183 2620 collapse : collapse,
bgneal@183 2621 selectNode : selectNode,
bgneal@183 2622 selectNodeContents : selectNodeContents,
bgneal@183 2623 compareBoundaryPoints : compareBoundaryPoints,
bgneal@183 2624 deleteContents : deleteContents,
bgneal@183 2625 extractContents : extractContents,
bgneal@183 2626 cloneContents : cloneContents,
bgneal@183 2627 insertNode : insertNode,
bgneal@183 2628 surroundContents : surroundContents,
bgneal@183 2629 cloneRange : cloneRange
bgneal@45 2630 });
bgneal@183 2631
bgneal@183 2632 function setStart(n, o) {
bgneal@183 2633 _setEndPoint(TRUE, n, o);
bgneal@183 2634 };
bgneal@183 2635
bgneal@183 2636 function setEnd(n, o) {
bgneal@183 2637 _setEndPoint(FALSE, n, o);
bgneal@183 2638 };
bgneal@183 2639
bgneal@183 2640 function setStartBefore(n) {
bgneal@183 2641 setStart(n.parentNode, nodeIndex(n));
bgneal@183 2642 };
bgneal@183 2643
bgneal@183 2644 function setStartAfter(n) {
bgneal@183 2645 setStart(n.parentNode, nodeIndex(n) + 1);
bgneal@183 2646 };
bgneal@183 2647
bgneal@183 2648 function setEndBefore(n) {
bgneal@183 2649 setEnd(n.parentNode, nodeIndex(n));
bgneal@183 2650 };
bgneal@183 2651
bgneal@183 2652 function setEndAfter(n) {
bgneal@183 2653 setEnd(n.parentNode, nodeIndex(n) + 1);
bgneal@183 2654 };
bgneal@183 2655
bgneal@183 2656 function collapse(ts) {
bgneal@45 2657 if (ts) {
bgneal@183 2658 t[END_CONTAINER] = t[START_CONTAINER];
bgneal@183 2659 t[END_OFFSET] = t[START_OFFSET];
bgneal@45 2660 } else {
bgneal@183 2661 t[START_CONTAINER] = t[END_CONTAINER];
bgneal@183 2662 t[START_OFFSET] = t[END_OFFSET];
bgneal@183 2663 }
bgneal@183 2664
bgneal@183 2665 t.collapsed = TRUE;
bgneal@183 2666 };
bgneal@183 2667
bgneal@183 2668 function selectNode(n) {
bgneal@183 2669 setStartBefore(n);
bgneal@183 2670 setEndAfter(n);
bgneal@183 2671 };
bgneal@183 2672
bgneal@183 2673 function selectNodeContents(n) {
bgneal@183 2674 setStart(n, 0);
bgneal@183 2675 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
bgneal@183 2676 };
bgneal@183 2677
bgneal@183 2678 function compareBoundaryPoints(h, r) {
bgneal@183 2679 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
bgneal@45 2680
bgneal@45 2681 // Check START_TO_START
bgneal@45 2682 if (h === 0)
bgneal@183 2683 return _compareBoundaryPoints(sc, so, sc, so);
bgneal@45 2684
bgneal@45 2685 // Check START_TO_END
bgneal@45 2686 if (h === 1)
bgneal@183 2687 return _compareBoundaryPoints(sc, so, ec, eo);
bgneal@45 2688
bgneal@45 2689 // Check END_TO_END
bgneal@45 2690 if (h === 2)
bgneal@183 2691 return _compareBoundaryPoints(ec, eo, ec, eo);
bgneal@45 2692
bgneal@45 2693 // Check END_TO_START
bgneal@45 2694 if (h === 3)
bgneal@183 2695 return _compareBoundaryPoints(ec, eo, sc, so);
bgneal@183 2696 };
bgneal@183 2697
bgneal@183 2698 function deleteContents() {
bgneal@183 2699 _traverse(DELETE);
bgneal@183 2700 };
bgneal@183 2701
bgneal@183 2702 function extractContents() {
bgneal@183 2703 return _traverse(EXTRACT);
bgneal@183 2704 };
bgneal@183 2705
bgneal@183 2706 function cloneContents() {
bgneal@183 2707 return _traverse(CLONE);
bgneal@183 2708 };
bgneal@183 2709
bgneal@183 2710 function insertNode(n) {
bgneal@183 2711 var startContainer = this[START_CONTAINER],
bgneal@183 2712 startOffset = this[START_OFFSET], nn, o;
bgneal@45 2713
bgneal@45 2714 // Node is TEXT_NODE or CDATA
bgneal@183 2715 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
bgneal@183 2716 if (!startOffset) {
bgneal@183 2717 // At the start of text
bgneal@183 2718 startContainer.parentNode.insertBefore(n, startContainer);
bgneal@183 2719 } else if (startOffset >= startContainer.nodeValue.length) {
bgneal@183 2720 // At the end of text
bgneal@183 2721 dom.insertAfter(n, startContainer);
bgneal@183 2722 } else {
bgneal@183 2723 // Middle, need to split
bgneal@183 2724 nn = startContainer.splitText(startOffset);
bgneal@183 2725 startContainer.parentNode.insertBefore(n, nn);
bgneal@183 2726 }
bgneal@45 2727 } else {
bgneal@45 2728 // Insert element node
bgneal@183 2729 if (startContainer.childNodes.length > 0)
bgneal@183 2730 o = startContainer.childNodes[startOffset];
bgneal@183 2731
bgneal@183 2732 if (o)
bgneal@183 2733 startContainer.insertBefore(n, o);
bgneal@183 2734 else
bgneal@183 2735 startContainer.appendChild(n);
bgneal@183 2736 }
bgneal@183 2737 };
bgneal@183 2738
bgneal@183 2739 function surroundContents(n) {
bgneal@183 2740 var f = t.extractContents();
bgneal@45 2741
bgneal@45 2742 t.insertNode(n);
bgneal@45 2743 n.appendChild(f);
bgneal@45 2744 t.selectNode(n);
bgneal@183 2745 };
bgneal@183 2746
bgneal@183 2747 function cloneRange() {
bgneal@183 2748 return extend(new Range(dom), {
bgneal@183 2749 startContainer : t[START_CONTAINER],
bgneal@183 2750 startOffset : t[START_OFFSET],
bgneal@183 2751 endContainer : t[END_CONTAINER],
bgneal@183 2752 endOffset : t[END_OFFSET],
bgneal@45 2753 collapsed : t.collapsed,
bgneal@45 2754 commonAncestorContainer : t.commonAncestorContainer
bgneal@45 2755 });
bgneal@183 2756 };
bgneal@183 2757
bgneal@183 2758 // Private methods
bgneal@183 2759
bgneal@183 2760 function _getSelectedNode(container, offset) {
bgneal@183 2761 var child;
bgneal@183 2762
bgneal@183 2763 if (container.nodeType == 3 /* TEXT_NODE */)
bgneal@183 2764 return container;
bgneal@183 2765
bgneal@183 2766 if (offset < 0)
bgneal@183 2767 return container;
bgneal@183 2768
bgneal@183 2769 child = container.firstChild;
bgneal@183 2770 while (child && offset > 0) {
bgneal@183 2771 --offset;
bgneal@183 2772 child = child.nextSibling;
bgneal@183 2773 }
bgneal@183 2774
bgneal@183 2775 if (child)
bgneal@183 2776 return child;
bgneal@183 2777
bgneal@183 2778 return container;
bgneal@183 2779 };
bgneal@183 2780
bgneal@183 2781 function _isCollapsed() {
bgneal@183 2782 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
bgneal@183 2783 };
bgneal@183 2784
bgneal@183 2785 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
bgneal@45 2786 var c, offsetC, n, cmnRoot, childA, childB;
bgneal@45 2787
bgneal@183 2788 // In the first case the boundary-points have the same container. A is before B
bgneal@183 2789 // if its offset is less than the offset of B, A is equal to B if its offset is
bgneal@183 2790 // equal to the offset of B, and A is after B if its offset is greater than the
bgneal@45 2791 // offset of B.
bgneal@45 2792 if (containerA == containerB) {
bgneal@183 2793 if (offsetA == offsetB)
bgneal@45 2794 return 0; // equal
bgneal@183 2795
bgneal@183 2796 if (offsetA < offsetB)
bgneal@45 2797 return -1; // before
bgneal@183 2798
bgneal@183 2799 return 1; // after
bgneal@183 2800 }
bgneal@183 2801
bgneal@183 2802 // In the second case a child node C of the container of A is an ancestor
bgneal@183 2803 // container of B. In this case, A is before B if the offset of A is less than or
bgneal@45 2804 // equal to the index of the child node C and A is after B otherwise.
bgneal@45 2805 c = containerB;
bgneal@183 2806 while (c && c.parentNode != containerA)
bgneal@45 2807 c = c.parentNode;
bgneal@183 2808
bgneal@45 2809 if (c) {
bgneal@45 2810 offsetC = 0;
bgneal@45 2811 n = containerA.firstChild;
bgneal@45 2812
bgneal@45 2813 while (n != c && offsetC < offsetA) {
bgneal@45 2814 offsetC++;
bgneal@45 2815 n = n.nextSibling;
bgneal@45 2816 }
bgneal@45 2817
bgneal@183 2818 if (offsetA <= offsetC)
bgneal@45 2819 return -1; // before
bgneal@183 2820
bgneal@183 2821 return 1; // after
bgneal@183 2822 }
bgneal@183 2823
bgneal@183 2824 // In the third case a child node C of the container of B is an ancestor container
bgneal@183 2825 // of A. In this case, A is before B if the index of the child node C is less than
bgneal@45 2826 // the offset of B and A is after B otherwise.
bgneal@45 2827 c = containerA;
bgneal@45 2828 while (c && c.parentNode != containerB) {
bgneal@45 2829 c = c.parentNode;
bgneal@45 2830 }
bgneal@45 2831
bgneal@45 2832 if (c) {
bgneal@45 2833 offsetC = 0;
bgneal@45 2834 n = containerB.firstChild;
bgneal@45 2835
bgneal@45 2836 while (n != c && offsetC < offsetB) {
bgneal@45 2837 offsetC++;
bgneal@45 2838 n = n.nextSibling;
bgneal@45 2839 }
bgneal@45 2840
bgneal@183 2841 if (offsetC < offsetB)
bgneal@45 2842 return -1; // before
bgneal@183 2843
bgneal@183 2844 return 1; // after
bgneal@183 2845 }
bgneal@183 2846
bgneal@183 2847 // In the fourth case, none of three other cases hold: the containers of A and B
bgneal@183 2848 // are siblings or descendants of sibling nodes. In this case, A is before B if
bgneal@45 2849 // the container of A is before the container of B in a pre-order traversal of the
bgneal@45 2850 // Ranges' context tree and A is after B otherwise.
bgneal@183 2851 cmnRoot = dom.findCommonAncestor(containerA, containerB);
bgneal@45 2852 childA = containerA;
bgneal@45 2853
bgneal@183 2854 while (childA && childA.parentNode != cmnRoot)
bgneal@183 2855 childA = childA.parentNode;
bgneal@183 2856
bgneal@183 2857 if (!childA)
bgneal@45 2858 childA = cmnRoot;
bgneal@45 2859
bgneal@45 2860 childB = containerB;
bgneal@183 2861 while (childB && childB.parentNode != cmnRoot)
bgneal@45 2862 childB = childB.parentNode;
bgneal@183 2863
bgneal@183 2864 if (!childB)
bgneal@45 2865 childB = cmnRoot;
bgneal@183 2866
bgneal@183 2867 if (childA == childB)
bgneal@45 2868 return 0; // equal
bgneal@45 2869
bgneal@45 2870 n = cmnRoot.firstChild;
bgneal@45 2871 while (n) {
bgneal@183 2872 if (n == childA)
bgneal@45 2873 return -1; // before
bgneal@183 2874
bgneal@183 2875 if (n == childB)
bgneal@45 2876 return 1; // after
bgneal@45 2877
bgneal@45 2878 n = n.nextSibling;
bgneal@45 2879 }
bgneal@183 2880 };
bgneal@183 2881
bgneal@183 2882 function _setEndPoint(st, n, o) {
bgneal@183 2883 var ec, sc;
bgneal@45 2884
bgneal@45 2885 if (st) {
bgneal@183 2886 t[START_CONTAINER] = n;
bgneal@183 2887 t[START_OFFSET] = o;
bgneal@45 2888 } else {
bgneal@183 2889 t[END_CONTAINER] = n;
bgneal@183 2890 t[END_OFFSET] = o;
bgneal@183 2891 }
bgneal@183 2892
bgneal@183 2893 // If one boundary-point of a Range is set to have a root container
bgneal@183 2894 // other than the current one for the Range, the Range is collapsed to
bgneal@45 2895 // the new position. This enforces the restriction that both boundary-
bgneal@45 2896 // points of a Range must have the same root container.
bgneal@183 2897 ec = t[END_CONTAINER];
bgneal@45 2898 while (ec.parentNode)
bgneal@45 2899 ec = ec.parentNode;
bgneal@45 2900
bgneal@183 2901 sc = t[START_CONTAINER];
bgneal@45 2902 while (sc.parentNode)
bgneal@45 2903 sc = sc.parentNode;
bgneal@45 2904
bgneal@183 2905 if (sc == ec) {
bgneal@183 2906 // The start position of a Range is guaranteed to never be after the
bgneal@183 2907 // end position. To enforce this restriction, if the start is set to
bgneal@183 2908 // be at a position after the end, the Range is collapsed to that
bgneal@183 2909 // position.
bgneal@183 2910 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
bgneal@183 2911 t.collapse(st);
bgneal@183 2912 } else
bgneal@45 2913 t.collapse(st);
bgneal@183 2914
bgneal@183 2915 t.collapsed = _isCollapsed();
bgneal@183 2916 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
bgneal@183 2917 };
bgneal@183 2918
bgneal@183 2919 function _traverse(how) {
bgneal@183 2920 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
bgneal@183 2921
bgneal@183 2922 if (t[START_CONTAINER] == t[END_CONTAINER])
bgneal@183 2923 return _traverseSameContainer(how);
bgneal@183 2924
bgneal@183 2925 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@183 2926 if (p == t[START_CONTAINER])
bgneal@183 2927 return _traverseCommonStartContainer(c, how);
bgneal@45 2928
bgneal@45 2929 ++endContainerDepth;
bgneal@45 2930 }
bgneal@45 2931
bgneal@183 2932 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@183 2933 if (p == t[END_CONTAINER])
bgneal@183 2934 return _traverseCommonEndContainer(c, how);
bgneal@45 2935
bgneal@45 2936 ++startContainerDepth;
bgneal@45 2937 }
bgneal@45 2938
bgneal@45 2939 depthDiff = startContainerDepth - endContainerDepth;
bgneal@45 2940
bgneal@183 2941 startNode = t[START_CONTAINER];
bgneal@45 2942 while (depthDiff > 0) {
bgneal@45 2943 startNode = startNode.parentNode;
bgneal@45 2944 depthDiff--;
bgneal@45 2945 }
bgneal@45 2946
bgneal@183 2947 endNode = t[END_CONTAINER];
bgneal@45 2948 while (depthDiff < 0) {
bgneal@45 2949 endNode = endNode.parentNode;
bgneal@45 2950 depthDiff++;
bgneal@45 2951 }
bgneal@45 2952
bgneal@45 2953 // ascend the ancestor hierarchy until we have a common parent.
bgneal@45 2954 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
bgneal@45 2955 startNode = sp;
bgneal@45 2956 endNode = ep;
bgneal@45 2957 }
bgneal@45 2958
bgneal@183 2959 return _traverseCommonAncestors(startNode, endNode, how);
bgneal@183 2960 };
bgneal@183 2961
bgneal@183 2962 function _traverseSameContainer(how) {
bgneal@183 2963 var frag, s, sub, n, cnt, sibling, xferNode;
bgneal@45 2964
bgneal@45 2965 if (how != DELETE)
bgneal@183 2966 frag = doc.createDocumentFragment();
bgneal@45 2967
bgneal@45 2968 // If selection is empty, just return the fragment
bgneal@183 2969 if (t[START_OFFSET] == t[END_OFFSET])
bgneal@45 2970 return frag;
bgneal@45 2971
bgneal@45 2972 // Text node needs special case handling
bgneal@183 2973 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
bgneal@45 2974 // get the substring
bgneal@183 2975 s = t[START_CONTAINER].nodeValue;
bgneal@183 2976 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
bgneal@45 2977
bgneal@45 2978 // set the original text node to its new value
bgneal@45 2979 if (how != CLONE) {
bgneal@183 2980 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
bgneal@45 2981
bgneal@45 2982 // Nothing is partially selected, so collapse to start point
bgneal@183 2983 t.collapse(TRUE);
bgneal@45 2984 }
bgneal@45 2985
bgneal@45 2986 if (how == DELETE)
bgneal@183 2987 return;
bgneal@183 2988
bgneal@183 2989 frag.appendChild(doc.createTextNode(sub));
bgneal@45 2990 return frag;
bgneal@45 2991 }
bgneal@45 2992
bgneal@45 2993 // Copy nodes between the start/end offsets.
bgneal@183 2994 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
bgneal@183 2995 cnt = t[END_OFFSET] - t[START_OFFSET];
bgneal@45 2996
bgneal@45 2997 while (cnt > 0) {
bgneal@45 2998 sibling = n.nextSibling;
bgneal@183 2999 xferNode = _traverseFullySelected(n, how);
bgneal@45 3000
bgneal@45 3001 if (frag)
bgneal@45 3002 frag.appendChild( xferNode );
bgneal@45 3003
bgneal@45 3004 --cnt;
bgneal@45 3005 n = sibling;
bgneal@45 3006 }
bgneal@45 3007
bgneal@45 3008 // Nothing is partially selected, so collapse to start point
bgneal@45 3009 if (how != CLONE)
bgneal@183 3010 t.collapse(TRUE);
bgneal@45 3011
bgneal@45 3012 return frag;
bgneal@183 3013 };
bgneal@183 3014
bgneal@183 3015 function _traverseCommonStartContainer(endAncestor, how) {
bgneal@183 3016 var frag, n, endIdx, cnt, sibling, xferNode;
bgneal@45 3017
bgneal@45 3018 if (how != DELETE)
bgneal@183 3019 frag = doc.createDocumentFragment();
bgneal@183 3020
bgneal@183 3021 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 3022
bgneal@45 3023 if (frag)
bgneal@45 3024 frag.appendChild(n);
bgneal@45 3025
bgneal@183 3026 endIdx = nodeIndex(endAncestor);
bgneal@183 3027 cnt = endIdx - t[START_OFFSET];
bgneal@45 3028
bgneal@45 3029 if (cnt <= 0) {
bgneal@183 3030 // Collapse to just before the endAncestor, which
bgneal@45 3031 // is partially selected.
bgneal@45 3032 if (how != CLONE) {
bgneal@45 3033 t.setEndBefore(endAncestor);
bgneal@183 3034 t.collapse(FALSE);
bgneal@45 3035 }
bgneal@45 3036
bgneal@45 3037 return frag;
bgneal@45 3038 }
bgneal@45 3039
bgneal@45 3040 n = endAncestor.previousSibling;
bgneal@45 3041 while (cnt > 0) {
bgneal@45 3042 sibling = n.previousSibling;
bgneal@183 3043 xferNode = _traverseFullySelected(n, how);
bgneal@45 3044
bgneal@45 3045 if (frag)
bgneal@45 3046 frag.insertBefore(xferNode, frag.firstChild);
bgneal@45 3047
bgneal@45 3048 --cnt;
bgneal@45 3049 n = sibling;
bgneal@45 3050 }
bgneal@45 3051
bgneal@183 3052 // Collapse to just before the endAncestor, which
bgneal@45 3053 // is partially selected.
bgneal@45 3054 if (how != CLONE) {
bgneal@45 3055 t.setEndBefore(endAncestor);
bgneal@183 3056 t.collapse(FALSE);
bgneal@45 3057 }
bgneal@45 3058
bgneal@45 3059 return frag;
bgneal@183 3060 };
bgneal@183 3061
bgneal@183 3062 function _traverseCommonEndContainer(startAncestor, how) {
bgneal@183 3063 var frag, startIdx, n, cnt, sibling, xferNode;
bgneal@45 3064
bgneal@45 3065 if (how != DELETE)
bgneal@183 3066 frag = doc.createDocumentFragment();
bgneal@183 3067
bgneal@183 3068 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 3069 if (frag)
bgneal@45 3070 frag.appendChild(n);
bgneal@45 3071
bgneal@183 3072 startIdx = nodeIndex(startAncestor);
bgneal@45 3073 ++startIdx; // Because we already traversed it....
bgneal@45 3074
bgneal@183 3075 cnt = t[END_OFFSET] - startIdx;
bgneal@45 3076 n = startAncestor.nextSibling;
bgneal@45 3077 while (cnt > 0) {
bgneal@45 3078 sibling = n.nextSibling;
bgneal@183 3079 xferNode = _traverseFullySelected(n, how);
bgneal@45 3080
bgneal@45 3081 if (frag)
bgneal@45 3082 frag.appendChild(xferNode);
bgneal@45 3083
bgneal@45 3084 --cnt;
bgneal@45 3085 n = sibling;
bgneal@45 3086 }
bgneal@45 3087
bgneal@45 3088 if (how != CLONE) {
bgneal@45 3089 t.setStartAfter(startAncestor);
bgneal@183 3090 t.collapse(TRUE);
bgneal@45 3091 }
bgneal@45 3092
bgneal@45 3093 return frag;
bgneal@183 3094 };
bgneal@183 3095
bgneal@183 3096 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
bgneal@183 3097 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
bgneal@45 3098
bgneal@45 3099 if (how != DELETE)
bgneal@183 3100 frag = doc.createDocumentFragment();
bgneal@183 3101
bgneal@183 3102 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 3103 if (frag)
bgneal@45 3104 frag.appendChild(n);
bgneal@45 3105
bgneal@45 3106 commonParent = startAncestor.parentNode;
bgneal@183 3107 startOffset = nodeIndex(startAncestor);
bgneal@183 3108 endOffset = nodeIndex(endAncestor);
bgneal@45 3109 ++startOffset;
bgneal@45 3110
bgneal@45 3111 cnt = endOffset - startOffset;
bgneal@45 3112 sibling = startAncestor.nextSibling;
bgneal@45 3113
bgneal@45 3114 while (cnt > 0) {
bgneal@45 3115 nextSibling = sibling.nextSibling;
bgneal@183 3116 n = _traverseFullySelected(sibling, how);
bgneal@45 3117
bgneal@45 3118 if (frag)
bgneal@45 3119 frag.appendChild(n);
bgneal@45 3120
bgneal@45 3121 sibling = nextSibling;
bgneal@45 3122 --cnt;
bgneal@45 3123 }
bgneal@45 3124
bgneal@183 3125 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 3126
bgneal@45 3127 if (frag)
bgneal@45 3128 frag.appendChild(n);
bgneal@45 3129
bgneal@45 3130 if (how != CLONE) {
bgneal@45 3131 t.setStartAfter(startAncestor);
bgneal@183 3132 t.collapse(TRUE);
bgneal@45 3133 }
bgneal@45 3134
bgneal@45 3135 return frag;
bgneal@183 3136 };
bgneal@183 3137
bgneal@183 3138 function _traverseRightBoundary(root, how) {
bgneal@183 3139 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
bgneal@45 3140
bgneal@45 3141 if (next == root)
bgneal@183 3142 return _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 3143
bgneal@45 3144 parent = next.parentNode;
bgneal@183 3145 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@183 3146
bgneal@183 3147 while (parent) {
bgneal@183 3148 while (next) {
bgneal@45 3149 prevSibling = next.previousSibling;
bgneal@183 3150 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 3151
bgneal@45 3152 if (how != DELETE)
bgneal@45 3153 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
bgneal@45 3154
bgneal@183 3155 isFullySelected = TRUE;
bgneal@45 3156 next = prevSibling;
bgneal@45 3157 }
bgneal@45 3158
bgneal@45 3159 if (parent == root)
bgneal@45 3160 return clonedParent;
bgneal@45 3161
bgneal@45 3162 next = parent.previousSibling;
bgneal@45 3163 parent = parent.parentNode;
bgneal@45 3164
bgneal@183 3165 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@45 3166
bgneal@45 3167 if (how != DELETE)
bgneal@45 3168 clonedGrandParent.appendChild(clonedParent);
bgneal@45 3169
bgneal@45 3170 clonedParent = clonedGrandParent;
bgneal@45 3171 }
bgneal@183 3172 };
bgneal@183 3173
bgneal@183 3174 function _traverseLeftBoundary(root, how) {
bgneal@183 3175 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
bgneal@45 3176
bgneal@45 3177 if (next == root)
bgneal@183 3178 return _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 3179
bgneal@45 3180 parent = next.parentNode;
bgneal@183 3181 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@183 3182
bgneal@183 3183 while (parent) {
bgneal@183 3184 while (next) {
bgneal@45 3185 nextSibling = next.nextSibling;
bgneal@183 3186 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 3187
bgneal@45 3188 if (how != DELETE)
bgneal@45 3189 clonedParent.appendChild(clonedChild);
bgneal@45 3190
bgneal@183 3191 isFullySelected = TRUE;
bgneal@45 3192 next = nextSibling;
bgneal@45 3193 }
bgneal@45 3194
bgneal@45 3195 if (parent == root)
bgneal@45 3196 return clonedParent;
bgneal@45 3197
bgneal@45 3198 next = parent.nextSibling;
bgneal@45 3199 parent = parent.parentNode;
bgneal@45 3200
bgneal@183 3201 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@45 3202
bgneal@45 3203 if (how != DELETE)
bgneal@45 3204 clonedGrandParent.appendChild(clonedParent);
bgneal@45 3205
bgneal@45 3206 clonedParent = clonedGrandParent;
bgneal@45 3207 }
bgneal@183 3208 };
bgneal@183 3209
bgneal@183 3210 function _traverseNode(n, isFullySelected, isLeft, how) {
bgneal@183 3211 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
bgneal@45 3212
bgneal@45 3213 if (isFullySelected)
bgneal@183 3214 return _traverseFullySelected(n, how);
bgneal@45 3215
bgneal@45 3216 if (n.nodeType == 3 /* TEXT_NODE */) {
bgneal@45 3217 txtValue = n.nodeValue;
bgneal@45 3218
bgneal@45 3219 if (isLeft) {
bgneal@183 3220 offset = t[START_OFFSET];
bgneal@45 3221 newNodeValue = txtValue.substring(offset);
bgneal@45 3222 oldNodeValue = txtValue.substring(0, offset);
bgneal@45 3223 } else {
bgneal@183 3224 offset = t[END_OFFSET];
bgneal@45 3225 newNodeValue = txtValue.substring(0, offset);
bgneal@45 3226 oldNodeValue = txtValue.substring(offset);
bgneal@45 3227 }
bgneal@45 3228
bgneal@45 3229 if (how != CLONE)
bgneal@45 3230 n.nodeValue = oldNodeValue;
bgneal@45 3231
bgneal@45 3232 if (how == DELETE)
bgneal@183 3233 return;
bgneal@183 3234
bgneal@183 3235 newNode = n.cloneNode(FALSE);
bgneal@45 3236 newNode.nodeValue = newNodeValue;
bgneal@45 3237
bgneal@45 3238 return newNode;
bgneal@45 3239 }
bgneal@45 3240
bgneal@45 3241 if (how == DELETE)
bgneal@183 3242 return;
bgneal@183 3243
bgneal@183 3244 return n.cloneNode(FALSE);
bgneal@183 3245 };
bgneal@183 3246
bgneal@183 3247 function _traverseFullySelected(n, how) {
bgneal@45 3248 if (how != DELETE)
bgneal@183 3249 return how == CLONE ? n.cloneNode(TRUE) : n;
bgneal@45 3250
bgneal@45 3251 n.parentNode.removeChild(n);
bgneal@183 3252 };
bgneal@183 3253 };
bgneal@45 3254
bgneal@45 3255 ns.Range = Range;
bgneal@45 3256 })(tinymce.dom);
bgneal@183 3257
bgneal@45 3258 (function() {
bgneal@45 3259 function Selection(selection) {
bgneal@183 3260 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
bgneal@183 3261
bgneal@183 3262 // Returns a W3C DOM compatible range object by using the IE Range API
bgneal@45 3263 function getRange() {
bgneal@217 3264 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
bgneal@183 3265
bgneal@183 3266 // If selection is outside the current document just return an empty range
bgneal@183 3267 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
bgneal@183 3268 if (element.ownerDocument != dom.doc)
bgneal@45 3269 return domRange;
bgneal@183 3270
bgneal@183 3271 // Handle control selection or text selection of a image
bgneal@183 3272 if (ieRange.item || !element.hasChildNodes()) {
bgneal@183 3273 domRange.setStart(element.parentNode, dom.nodeIndex(element));
bgneal@183 3274 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
bgneal@183 3275
bgneal@183 3276 return domRange;
bgneal@183 3277 }
bgneal@183 3278
bgneal@183 3279 collapsed = selection.isCollapsed();
bgneal@183 3280
bgneal@217 3281 function findEndPoint(start) {
bgneal@217 3282 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
bgneal@217 3283
bgneal@217 3284 // Setup temp range and collapse it
bgneal@217 3285 checkRng = ieRange.duplicate();
bgneal@217 3286 checkRng.collapse(start);
bgneal@217 3287
bgneal@217 3288 // Create marker and insert it at the end of the endpoints parent
bgneal@217 3289 marker = dom.create('a');
bgneal@217 3290 parent = checkRng.parentElement();
bgneal@247 3291
bgneal@247 3292 // If parent doesn't have any children then set the container to that parent and the index to 0
bgneal@247 3293 if (!parent.hasChildNodes()) {
bgneal@247 3294 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
bgneal@247 3295 return;
bgneal@247 3296 }
bgneal@247 3297
bgneal@217 3298 parent.appendChild(marker);
bgneal@217 3299 checkRng.moveToElementText(marker);
bgneal@217 3300 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@217 3301 if (position > 0) {
bgneal@217 3302 // The position is after the end of the parent element.
bgneal@217 3303 // This is the case where IE puts the caret to the left edge of a table.
bgneal@217 3304 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
bgneal@183 3305 dom.remove(marker);
bgneal@217 3306 return;
bgneal@217 3307 }
bgneal@217 3308
bgneal@217 3309 // Setup node list and endIndex
bgneal@217 3310 nodes = tinymce.grep(parent.childNodes);
bgneal@217 3311 endIndex = nodes.length - 1;
bgneal@217 3312 // Perform a binary search for the position
bgneal@217 3313 while (startIndex <= endIndex) {
bgneal@217 3314 index = Math.floor((startIndex + endIndex) / 2);
bgneal@217 3315
bgneal@217 3316 // Insert marker and check it's position relative to the selection
bgneal@217 3317 parent.insertBefore(marker, nodes[index]);
bgneal@217 3318 checkRng.moveToElementText(marker);
bgneal@217 3319 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@217 3320 if (position > 0) {
bgneal@217 3321 // Marker is to the right
bgneal@217 3322 startIndex = index + 1;
bgneal@217 3323 } else if (position < 0) {
bgneal@217 3324 // Marker is to the left
bgneal@217 3325 endIndex = index - 1;
bgneal@217 3326 } else {
bgneal@217 3327 // Maker is where we are
bgneal@217 3328 found = true;
bgneal@217 3329 break;
bgneal@217 3330 }
bgneal@217 3331 }
bgneal@217 3332
bgneal@217 3333 // Setup container
bgneal@217 3334 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
bgneal@217 3335
bgneal@217 3336 // Handle element selection
bgneal@217 3337 if (container.nodeType == 1) {
bgneal@217 3338 dom.remove(marker);
bgneal@217 3339
bgneal@217 3340 // Find offset and container
bgneal@217 3341 offset = dom.nodeIndex(container);
bgneal@217 3342 container = container.parentNode;
bgneal@217 3343
bgneal@217 3344 // Move the offset if we are setting the end or the position is after an element
bgneal@217 3345 if (!start || index > 0)
bgneal@217 3346 offset++;
bgneal@183 3347 } else {
bgneal@217 3348 // Calculate offset within text node
bgneal@217 3349 if (position > 0 || index == 0) {
bgneal@217 3350 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@217 3351 offset = checkRng.text.length;
bgneal@183 3352 } else {
bgneal@217 3353 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@217 3354 offset = container.nodeValue.length - checkRng.text.length;
bgneal@183 3355 }
bgneal@183 3356
bgneal@183 3357 dom.remove(marker);
bgneal@183 3358 }
bgneal@183 3359
bgneal@217 3360 domRange[start ? 'setStart' : 'setEnd'](container, offset);
bgneal@45 3361 };
bgneal@45 3362
bgneal@217 3363 // Find start point
bgneal@217 3364 findEndPoint(true);
bgneal@217 3365
bgneal@217 3366 // Find end point if needed
bgneal@183 3367 if (!collapsed)
bgneal@217 3368 findEndPoint();
bgneal@183 3369
bgneal@183 3370 return domRange;
bgneal@183 3371 };
bgneal@183 3372
bgneal@183 3373 this.addRange = function(rng) {
bgneal@247 3374 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
bgneal@247 3375
bgneal@247 3376 function setEndPoint(start) {
bgneal@247 3377 var container, offset, marker, tmpRng, nodes;
bgneal@247 3378
bgneal@247 3379 marker = dom.create('a');
bgneal@247 3380 container = start ? startContainer : endContainer;
bgneal@247 3381 offset = start ? startOffset : endOffset;
bgneal@247 3382 tmpRng = ieRng.duplicate();
bgneal@247 3383
bgneal@247 3384 if (container == doc) {
bgneal@247 3385 container = body;
bgneal@247 3386 offset = 0;
bgneal@247 3387 }
bgneal@247 3388
bgneal@247 3389 if (container.nodeType == 3) {
bgneal@247 3390 container.parentNode.insertBefore(marker, container);
bgneal@247 3391 tmpRng.moveToElementText(marker);
bgneal@247 3392 tmpRng.moveStart('character', offset);
bgneal@247 3393 dom.remove(marker);
bgneal@247 3394 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@247 3395 } else {
bgneal@247 3396 nodes = container.childNodes;
bgneal@247 3397
bgneal@247 3398 if (nodes.length) {
bgneal@247 3399 if (offset >= nodes.length) {
bgneal@247 3400 dom.insertAfter(marker, nodes[nodes.length - 1]);
bgneal@247 3401 } else {
bgneal@247 3402 container.insertBefore(marker, nodes[offset]);
bgneal@247 3403 }
bgneal@247 3404
bgneal@247 3405 tmpRng.moveToElementText(marker);
bgneal@247 3406 } else {
bgneal@247 3407 // Empty node selection for example <div>|</div>
bgneal@247 3408 marker = doc.createTextNode(invisibleChar);
bgneal@247 3409 container.appendChild(marker);
bgneal@247 3410 tmpRng.moveToElementText(marker.parentNode);
bgneal@247 3411 tmpRng.collapse(TRUE);
bgneal@247 3412 }
bgneal@247 3413
bgneal@247 3414 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@247 3415 dom.remove(marker);
bgneal@247 3416 }
bgneal@247 3417 }
bgneal@247 3418
bgneal@247 3419 // Destroy cached range
bgneal@183 3420 this.destroy();
bgneal@183 3421
bgneal@183 3422 // Setup some shorter versions
bgneal@247 3423 startContainer = rng.startContainer;
bgneal@247 3424 startOffset = rng.startOffset;
bgneal@247 3425 endContainer = rng.endContainer;
bgneal@247 3426 endOffset = rng.endOffset;
bgneal@183 3427 ieRng = body.createTextRange();
bgneal@183 3428
bgneal@247 3429 // If single element selection then try making a control selection out of it
bgneal@247 3430 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
bgneal@247 3431 if (startOffset == endOffset - 1) {
bgneal@247 3432 try {
bgneal@247 3433 ctrlRng = body.createControlRange();
bgneal@247 3434 ctrlRng.addElement(startContainer.childNodes[startOffset]);
bgneal@247 3435 ctrlRng.select();
bgneal@247 3436 ctrlRng.scrollIntoView();
bgneal@247 3437 return;
bgneal@247 3438 } catch (ex) {
bgneal@247 3439 // Ignore
bgneal@247 3440 }
bgneal@247 3441 }
bgneal@247 3442 }
bgneal@247 3443
bgneal@247 3444 // Set start/end point of selection
bgneal@247 3445 setEndPoint(true);
bgneal@247 3446 setEndPoint();
bgneal@247 3447
bgneal@247 3448 // Select the new range and scroll it into view
bgneal@183 3449 ieRng.select();
bgneal@183 3450 ieRng.scrollIntoView();
bgneal@183 3451 };
bgneal@183 3452
bgneal@183 3453 this.getRangeAt = function() {
bgneal@183 3454 // Setup new range if the cache is empty
bgneal@217 3455 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
bgneal@183 3456 range = getRange();
bgneal@183 3457
bgneal@183 3458 // Store away text range for next call
bgneal@183 3459 lastIERng = selection.getRng();
bgneal@183 3460 }
bgneal@183 3461
bgneal@217 3462 // IE will say that the range is equal then produce an invalid argument exception
bgneal@217 3463 // if you perform specific operations in a keyup event. For example Ctrl+Del.
bgneal@217 3464 // This hack will invalidate the range cache if the exception occurs
bgneal@217 3465 try {
bgneal@217 3466 range.startContainer.nextSibling;
bgneal@217 3467 } catch (ex) {
bgneal@217 3468 range = getRange();
bgneal@217 3469 lastIERng = null;
bgneal@217 3470 }
bgneal@217 3471
bgneal@183 3472 // Return cached range
bgneal@183 3473 return range;
bgneal@183 3474 };
bgneal@183 3475
bgneal@183 3476 this.destroy = function() {
bgneal@183 3477 // Destroy cached range and last IE range to avoid memory leaks
bgneal@183 3478 lastIERng = range = null;
bgneal@183 3479 };
bgneal@183 3480
bgneal@183 3481 // 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@183 3482 if (selection.dom.boxModel) {
bgneal@183 3483 (function() {
bgneal@183 3484 var doc = dom.doc, body = doc.body, started, startRng;
bgneal@183 3485
bgneal@183 3486 // Make HTML element unselectable since we are going to handle selection by hand
bgneal@183 3487 doc.documentElement.unselectable = TRUE;
bgneal@183 3488
bgneal@183 3489 // Return range from point or null if it failed
bgneal@183 3490 function rngFromPoint(x, y) {
bgneal@183 3491 var rng = body.createTextRange();
bgneal@183 3492
bgneal@183 3493 try {
bgneal@183 3494 rng.moveToPoint(x, y);
bgneal@183 3495 } catch (ex) {
bgneal@183 3496 // IE sometimes throws and exception, so lets just ignore it
bgneal@183 3497 rng = null;
bgneal@183 3498 }
bgneal@183 3499
bgneal@183 3500 return rng;
bgneal@183 3501 };
bgneal@183 3502
bgneal@183 3503 // Fires while the selection is changing
bgneal@183 3504 function selectionChange(e) {
bgneal@183 3505 var pointRng;
bgneal@183 3506
bgneal@183 3507 // Check if the button is down or not
bgneal@183 3508 if (e.button) {
bgneal@183 3509 // Create range from mouse position
bgneal@183 3510 pointRng = rngFromPoint(e.x, e.y);
bgneal@183 3511
bgneal@183 3512 if (pointRng) {
bgneal@183 3513 // Check if pointRange is before/after selection then change the endPoint
bgneal@183 3514 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
bgneal@183 3515 pointRng.setEndPoint('StartToStart', startRng);
bgneal@183 3516 else
bgneal@183 3517 pointRng.setEndPoint('EndToEnd', startRng);
bgneal@183 3518
bgneal@183 3519 pointRng.select();
bgneal@183 3520 }
bgneal@45 3521 } else
bgneal@183 3522 endSelection();
bgneal@183 3523 }
bgneal@183 3524
bgneal@183 3525 // Removes listeners
bgneal@183 3526 function endSelection() {
bgneal@183 3527 dom.unbind(doc, 'mouseup', endSelection);
bgneal@183 3528 dom.unbind(doc, 'mousemove', selectionChange);
bgneal@183 3529 started = 0;
bgneal@183 3530 };
bgneal@183 3531
bgneal@183 3532 // Detect when user selects outside BODY
bgneal@183 3533 dom.bind(doc, 'mousedown', function(e) {
bgneal@183 3534 if (e.target.nodeName === 'HTML') {
bgneal@183 3535 if (started)
bgneal@183 3536 endSelection();
bgneal@183 3537
bgneal@183 3538 started = 1;
bgneal@183 3539
bgneal@183 3540 // Setup start position
bgneal@183 3541 startRng = rngFromPoint(e.x, e.y);
bgneal@183 3542 if (startRng) {
bgneal@183 3543 // Listen for selection change events
bgneal@183 3544 dom.bind(doc, 'mouseup', endSelection);
bgneal@183 3545 dom.bind(doc, 'mousemove', selectionChange);
bgneal@183 3546
bgneal@183 3547 startRng.select();
bgneal@183 3548 }
bgneal@183 3549 }
bgneal@183 3550 });
bgneal@183 3551 })();
bgneal@183 3552 }
bgneal@45 3553 };
bgneal@45 3554
bgneal@45 3555 // Expose the selection object
bgneal@45 3556 tinymce.dom.TridentSelection = Selection;
bgneal@45 3557 })();
bgneal@45 3558
bgneal@183 3559
bgneal@45 3560 /*
bgneal@45 3561 * Sizzle CSS Selector Engine - v1.0
bgneal@45 3562 * Copyright 2009, The Dojo Foundation
bgneal@45 3563 * Released under the MIT, BSD, and GPL Licenses.
bgneal@45 3564 * More information: http://sizzlejs.com/
bgneal@45 3565 */
bgneal@45 3566 (function(){
bgneal@45 3567
bgneal@247 3568 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
bgneal@45 3569 done = 0,
bgneal@45 3570 toString = Object.prototype.toString,
bgneal@247 3571 hasDuplicate = false,
bgneal@247 3572 baseHasDuplicate = true;
bgneal@247 3573
bgneal@247 3574 // Here we check if the JavaScript engine is using some sort of
bgneal@247 3575 // optimization where it does not always call our comparision
bgneal@247 3576 // function. If that is the case, discard the hasDuplicate value.
bgneal@247 3577 // Thus far that includes Google Chrome.
bgneal@247 3578 [0, 0].sort(function(){
bgneal@247 3579 baseHasDuplicate = false;
bgneal@247 3580 return 0;
bgneal@247 3581 });
bgneal@45 3582
bgneal@45 3583 var Sizzle = function(selector, context, results, seed) {
bgneal@45 3584 results = results || [];
bgneal@247 3585 context = context || document;
bgneal@247 3586
bgneal@247 3587 var origContext = context;
bgneal@45 3588
bgneal@45 3589 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
bgneal@45 3590 return [];
bgneal@45 3591 }
bgneal@45 3592
bgneal@45 3593 if ( !selector || typeof selector !== "string" ) {
bgneal@45 3594 return results;
bgneal@45 3595 }
bgneal@45 3596
bgneal@247 3597 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
bgneal@247 3598 soFar = selector, ret, cur, pop, i;
bgneal@45 3599
bgneal@45 3600 // Reset the position of the chunker regexp (start from head)
bgneal@247 3601 do {
bgneal@247 3602 chunker.exec("");
bgneal@247 3603 m = chunker.exec(soFar);
bgneal@247 3604
bgneal@247 3605 if ( m ) {
bgneal@247 3606 soFar = m[3];
bgneal@45 3607
bgneal@247 3608 parts.push( m[1] );
bgneal@247 3609
bgneal@247 3610 if ( m[2] ) {
bgneal@247 3611 extra = m[3];
bgneal@247 3612 break;
bgneal@247 3613 }
bgneal@45 3614 }
bgneal@247 3615 } while ( m );
bgneal@45 3616
bgneal@45 3617 if ( parts.length > 1 && origPOS.exec( selector ) ) {
bgneal@45 3618 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
bgneal@45 3619 set = posProcess( parts[0] + parts[1], context );
bgneal@45 3620 } else {
bgneal@45 3621 set = Expr.relative[ parts[0] ] ?
bgneal@45 3622 [ context ] :
bgneal@45 3623 Sizzle( parts.shift(), context );
bgneal@45 3624
bgneal@45 3625 while ( parts.length ) {
bgneal@45 3626 selector = parts.shift();
bgneal@45 3627
bgneal@247 3628 if ( Expr.relative[ selector ] ) {
bgneal@45 3629 selector += parts.shift();
bgneal@247 3630 }
bgneal@247 3631
bgneal@45 3632 set = posProcess( selector, set );
bgneal@45 3633 }
bgneal@45 3634 }
bgneal@45 3635 } else {
bgneal@45 3636 // Take a shortcut and set the context if the root selector is an ID
bgneal@45 3637 // (but not if it'll be faster if the inner selector is an ID)
bgneal@45 3638 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
bgneal@45 3639 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
bgneal@247 3640 ret = Sizzle.find( parts.shift(), context, contextXML );
bgneal@45 3641 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
bgneal@45 3642 }
bgneal@45 3643
bgneal@45 3644 if ( context ) {
bgneal@247 3645 ret = seed ?
bgneal@45 3646 { expr: parts.pop(), set: makeArray(seed) } :
bgneal@45 3647 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
bgneal@45 3648 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
bgneal@45 3649
bgneal@45 3650 if ( parts.length > 0 ) {
bgneal@45 3651 checkSet = makeArray(set);
bgneal@45 3652 } else {
bgneal@45 3653 prune = false;
bgneal@45 3654 }
bgneal@45 3655
bgneal@45 3656 while ( parts.length ) {
bgneal@247 3657 cur = parts.pop();
bgneal@247 3658 pop = cur;
bgneal@45 3659
bgneal@45 3660 if ( !Expr.relative[ cur ] ) {
bgneal@45 3661 cur = "";
bgneal@45 3662 } else {
bgneal@45 3663 pop = parts.pop();
bgneal@45 3664 }
bgneal@45 3665
bgneal@45 3666 if ( pop == null ) {
bgneal@45 3667 pop = context;
bgneal@45 3668 }
bgneal@45 3669
bgneal@45 3670 Expr.relative[ cur ]( checkSet, pop, contextXML );
bgneal@45 3671 }
bgneal@45 3672 } else {
bgneal@45 3673 checkSet = parts = [];
bgneal@45 3674 }
bgneal@45 3675 }
bgneal@45 3676
bgneal@45 3677 if ( !checkSet ) {
bgneal@45 3678 checkSet = set;
bgneal@45 3679 }
bgneal@45 3680
bgneal@45 3681 if ( !checkSet ) {
bgneal@247 3682 Sizzle.error( cur || selector );
bgneal@45 3683 }
bgneal@45 3684
bgneal@45 3685 if ( toString.call(checkSet) === "[object Array]" ) {
bgneal@45 3686 if ( !prune ) {
bgneal@183 3687 results.push.apply( results, checkSet );
bgneal@45 3688 } else if ( context && context.nodeType === 1 ) {
bgneal@247 3689 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@247 3690 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
bgneal@183 3691 results.push( set[i] );
bgneal@45 3692 }
bgneal@45 3693 }
bgneal@45 3694 } else {
bgneal@247 3695 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@45 3696 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
bgneal@183 3697 results.push( set[i] );
bgneal@45 3698 }
bgneal@45 3699 }
bgneal@45 3700 }
bgneal@45 3701 } else {
bgneal@45 3702 makeArray( checkSet, results );
bgneal@45 3703 }
bgneal@45 3704
bgneal@45 3705 if ( extra ) {
bgneal@45 3706 Sizzle( extra, origContext, results, seed );
bgneal@45 3707 Sizzle.uniqueSort( results );
bgneal@45 3708 }
bgneal@45 3709
bgneal@45 3710 return results;
bgneal@45 3711 };
bgneal@45 3712
bgneal@45 3713 Sizzle.uniqueSort = function(results){
bgneal@45 3714 if ( sortOrder ) {
bgneal@247 3715 hasDuplicate = baseHasDuplicate;
bgneal@183 3716 results.sort(sortOrder);
bgneal@45 3717
bgneal@45 3718 if ( hasDuplicate ) {
bgneal@45 3719 for ( var i = 1; i < results.length; i++ ) {
bgneal@45 3720 if ( results[i] === results[i-1] ) {
bgneal@183 3721 results.splice(i--, 1);
bgneal@45 3722 }
bgneal@45 3723 }
bgneal@45 3724 }
bgneal@45 3725 }
bgneal@247 3726
bgneal@247 3727 return results;
bgneal@45 3728 };
bgneal@45 3729
bgneal@45 3730 Sizzle.matches = function(expr, set){
bgneal@45 3731 return Sizzle(expr, null, null, set);
bgneal@45 3732 };
bgneal@45 3733
bgneal@45 3734 Sizzle.find = function(expr, context, isXML){
bgneal@247 3735 var set;
bgneal@45 3736
bgneal@45 3737 if ( !expr ) {
bgneal@45 3738 return [];
bgneal@45 3739 }
bgneal@45 3740
bgneal@45 3741 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
bgneal@45 3742 var type = Expr.order[i], match;
bgneal@45 3743
bgneal@247 3744 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
bgneal@247 3745 var left = match[1];
bgneal@247 3746 match.splice(1,1);
bgneal@45 3747
bgneal@45 3748 if ( left.substr( left.length - 1 ) !== "\\" ) {
bgneal@45 3749 match[1] = (match[1] || "").replace(/\\/g, "");
bgneal@45 3750 set = Expr.find[ type ]( match, context, isXML );
bgneal@45 3751 if ( set != null ) {
bgneal@45 3752 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 3753 break;
bgneal@45 3754 }
bgneal@45 3755 }
bgneal@45 3756 }
bgneal@45 3757 }
bgneal@45 3758
bgneal@45 3759 if ( !set ) {
bgneal@45 3760 set = context.getElementsByTagName("*");
bgneal@45 3761 }
bgneal@45 3762
bgneal@45 3763 return {set: set, expr: expr};
bgneal@45 3764 };
bgneal@45 3765
bgneal@45 3766 Sizzle.filter = function(expr, set, inplace, not){
bgneal@45 3767 var old = expr, result = [], curLoop = set, match, anyFound,
bgneal@247 3768 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
bgneal@45 3769
bgneal@45 3770 while ( expr && set.length ) {
bgneal@45 3771 for ( var type in Expr.filter ) {
bgneal@247 3772 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
bgneal@247 3773 var filter = Expr.filter[ type ], found, item, left = match[1];
bgneal@45 3774 anyFound = false;
bgneal@45 3775
bgneal@247 3776 match.splice(1,1);
bgneal@247 3777
bgneal@247 3778 if ( left.substr( left.length - 1 ) === "\\" ) {
bgneal@247 3779 continue;
bgneal@247 3780 }
bgneal@247 3781
bgneal@247 3782 if ( curLoop === result ) {
bgneal@45 3783 result = [];
bgneal@45 3784 }
bgneal@45 3785
bgneal@45 3786 if ( Expr.preFilter[ type ] ) {
bgneal@45 3787 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
bgneal@45 3788
bgneal@45 3789 if ( !match ) {
bgneal@45 3790 anyFound = found = true;
bgneal@45 3791 } else if ( match === true ) {
bgneal@45 3792 continue;
bgneal@45 3793 }
bgneal@45 3794 }
bgneal@45 3795
bgneal@45 3796 if ( match ) {
bgneal@45 3797 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
bgneal@45 3798 if ( item ) {
bgneal@45 3799 found = filter( item, match, i, curLoop );
bgneal@45 3800 var pass = not ^ !!found;
bgneal@45 3801
bgneal@45 3802 if ( inplace && found != null ) {
bgneal@45 3803 if ( pass ) {
bgneal@45 3804 anyFound = true;
bgneal@45 3805 } else {
bgneal@45 3806 curLoop[i] = false;
bgneal@45 3807 }
bgneal@45 3808 } else if ( pass ) {
bgneal@45 3809 result.push( item );
bgneal@45 3810 anyFound = true;
bgneal@45 3811 }
bgneal@45 3812 }
bgneal@45 3813 }
bgneal@45 3814 }
bgneal@45 3815
bgneal@45 3816 if ( found !== undefined ) {
bgneal@45 3817 if ( !inplace ) {
bgneal@45 3818 curLoop = result;
bgneal@45 3819 }
bgneal@45 3820
bgneal@45 3821 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 3822
bgneal@45 3823 if ( !anyFound ) {
bgneal@45 3824 return [];
bgneal@45 3825 }
bgneal@45 3826
bgneal@45 3827 break;
bgneal@45 3828 }
bgneal@45 3829 }
bgneal@45 3830 }
bgneal@45 3831
bgneal@45 3832 // Improper expression
bgneal@247 3833 if ( expr === old ) {
bgneal@45 3834 if ( anyFound == null ) {
bgneal@247 3835 Sizzle.error( expr );
bgneal@45 3836 } else {
bgneal@45 3837 break;
bgneal@45 3838 }
bgneal@45 3839 }
bgneal@45 3840
bgneal@45 3841 old = expr;
bgneal@45 3842 }
bgneal@45 3843
bgneal@45 3844 return curLoop;
bgneal@45 3845 };
bgneal@45 3846
bgneal@247 3847 Sizzle.error = function( msg ) {
bgneal@247 3848 throw "Syntax error, unrecognized expression: " + msg;
bgneal@247 3849 };
bgneal@247 3850
bgneal@45 3851 var Expr = Sizzle.selectors = {
bgneal@45 3852 order: [ "ID", "NAME", "TAG" ],
bgneal@45 3853 match: {
bgneal@247 3854 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@247 3855 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@247 3856 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
bgneal@247 3857 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
bgneal@247 3858 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
bgneal@247 3859 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
bgneal@247 3860 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
bgneal@247 3861 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
bgneal@45 3862 },
bgneal@247 3863 leftMatch: {},
bgneal@45 3864 attrMap: {
bgneal@45 3865 "class": "className",
bgneal@45 3866 "for": "htmlFor"
bgneal@45 3867 },
bgneal@45 3868 attrHandle: {
bgneal@45 3869 href: function(elem){
bgneal@45 3870 return elem.getAttribute("href");
bgneal@45 3871 }
bgneal@45 3872 },
bgneal@45 3873 relative: {
bgneal@247 3874 "+": function(checkSet, part){
bgneal@45 3875 var isPartStr = typeof part === "string",
bgneal@45 3876 isTag = isPartStr && !/\W/.test(part),
bgneal@45 3877 isPartStrNotTag = isPartStr && !isTag;
bgneal@45 3878
bgneal@247 3879 if ( isTag ) {
bgneal@247 3880 part = part.toLowerCase();
bgneal@45 3881 }
bgneal@45 3882
bgneal@45 3883 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
bgneal@45 3884 if ( (elem = checkSet[i]) ) {
bgneal@45 3885 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
bgneal@45 3886
bgneal@247 3887 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
bgneal@45 3888 elem || false :
bgneal@45 3889 elem === part;
bgneal@45 3890 }
bgneal@45 3891 }
bgneal@45 3892
bgneal@45 3893 if ( isPartStrNotTag ) {
bgneal@45 3894 Sizzle.filter( part, checkSet, true );
bgneal@45 3895 }
bgneal@45 3896 },
bgneal@247 3897 ">": function(checkSet, part){
bgneal@247 3898 var isPartStr = typeof part === "string",
bgneal@247 3899 elem, i = 0, l = checkSet.length;
bgneal@45 3900
bgneal@45 3901 if ( isPartStr && !/\W/.test(part) ) {
bgneal@247 3902 part = part.toLowerCase();
bgneal@247 3903
bgneal@247 3904 for ( ; i < l; i++ ) {
bgneal@247 3905 elem = checkSet[i];
bgneal@45 3906 if ( elem ) {
bgneal@45 3907 var parent = elem.parentNode;
bgneal@247 3908 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
bgneal@45 3909 }
bgneal@45 3910 }
bgneal@45 3911 } else {
bgneal@247 3912 for ( ; i < l; i++ ) {
bgneal@247 3913 elem = checkSet[i];
bgneal@45 3914 if ( elem ) {
bgneal@45 3915 checkSet[i] = isPartStr ?
bgneal@45 3916 elem.parentNode :
bgneal@45 3917 elem.parentNode === part;
bgneal@45 3918 }
bgneal@45 3919 }
bgneal@45 3920
bgneal@45 3921 if ( isPartStr ) {
bgneal@45 3922 Sizzle.filter( part, checkSet, true );
bgneal@45 3923 }
bgneal@45 3924 }
bgneal@45 3925 },
bgneal@45 3926 "": function(checkSet, part, isXML){
bgneal@247 3927 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@247 3928
bgneal@247 3929 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@247 3930 part = part.toLowerCase();
bgneal@247 3931 nodeCheck = part;
bgneal@45 3932 checkFn = dirNodeCheck;
bgneal@45 3933 }
bgneal@45 3934
bgneal@45 3935 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 3936 },
bgneal@45 3937 "~": function(checkSet, part, isXML){
bgneal@247 3938 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@247 3939
bgneal@247 3940 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@247 3941 part = part.toLowerCase();
bgneal@247 3942 nodeCheck = part;
bgneal@45 3943 checkFn = dirNodeCheck;
bgneal@45 3944 }
bgneal@45 3945
bgneal@45 3946 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 3947 }
bgneal@45 3948 },
bgneal@45 3949 find: {
bgneal@45 3950 ID: function(match, context, isXML){
bgneal@45 3951 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 3952 var m = context.getElementById(match[1]);
bgneal@45 3953 return m ? [m] : [];
bgneal@45 3954 }
bgneal@45 3955 },
bgneal@247 3956 NAME: function(match, context){
bgneal@45 3957 if ( typeof context.getElementsByName !== "undefined" ) {
bgneal@45 3958 var ret = [], results = context.getElementsByName(match[1]);
bgneal@45 3959
bgneal@45 3960 for ( var i = 0, l = results.length; i < l; i++ ) {
bgneal@45 3961 if ( results[i].getAttribute("name") === match[1] ) {
bgneal@45 3962 ret.push( results[i] );
bgneal@45 3963 }
bgneal@45 3964 }
bgneal@45 3965
bgneal@45 3966 return ret.length === 0 ? null : ret;
bgneal@45 3967 }
bgneal@45 3968 },
bgneal@45 3969 TAG: function(match, context){
bgneal@45 3970 return context.getElementsByTagName(match[1]);
bgneal@45 3971 }
bgneal@45 3972 },
bgneal@45 3973 preFilter: {
bgneal@45 3974 CLASS: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 3975 match = " " + match[1].replace(/\\/g, "") + " ";
bgneal@45 3976
bgneal@45 3977 if ( isXML ) {
bgneal@45 3978 return match;
bgneal@45 3979 }
bgneal@45 3980
bgneal@45 3981 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
bgneal@45 3982 if ( elem ) {
bgneal@247 3983 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
bgneal@247 3984 if ( !inplace ) {
bgneal@45 3985 result.push( elem );
bgneal@247 3986 }
bgneal@45 3987 } else if ( inplace ) {
bgneal@45 3988 curLoop[i] = false;
bgneal@45 3989 }
bgneal@45 3990 }
bgneal@45 3991 }
bgneal@45 3992
bgneal@45 3993 return false;
bgneal@45 3994 },
bgneal@45 3995 ID: function(match){
bgneal@45 3996 return match[1].replace(/\\/g, "");
bgneal@45 3997 },
bgneal@45 3998 TAG: function(match, curLoop){
bgneal@247 3999 return match[1].toLowerCase();
bgneal@45 4000 },
bgneal@45 4001 CHILD: function(match){
bgneal@247 4002 if ( match[1] === "nth" ) {
bgneal@45 4003 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
bgneal@45 4004 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
bgneal@247 4005 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
bgneal@45 4006 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
bgneal@45 4007
bgneal@45 4008 // calculate the numbers (first)n+(last) including if they are negative
bgneal@45 4009 match[2] = (test[1] + (test[2] || 1)) - 0;
bgneal@45 4010 match[3] = test[3] - 0;
bgneal@45 4011 }
bgneal@45 4012
bgneal@45 4013 // TODO: Move to normal caching system
bgneal@45 4014 match[0] = done++;
bgneal@45 4015
bgneal@45 4016 return match;
bgneal@45 4017 },
bgneal@45 4018 ATTR: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 4019 var name = match[1].replace(/\\/g, "");
bgneal@45 4020
bgneal@45 4021 if ( !isXML && Expr.attrMap[name] ) {
bgneal@45 4022 match[1] = Expr.attrMap[name];
bgneal@45 4023 }
bgneal@45 4024
bgneal@45 4025 if ( match[2] === "~=" ) {
bgneal@45 4026 match[4] = " " + match[4] + " ";
bgneal@45 4027 }
bgneal@45 4028
bgneal@45 4029 return match;
bgneal@45 4030 },
bgneal@45 4031 PSEUDO: function(match, curLoop, inplace, result, not){
bgneal@45 4032 if ( match[1] === "not" ) {
bgneal@45 4033 // If we're dealing with a complex expression, or a simple one
bgneal@247 4034 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
bgneal@45 4035 match[3] = Sizzle(match[3], null, null, curLoop);
bgneal@45 4036 } else {
bgneal@45 4037 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
bgneal@45 4038 if ( !inplace ) {
bgneal@45 4039 result.push.apply( result, ret );
bgneal@45 4040 }
bgneal@45 4041 return false;
bgneal@45 4042 }
bgneal@45 4043 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
bgneal@45 4044 return true;
bgneal@45 4045 }
bgneal@45 4046
bgneal@45 4047 return match;
bgneal@45 4048 },
bgneal@45 4049 POS: function(match){
bgneal@45 4050 match.unshift( true );
bgneal@45 4051 return match;
bgneal@45 4052 }
bgneal@45 4053 },
bgneal@45 4054 filters: {
bgneal@45 4055 enabled: function(elem){
bgneal@45 4056 return elem.disabled === false && elem.type !== "hidden";
bgneal@45 4057 },
bgneal@45 4058 disabled: function(elem){
bgneal@45 4059 return elem.disabled === true;
bgneal@45 4060 },
bgneal@45 4061 checked: function(elem){
bgneal@45 4062 return elem.checked === true;
bgneal@45 4063 },
bgneal@45 4064 selected: function(elem){
bgneal@45 4065 // Accessing this property makes selected-by-default
bgneal@45 4066 // options in Safari work properly
bgneal@45 4067 elem.parentNode.selectedIndex;
bgneal@45 4068 return elem.selected === true;
bgneal@45 4069 },
bgneal@45 4070 parent: function(elem){
bgneal@45 4071 return !!elem.firstChild;
bgneal@45 4072 },
bgneal@45 4073 empty: function(elem){
bgneal@45 4074 return !elem.firstChild;
bgneal@45 4075 },
bgneal@45 4076 has: function(elem, i, match){
bgneal@45 4077 return !!Sizzle( match[3], elem ).length;
bgneal@45 4078 },
bgneal@45 4079 header: function(elem){
bgneal@247 4080 return (/h\d/i).test( elem.nodeName );
bgneal@45 4081 },
bgneal@45 4082 text: function(elem){
bgneal@45 4083 return "text" === elem.type;
bgneal@45 4084 },
bgneal@45 4085 radio: function(elem){
bgneal@45 4086 return "radio" === elem.type;
bgneal@45 4087 },
bgneal@45 4088 checkbox: function(elem){
bgneal@45 4089 return "checkbox" === elem.type;
bgneal@45 4090 },
bgneal@45 4091 file: function(elem){
bgneal@45 4092 return "file" === elem.type;
bgneal@45 4093 },
bgneal@45 4094 password: function(elem){
bgneal@45 4095 return "password" === elem.type;
bgneal@45 4096 },
bgneal@45 4097 submit: function(elem){
bgneal@45 4098 return "submit" === elem.type;
bgneal@45 4099 },
bgneal@45 4100 image: function(elem){
bgneal@45 4101 return "image" === elem.type;
bgneal@45 4102 },
bgneal@45 4103 reset: function(elem){
bgneal@45 4104 return "reset" === elem.type;
bgneal@45 4105 },
bgneal@45 4106 button: function(elem){
bgneal@247 4107 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
bgneal@45 4108 },
bgneal@45 4109 input: function(elem){
bgneal@247 4110 return (/input|select|textarea|button/i).test(elem.nodeName);
bgneal@45 4111 }
bgneal@45 4112 },
bgneal@45 4113 setFilters: {
bgneal@45 4114 first: function(elem, i){
bgneal@45 4115 return i === 0;
bgneal@45 4116 },
bgneal@45 4117 last: function(elem, i, match, array){
bgneal@45 4118 return i === array.length - 1;
bgneal@45 4119 },
bgneal@45 4120 even: function(elem, i){
bgneal@45 4121 return i % 2 === 0;
bgneal@45 4122 },
bgneal@45 4123 odd: function(elem, i){
bgneal@45 4124 return i % 2 === 1;
bgneal@45 4125 },
bgneal@45 4126 lt: function(elem, i, match){
bgneal@45 4127 return i < match[3] - 0;
bgneal@45 4128 },
bgneal@45 4129 gt: function(elem, i, match){
bgneal@45 4130 return i > match[3] - 0;
bgneal@45 4131 },
bgneal@45 4132 nth: function(elem, i, match){
bgneal@247 4133 return match[3] - 0 === i;
bgneal@45 4134 },
bgneal@45 4135 eq: function(elem, i, match){
bgneal@247 4136 return match[3] - 0 === i;
bgneal@45 4137 }
bgneal@45 4138 },
bgneal@45 4139 filter: {
bgneal@45 4140 PSEUDO: function(elem, match, i, array){
bgneal@45 4141 var name = match[1], filter = Expr.filters[ name ];
bgneal@45 4142
bgneal@45 4143 if ( filter ) {
bgneal@45 4144 return filter( elem, i, match, array );
bgneal@45 4145 } else if ( name === "contains" ) {
bgneal@247 4146 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
bgneal@45 4147 } else if ( name === "not" ) {
bgneal@45 4148 var not = match[3];
bgneal@45 4149
bgneal@247 4150 for ( var j = 0, l = not.length; j < l; j++ ) {
bgneal@247 4151 if ( not[j] === elem ) {
bgneal@45 4152 return false;
bgneal@45 4153 }
bgneal@45 4154 }
bgneal@45 4155
bgneal@45 4156 return true;
bgneal@247 4157 } else {
bgneal@247 4158 Sizzle.error( "Syntax error, unrecognized expression: " + name );
bgneal@45 4159 }
bgneal@45 4160 },
bgneal@45 4161 CHILD: function(elem, match){
bgneal@45 4162 var type = match[1], node = elem;
bgneal@45 4163 switch (type) {
bgneal@45 4164 case 'only':
bgneal@45 4165 case 'first':
bgneal@247 4166 while ( (node = node.previousSibling) ) {
bgneal@247 4167 if ( node.nodeType === 1 ) {
bgneal@247 4168 return false;
bgneal@247 4169 }
bgneal@247 4170 }
bgneal@247 4171 if ( type === "first" ) {
bgneal@247 4172 return true;
bgneal@247 4173 }
bgneal@45 4174 node = elem;
bgneal@45 4175 case 'last':
bgneal@247 4176 while ( (node = node.nextSibling) ) {
bgneal@247 4177 if ( node.nodeType === 1 ) {
bgneal@247 4178 return false;
bgneal@247 4179 }
bgneal@45 4180 }
bgneal@45 4181 return true;
bgneal@45 4182 case 'nth':
bgneal@45 4183 var first = match[2], last = match[3];
bgneal@45 4184
bgneal@247 4185 if ( first === 1 && last === 0 ) {
bgneal@45 4186 return true;
bgneal@45 4187 }
bgneal@45 4188
bgneal@45 4189 var doneName = match[0],
bgneal@45 4190 parent = elem.parentNode;
bgneal@45 4191
bgneal@45 4192 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
bgneal@45 4193 var count = 0;
bgneal@45 4194 for ( node = parent.firstChild; node; node = node.nextSibling ) {
bgneal@45 4195 if ( node.nodeType === 1 ) {
bgneal@45 4196 node.nodeIndex = ++count;
bgneal@45 4197 }
bgneal@45 4198 }
bgneal@45 4199 parent.sizcache = doneName;
bgneal@45 4200 }
bgneal@45 4201
bgneal@45 4202 var diff = elem.nodeIndex - last;
bgneal@247 4203 if ( first === 0 ) {
bgneal@247 4204 return diff === 0;
bgneal@45 4205 } else {
bgneal@247 4206 return ( diff % first === 0 && diff / first >= 0 );
bgneal@45 4207 }
bgneal@45 4208 }
bgneal@45 4209 },
bgneal@45 4210 ID: function(elem, match){
bgneal@45 4211 return elem.nodeType === 1 && elem.getAttribute("id") === match;
bgneal@45 4212 },
bgneal@45 4213 TAG: function(elem, match){
bgneal@247 4214 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
bgneal@45 4215 },
bgneal@45 4216 CLASS: function(elem, match){
bgneal@45 4217 return (" " + (elem.className || elem.getAttribute("class")) + " ")
bgneal@45 4218 .indexOf( match ) > -1;
bgneal@45 4219 },
bgneal@45 4220 ATTR: function(elem, match){
bgneal@45 4221 var name = match[1],
bgneal@45 4222 result = Expr.attrHandle[ name ] ?
bgneal@45 4223 Expr.attrHandle[ name ]( elem ) :
bgneal@45 4224 elem[ name ] != null ?
bgneal@45 4225 elem[ name ] :
bgneal@45 4226 elem.getAttribute( name ),
bgneal@45 4227 value = result + "",
bgneal@45 4228 type = match[2],
bgneal@45 4229 check = match[4];
bgneal@45 4230
bgneal@45 4231 return result == null ?
bgneal@45 4232 type === "!=" :
bgneal@45 4233 type === "=" ?
bgneal@45 4234 value === check :
bgneal@45 4235 type === "*=" ?
bgneal@45 4236 value.indexOf(check) >= 0 :
bgneal@45 4237 type === "~=" ?
bgneal@45 4238 (" " + value + " ").indexOf(check) >= 0 :
bgneal@45 4239 !check ?
bgneal@45 4240 value && result !== false :
bgneal@45 4241 type === "!=" ?
bgneal@247 4242 value !== check :
bgneal@45 4243 type === "^=" ?
bgneal@45 4244 value.indexOf(check) === 0 :
bgneal@45 4245 type === "$=" ?
bgneal@45 4246 value.substr(value.length - check.length) === check :
bgneal@45 4247 type === "|=" ?
bgneal@45 4248 value === check || value.substr(0, check.length + 1) === check + "-" :
bgneal@45 4249 false;
bgneal@45 4250 },
bgneal@45 4251 POS: function(elem, match, i, array){
bgneal@45 4252 var name = match[2], filter = Expr.setFilters[ name ];
bgneal@45 4253
bgneal@45 4254 if ( filter ) {
bgneal@45 4255 return filter( elem, i, match, array );
bgneal@45 4256 }
bgneal@45 4257 }
bgneal@45 4258 }
bgneal@45 4259 };
bgneal@45 4260
bgneal@247 4261 var origPOS = Expr.match.POS,
bgneal@247 4262 fescape = function(all, num){
bgneal@247 4263 return "\\" + (num - 0 + 1);
bgneal@247 4264 };
bgneal@45 4265
bgneal@45 4266 for ( var type in Expr.match ) {
bgneal@247 4267 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
bgneal@247 4268 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
bgneal@45 4269 }
bgneal@45 4270
bgneal@45 4271 var makeArray = function(array, results) {
bgneal@247 4272 array = Array.prototype.slice.call( array, 0 );
bgneal@45 4273
bgneal@45 4274 if ( results ) {
bgneal@183 4275 results.push.apply( results, array );
bgneal@45 4276 return results;
bgneal@45 4277 }
bgneal@45 4278
bgneal@45 4279 return array;
bgneal@45 4280 };
bgneal@45 4281
bgneal@45 4282 // Perform a simple check to determine if the browser is capable of
bgneal@45 4283 // converting a NodeList to an array using builtin methods.
bgneal@247 4284 // Also verifies that the returned array holds DOM nodes
bgneal@247 4285 // (which is not the case in the Blackberry browser)
bgneal@45 4286 try {
bgneal@247 4287 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
bgneal@45 4288
bgneal@45 4289 // Provide a fallback method if it does not work
bgneal@45 4290 } catch(e){
bgneal@45 4291 makeArray = function(array, results) {
bgneal@247 4292 var ret = results || [], i = 0;
bgneal@45 4293
bgneal@45 4294 if ( toString.call(array) === "[object Array]" ) {
bgneal@45 4295 Array.prototype.push.apply( ret, array );
bgneal@45 4296 } else {
bgneal@45 4297 if ( typeof array.length === "number" ) {
bgneal@247 4298 for ( var l = array.length; i < l; i++ ) {
bgneal@45 4299 ret.push( array[i] );
bgneal@45 4300 }
bgneal@45 4301 } else {
bgneal@247 4302 for ( ; array[i]; i++ ) {
bgneal@45 4303 ret.push( array[i] );
bgneal@45 4304 }
bgneal@45 4305 }
bgneal@45 4306 }
bgneal@45 4307
bgneal@45 4308 return ret;
bgneal@45 4309 };
bgneal@45 4310 }
bgneal@45 4311
bgneal@45 4312 var sortOrder;
bgneal@45 4313
bgneal@45 4314 if ( document.documentElement.compareDocumentPosition ) {
bgneal@45 4315 sortOrder = function( a, b ) {
bgneal@247 4316 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
bgneal@247 4317 if ( a == b ) {
bgneal@247 4318 hasDuplicate = true;
bgneal@247 4319 }
bgneal@247 4320 return a.compareDocumentPosition ? -1 : 1;
bgneal@247 4321 }
bgneal@247 4322
bgneal@45 4323 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
bgneal@45 4324 if ( ret === 0 ) {
bgneal@45 4325 hasDuplicate = true;
bgneal@45 4326 }
bgneal@45 4327 return ret;
bgneal@45 4328 };
bgneal@45 4329 } else if ( "sourceIndex" in document.documentElement ) {
bgneal@45 4330 sortOrder = function( a, b ) {
bgneal@247 4331 if ( !a.sourceIndex || !b.sourceIndex ) {
bgneal@247 4332 if ( a == b ) {
bgneal@247 4333 hasDuplicate = true;
bgneal@247 4334 }
bgneal@247 4335 return a.sourceIndex ? -1 : 1;
bgneal@247 4336 }
bgneal@247 4337
bgneal@45 4338 var ret = a.sourceIndex - b.sourceIndex;
bgneal@45 4339 if ( ret === 0 ) {
bgneal@45 4340 hasDuplicate = true;
bgneal@45 4341 }
bgneal@45 4342 return ret;
bgneal@45 4343 };
bgneal@45 4344 } else if ( document.createRange ) {
bgneal@45 4345 sortOrder = function( a, b ) {
bgneal@247 4346 if ( !a.ownerDocument || !b.ownerDocument ) {
bgneal@247 4347 if ( a == b ) {
bgneal@247 4348 hasDuplicate = true;
bgneal@247 4349 }
bgneal@247 4350 return a.ownerDocument ? -1 : 1;
bgneal@247 4351 }
bgneal@247 4352
bgneal@45 4353 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
bgneal@183 4354 aRange.setStart(a, 0);
bgneal@183 4355 aRange.setEnd(a, 0);
bgneal@183 4356 bRange.setStart(b, 0);
bgneal@183 4357 bRange.setEnd(b, 0);
bgneal@45 4358 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
bgneal@45 4359 if ( ret === 0 ) {
bgneal@45 4360 hasDuplicate = true;
bgneal@45 4361 }
bgneal@45 4362 return ret;
bgneal@45 4363 };
bgneal@45 4364 }
bgneal@45 4365
bgneal@247 4366 // Utility function for retreiving the text value of an array of DOM nodes
bgneal@247 4367 Sizzle.getText = function( elems ) {
bgneal@247 4368 var ret = "", elem;
bgneal@247 4369
bgneal@247 4370 for ( var i = 0; elems[i]; i++ ) {
bgneal@247 4371 elem = elems[i];
bgneal@247 4372
bgneal@247 4373 // Get the text from text nodes and CDATA nodes
bgneal@247 4374 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
bgneal@247 4375 ret += elem.nodeValue;
bgneal@247 4376
bgneal@247 4377 // Traverse everything else, except comment nodes
bgneal@247 4378 } else if ( elem.nodeType !== 8 ) {
bgneal@247 4379 ret += Sizzle.getText( elem.childNodes );
bgneal@247 4380 }
bgneal@247 4381 }
bgneal@247 4382
bgneal@247 4383 return ret;
bgneal@247 4384 };
bgneal@247 4385
bgneal@45 4386 // Check to see if the browser returns elements by name when
bgneal@45 4387 // querying by getElementById (and provide a workaround)
bgneal@45 4388 (function(){
bgneal@45 4389 // We're going to inject a fake input element with a specified name
bgneal@183 4390 var form = document.createElement("div"),
bgneal@247 4391 id = "script" + (new Date()).getTime();
bgneal@183 4392 form.innerHTML = "<a name='" + id + "'/>";
bgneal@45 4393
bgneal@45 4394 // Inject it into the root element, check its status, and remove it quickly
bgneal@45 4395 var root = document.documentElement;
bgneal@45 4396 root.insertBefore( form, root.firstChild );
bgneal@45 4397
bgneal@45 4398 // The workaround has to do additional checks after a getElementById
bgneal@45 4399 // Which slows things down for other browsers (hence the branching)
bgneal@247 4400 if ( document.getElementById( id ) ) {
bgneal@45 4401 Expr.find.ID = function(match, context, isXML){
bgneal@45 4402 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 4403 var m = context.getElementById(match[1]);
bgneal@45 4404 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
bgneal@45 4405 }
bgneal@45 4406 };
bgneal@45 4407
bgneal@45 4408 Expr.filter.ID = function(elem, match){
bgneal@45 4409 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
bgneal@45 4410 return elem.nodeType === 1 && node && node.nodeValue === match;
bgneal@45 4411 };
bgneal@45 4412 }
bgneal@45 4413
bgneal@45 4414 root.removeChild( form );
bgneal@247 4415 root = form = null; // release memory in IE
bgneal@45 4416 })();
bgneal@45 4417
bgneal@45 4418 (function(){
bgneal@45 4419 // Check to see if the browser returns only elements
bgneal@45 4420 // when doing getElementsByTagName("*")
bgneal@45 4421
bgneal@45 4422 // Create a fake element
bgneal@45 4423 var div = document.createElement("div");
bgneal@45 4424 div.appendChild( document.createComment("") );
bgneal@45 4425
bgneal@45 4426 // Make sure no comments are found
bgneal@45 4427 if ( div.getElementsByTagName("*").length > 0 ) {
bgneal@45 4428 Expr.find.TAG = function(match, context){
bgneal@45 4429 var results = context.getElementsByTagName(match[1]);
bgneal@45 4430
bgneal@45 4431 // Filter out possible comments
bgneal@45 4432 if ( match[1] === "*" ) {
bgneal@45 4433 var tmp = [];
bgneal@45 4434
bgneal@45 4435 for ( var i = 0; results[i]; i++ ) {
bgneal@45 4436 if ( results[i].nodeType === 1 ) {
bgneal@45 4437 tmp.push( results[i] );
bgneal@45 4438 }
bgneal@45 4439 }
bgneal@45 4440
bgneal@45 4441 results = tmp;
bgneal@45 4442 }
bgneal@45 4443
bgneal@45 4444 return results;
bgneal@45 4445 };
bgneal@45 4446 }
bgneal@45 4447
bgneal@45 4448 // Check to see if an attribute returns normalized href attributes
bgneal@45 4449 div.innerHTML = "<a href='#'></a>";
bgneal@45 4450 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
bgneal@45 4451 div.firstChild.getAttribute("href") !== "#" ) {
bgneal@45 4452 Expr.attrHandle.href = function(elem){
bgneal@45 4453 return elem.getAttribute("href", 2);
bgneal@45 4454 };
bgneal@45 4455 }
bgneal@247 4456
bgneal@247 4457 div = null; // release memory in IE
bgneal@45 4458 })();
bgneal@45 4459
bgneal@247 4460 if ( document.querySelectorAll ) {
bgneal@247 4461 (function(){
bgneal@247 4462 var oldSizzle = Sizzle, div = document.createElement("div");
bgneal@247 4463 div.innerHTML = "<p class='TEST'></p>";
bgneal@247 4464
bgneal@247 4465 // Safari can't handle uppercase or unicode characters when
bgneal@247 4466 // in quirks mode.
bgneal@247 4467 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
bgneal@247 4468 return;
bgneal@247 4469 }
bgneal@247 4470
bgneal@247 4471 Sizzle = function(query, context, extra, seed){
bgneal@247 4472 context = context || document;
bgneal@247 4473
bgneal@247 4474 // Only use querySelectorAll on non-XML documents
bgneal@247 4475 // (ID selectors don't work in non-HTML documents)
bgneal@247 4476 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
bgneal@247 4477 try {
bgneal@247 4478 return makeArray( context.querySelectorAll(query), extra );
bgneal@247 4479 } catch(e){}
bgneal@247 4480 }
bgneal@247 4481
bgneal@247 4482 return oldSizzle(query, context, extra, seed);
bgneal@247 4483 };
bgneal@247 4484
bgneal@247 4485 for ( var prop in oldSizzle ) {
bgneal@247 4486 Sizzle[ prop ] = oldSizzle[ prop ];
bgneal@247 4487 }
bgneal@247 4488
bgneal@247 4489 div = null; // release memory in IE
bgneal@247 4490 })();
bgneal@247 4491 }
bgneal@247 4492
bgneal@247 4493 (function(){
bgneal@247 4494 var div = document.createElement("div");
bgneal@247 4495
bgneal@247 4496 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
bgneal@247 4497
bgneal@247 4498 // Opera can't find a second classname (in 9.6)
bgneal@247 4499 // Also, make sure that getElementsByClassName actually exists
bgneal@247 4500 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
bgneal@247 4501 return;
bgneal@247 4502 }
bgneal@247 4503
bgneal@247 4504 // Safari caches class attributes, doesn't catch changes (in 3.2)
bgneal@247 4505 div.lastChild.className = "e";
bgneal@247 4506
bgneal@247 4507 if ( div.getElementsByClassName("e").length === 1 ) {
bgneal@45 4508 return;
bgneal@45 4509 }
bgneal@45 4510
bgneal@45 4511 Expr.order.splice(1, 0, "CLASS");
bgneal@45 4512 Expr.find.CLASS = function(match, context, isXML) {
bgneal@45 4513 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
bgneal@45 4514 return context.getElementsByClassName(match[1]);
bgneal@45 4515 }
bgneal@45 4516 };
bgneal@247 4517
bgneal@247 4518 div = null; // release memory in IE
bgneal@45 4519 })();
bgneal@45 4520
bgneal@45 4521 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 4522 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 4523 var elem = checkSet[i];
bgneal@45 4524 if ( elem ) {
bgneal@45 4525 elem = elem[dir];
bgneal@45 4526 var match = false;
bgneal@45 4527
bgneal@45 4528 while ( elem ) {
bgneal@45 4529 if ( elem.sizcache === doneName ) {
bgneal@45 4530 match = checkSet[elem.sizset];
bgneal@45 4531 break;
bgneal@45 4532 }
bgneal@45 4533
bgneal@45 4534 if ( elem.nodeType === 1 && !isXML ){
bgneal@45 4535 elem.sizcache = doneName;
bgneal@45 4536 elem.sizset = i;
bgneal@45 4537 }
bgneal@45 4538
bgneal@247 4539 if ( elem.nodeName.toLowerCase() === cur ) {
bgneal@45 4540 match = elem;
bgneal@45 4541 break;
bgneal@45 4542 }
bgneal@45 4543
bgneal@45 4544 elem = elem[dir];
bgneal@45 4545 }
bgneal@45 4546
bgneal@45 4547 checkSet[i] = match;
bgneal@45 4548 }
bgneal@45 4549 }
bgneal@45 4550 }
bgneal@45 4551
bgneal@45 4552 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 4553 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 4554 var elem = checkSet[i];
bgneal@45 4555 if ( elem ) {
bgneal@45 4556 elem = elem[dir];
bgneal@45 4557 var match = false;
bgneal@45 4558
bgneal@45 4559 while ( elem ) {
bgneal@45 4560 if ( elem.sizcache === doneName ) {
bgneal@45 4561 match = checkSet[elem.sizset];
bgneal@45 4562 break;
bgneal@45 4563 }
bgneal@45 4564
bgneal@45 4565 if ( elem.nodeType === 1 ) {
bgneal@45 4566 if ( !isXML ) {
bgneal@45 4567 elem.sizcache = doneName;
bgneal@45 4568 elem.sizset = i;
bgneal@45 4569 }
bgneal@45 4570 if ( typeof cur !== "string" ) {
bgneal@45 4571 if ( elem === cur ) {
bgneal@45 4572 match = true;
bgneal@45 4573 break;
bgneal@45 4574 }
bgneal@45 4575
bgneal@45 4576 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
bgneal@45 4577 match = elem;
bgneal@45 4578 break;
bgneal@45 4579 }
bgneal@45 4580 }
bgneal@45 4581
bgneal@45 4582 elem = elem[dir];
bgneal@45 4583 }
bgneal@45 4584
bgneal@45 4585 checkSet[i] = match;
bgneal@45 4586 }
bgneal@45 4587 }
bgneal@45 4588 }
bgneal@45 4589
bgneal@247 4590 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
bgneal@247 4591 return !!(a.compareDocumentPosition(b) & 16);
bgneal@45 4592 } : function(a, b){
bgneal@45 4593 return a !== b && (a.contains ? a.contains(b) : true);
bgneal@45 4594 };
bgneal@45 4595
bgneal@247 4596 Sizzle.isXML = function(elem){
bgneal@247 4597 // documentElement is verified for cases where it doesn't yet exist
bgneal@247 4598 // (such as loading iframes in IE - #4833)
bgneal@247 4599 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
bgneal@247 4600 return documentElement ? documentElement.nodeName !== "HTML" : false;
bgneal@45 4601 };
bgneal@45 4602
bgneal@45 4603 var posProcess = function(selector, context){
bgneal@45 4604 var tmpSet = [], later = "", match,
bgneal@45 4605 root = context.nodeType ? [context] : context;
bgneal@45 4606
bgneal@45 4607 // Position selectors must be done after the filter
bgneal@45 4608 // And so must :not(positional) so we move all PSEUDOs to the end
bgneal@45 4609 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
bgneal@45 4610 later += match[0];
bgneal@45 4611 selector = selector.replace( Expr.match.PSEUDO, "" );
bgneal@45 4612 }
bgneal@45 4613
bgneal@45 4614 selector = Expr.relative[selector] ? selector + "*" : selector;
bgneal@45 4615
bgneal@45 4616 for ( var i = 0, l = root.length; i < l; i++ ) {
bgneal@45 4617 Sizzle( selector, root[i], tmpSet );
bgneal@45 4618 }
bgneal@45 4619
bgneal@45 4620 return Sizzle.filter( later, tmpSet );
bgneal@45 4621 };
bgneal@45 4622
bgneal@45 4623 // EXPOSE
bgneal@45 4624
bgneal@45 4625 window.tinymce.dom.Sizzle = Sizzle;
bgneal@45 4626
bgneal@45 4627 })();
bgneal@183 4628
bgneal@183 4629
bgneal@45 4630 (function(tinymce) {
bgneal@45 4631 // Shorten names
bgneal@45 4632 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
bgneal@45 4633
bgneal@183 4634 tinymce.create('tinymce.dom.EventUtils', {
bgneal@183 4635 EventUtils : function() {
bgneal@183 4636 this.inits = [];
bgneal@183 4637 this.events = [];
bgneal@183 4638 },
bgneal@45 4639
bgneal@45 4640 add : function(o, n, f, s) {
bgneal@45 4641 var cb, t = this, el = t.events, r;
bgneal@45 4642
bgneal@183 4643 if (n instanceof Array) {
bgneal@183 4644 r = [];
bgneal@183 4645
bgneal@183 4646 each(n, function(n) {
bgneal@183 4647 r.push(t.add(o, n, f, s));
bgneal@183 4648 });
bgneal@183 4649
bgneal@183 4650 return r;
bgneal@183 4651 }
bgneal@183 4652
bgneal@45 4653 // Handle array
bgneal@45 4654 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@45 4655 r = [];
bgneal@45 4656
bgneal@45 4657 each(o, function(o) {
bgneal@45 4658 o = DOM.get(o);
bgneal@45 4659 r.push(t.add(o, n, f, s));
bgneal@45 4660 });
bgneal@45 4661
bgneal@45 4662 return r;
bgneal@45 4663 }
bgneal@45 4664
bgneal@45 4665 o = DOM.get(o);
bgneal@45 4666
bgneal@45 4667 if (!o)
bgneal@45 4668 return;
bgneal@45 4669
bgneal@45 4670 // Setup event callback
bgneal@45 4671 cb = function(e) {
bgneal@183 4672 // Is all events disabled
bgneal@183 4673 if (t.disabled)
bgneal@183 4674 return;
bgneal@183 4675
bgneal@45 4676 e = e || window.event;
bgneal@45 4677
bgneal@183 4678 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
bgneal@183 4679 if (e && isIE) {
bgneal@183 4680 if (!e.target)
bgneal@183 4681 e.target = e.srcElement;
bgneal@183 4682
bgneal@183 4683 // Patch in preventDefault, stopPropagation methods for W3C compatibility
bgneal@183 4684 tinymce.extend(e, t._stoppers);
bgneal@183 4685 }
bgneal@45 4686
bgneal@45 4687 if (!s)
bgneal@45 4688 return f(e);
bgneal@45 4689
bgneal@45 4690 return f.call(s, e);
bgneal@45 4691 };
bgneal@45 4692
bgneal@45 4693 if (n == 'unload') {
bgneal@45 4694 tinymce.unloads.unshift({func : cb});
bgneal@45 4695 return cb;
bgneal@45 4696 }
bgneal@45 4697
bgneal@45 4698 if (n == 'init') {
bgneal@45 4699 if (t.domLoaded)
bgneal@45 4700 cb();
bgneal@45 4701 else
bgneal@45 4702 t.inits.push(cb);
bgneal@45 4703
bgneal@45 4704 return cb;
bgneal@45 4705 }
bgneal@45 4706
bgneal@45 4707 // Store away listener reference
bgneal@45 4708 el.push({
bgneal@45 4709 obj : o,
bgneal@45 4710 name : n,
bgneal@45 4711 func : f,
bgneal@45 4712 cfunc : cb,
bgneal@45 4713 scope : s
bgneal@45 4714 });
bgneal@45 4715
bgneal@45 4716 t._add(o, n, cb);
bgneal@45 4717
bgneal@45 4718 return f;
bgneal@45 4719 },
bgneal@45 4720
bgneal@45 4721 remove : function(o, n, f) {
bgneal@45 4722 var t = this, a = t.events, s = false, r;
bgneal@45 4723
bgneal@45 4724 // Handle array
bgneal@45 4725 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@45 4726 r = [];
bgneal@45 4727
bgneal@45 4728 each(o, function(o) {
bgneal@45 4729 o = DOM.get(o);
bgneal@45 4730 r.push(t.remove(o, n, f));
bgneal@45 4731 });
bgneal@45 4732
bgneal@45 4733 return r;
bgneal@45 4734 }
bgneal@45 4735
bgneal@45 4736 o = DOM.get(o);
bgneal@45 4737
bgneal@45 4738 each(a, function(e, i) {
bgneal@45 4739 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
bgneal@45 4740 a.splice(i, 1);
bgneal@45 4741 t._remove(o, n, e.cfunc);
bgneal@45 4742 s = true;
bgneal@45 4743 return false;
bgneal@45 4744 }
bgneal@45 4745 });
bgneal@45 4746
bgneal@45 4747 return s;
bgneal@45 4748 },
bgneal@45 4749
bgneal@45 4750 clear : function(o) {
bgneal@45 4751 var t = this, a = t.events, i, e;
bgneal@45 4752
bgneal@45 4753 if (o) {
bgneal@45 4754 o = DOM.get(o);
bgneal@45 4755
bgneal@45 4756 for (i = a.length - 1; i >= 0; i--) {
bgneal@45 4757 e = a[i];
bgneal@45 4758
bgneal@45 4759 if (e.obj === o) {
bgneal@45 4760 t._remove(e.obj, e.name, e.cfunc);
bgneal@45 4761 e.obj = e.cfunc = null;
bgneal@45 4762 a.splice(i, 1);
bgneal@45 4763 }
bgneal@45 4764 }
bgneal@45 4765 }
bgneal@45 4766 },
bgneal@45 4767
bgneal@45 4768 cancel : function(e) {
bgneal@45 4769 if (!e)
bgneal@45 4770 return false;
bgneal@45 4771
bgneal@45 4772 this.stop(e);
bgneal@183 4773
bgneal@45 4774 return this.prevent(e);
bgneal@45 4775 },
bgneal@45 4776
bgneal@45 4777 stop : function(e) {
bgneal@45 4778 if (e.stopPropagation)
bgneal@45 4779 e.stopPropagation();
bgneal@45 4780 else
bgneal@45 4781 e.cancelBubble = true;
bgneal@45 4782
bgneal@45 4783 return false;
bgneal@45 4784 },
bgneal@45 4785
bgneal@45 4786 prevent : function(e) {
bgneal@45 4787 if (e.preventDefault)
bgneal@45 4788 e.preventDefault();
bgneal@45 4789 else
bgneal@45 4790 e.returnValue = false;
bgneal@45 4791
bgneal@45 4792 return false;
bgneal@45 4793 },
bgneal@45 4794
bgneal@183 4795 destroy : function() {
bgneal@183 4796 var t = this;
bgneal@45 4797
bgneal@45 4798 each(t.events, function(e, i) {
bgneal@45 4799 t._remove(e.obj, e.name, e.cfunc);
bgneal@45 4800 e.obj = e.cfunc = null;
bgneal@45 4801 });
bgneal@45 4802
bgneal@45 4803 t.events = [];
bgneal@45 4804 t = null;
bgneal@45 4805 },
bgneal@45 4806
bgneal@45 4807 _add : function(o, n, f) {
bgneal@45 4808 if (o.attachEvent)
bgneal@45 4809 o.attachEvent('on' + n, f);
bgneal@45 4810 else if (o.addEventListener)
bgneal@45 4811 o.addEventListener(n, f, false);
bgneal@45 4812 else
bgneal@45 4813 o['on' + n] = f;
bgneal@45 4814 },
bgneal@45 4815
bgneal@45 4816 _remove : function(o, n, f) {
bgneal@45 4817 if (o) {
bgneal@45 4818 try {
bgneal@45 4819 if (o.detachEvent)
bgneal@45 4820 o.detachEvent('on' + n, f);
bgneal@45 4821 else if (o.removeEventListener)
bgneal@45 4822 o.removeEventListener(n, f, false);
bgneal@45 4823 else
bgneal@45 4824 o['on' + n] = null;
bgneal@45 4825 } catch (ex) {
bgneal@45 4826 // Might fail with permission denined on IE so we just ignore that
bgneal@45 4827 }
bgneal@45 4828 }
bgneal@45 4829 },
bgneal@45 4830
bgneal@183 4831 _pageInit : function(win) {
bgneal@183 4832 var t = this;
bgneal@183 4833
bgneal@183 4834 // Keep it from running more than once
bgneal@183 4835 if (t.domLoaded)
bgneal@45 4836 return;
bgneal@45 4837
bgneal@183 4838 t.domLoaded = true;
bgneal@183 4839
bgneal@183 4840 each(t.inits, function(c) {
bgneal@45 4841 c();
bgneal@45 4842 });
bgneal@45 4843
bgneal@183 4844 t.inits = [];
bgneal@183 4845 },
bgneal@183 4846
bgneal@183 4847 _wait : function(win) {
bgneal@183 4848 var t = this, doc = win.document;
bgneal@45 4849
bgneal@45 4850 // No need since the document is already loaded
bgneal@183 4851 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
bgneal@183 4852 t.domLoaded = 1;
bgneal@45 4853 return;
bgneal@45 4854 }
bgneal@45 4855
bgneal@183 4856 // Use IE method
bgneal@183 4857 if (doc.attachEvent) {
bgneal@183 4858 doc.attachEvent("onreadystatechange", function() {
bgneal@183 4859 if (doc.readyState === "complete") {
bgneal@183 4860 doc.detachEvent("onreadystatechange", arguments.callee);
bgneal@183 4861 t._pageInit(win);
bgneal@183 4862 }
bgneal@183 4863 });
bgneal@183 4864
bgneal@183 4865 if (doc.documentElement.doScroll && win == win.top) {
bgneal@183 4866 (function() {
bgneal@183 4867 if (t.domLoaded)
bgneal@183 4868 return;
bgneal@183 4869
bgneal@183 4870 try {
bgneal@183 4871 // If IE is used, use the trick by Diego Perini
bgneal@183 4872 // http://javascript.nwbox.com/IEContentLoaded/
bgneal@183 4873 doc.documentElement.doScroll("left");
bgneal@183 4874 } catch (ex) {
bgneal@183 4875 setTimeout(arguments.callee, 0);
bgneal@183 4876 return;
bgneal@183 4877 }
bgneal@183 4878
bgneal@183 4879 t._pageInit(win);
bgneal@183 4880 })();
bgneal@183 4881 }
bgneal@183 4882 } else if (doc.addEventListener) {
bgneal@183 4883 t._add(win, 'DOMContentLoaded', function() {
bgneal@183 4884 t._pageInit(win);
bgneal@183 4885 });
bgneal@183 4886 }
bgneal@183 4887
bgneal@183 4888 t._add(win, 'load', function() {
bgneal@183 4889 t._pageInit(win);
bgneal@183 4890 });
bgneal@183 4891 },
bgneal@183 4892
bgneal@183 4893 _stoppers : {
bgneal@183 4894 preventDefault : function() {
bgneal@183 4895 this.returnValue = false;
bgneal@183 4896 },
bgneal@183 4897
bgneal@183 4898 stopPropagation : function() {
bgneal@183 4899 this.cancelBubble = true;
bgneal@45 4900 }
bgneal@45 4901 }
bgneal@183 4902 });
bgneal@183 4903
bgneal@183 4904 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
bgneal@45 4905
bgneal@45 4906 // Dispatch DOM content loaded event for IE and Safari
bgneal@183 4907 Event._wait(window);
bgneal@183 4908
bgneal@183 4909 tinymce.addUnload(function() {
bgneal@183 4910 Event.destroy();
bgneal@183 4911 });
bgneal@45 4912 })(tinymce);
bgneal@183 4913
bgneal@45 4914 (function(tinymce) {
bgneal@183 4915 tinymce.dom.Element = function(id, settings) {
bgneal@183 4916 var t = this, dom, el;
bgneal@183 4917
bgneal@183 4918 t.settings = settings = settings || {};
bgneal@183 4919 t.id = id;
bgneal@183 4920 t.dom = dom = settings.dom || tinymce.DOM;
bgneal@183 4921
bgneal@183 4922 // Only IE leaks DOM references, this is a lot faster
bgneal@183 4923 if (!tinymce.isIE)
bgneal@183 4924 el = dom.get(t.id);
bgneal@183 4925
bgneal@183 4926 tinymce.each(
bgneal@183 4927 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
bgneal@183 4928 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
bgneal@183 4929 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
bgneal@183 4930 'isHidden,setHTML,get').split(/,/)
bgneal@183 4931 , function(k) {
bgneal@45 4932 t[k] = function() {
bgneal@45 4933 var a = [id], i;
bgneal@45 4934
bgneal@45 4935 for (i = 0; i < arguments.length; i++)
bgneal@45 4936 a.push(arguments[i]);
bgneal@45 4937
bgneal@45 4938 a = dom[k].apply(dom, a);
bgneal@45 4939 t.update(k);
bgneal@45 4940
bgneal@45 4941 return a;
bgneal@45 4942 };
bgneal@45 4943 });
bgneal@183 4944
bgneal@183 4945 tinymce.extend(t, {
bgneal@183 4946 on : function(n, f, s) {
bgneal@183 4947 return tinymce.dom.Event.add(t.id, n, f, s);
bgneal@183 4948 },
bgneal@183 4949
bgneal@183 4950 getXY : function() {
bgneal@183 4951 return {
bgneal@183 4952 x : parseInt(t.getStyle('left')),
bgneal@183 4953 y : parseInt(t.getStyle('top'))
bgneal@183 4954 };
bgneal@183 4955 },
bgneal@183 4956
bgneal@183 4957 getSize : function() {
bgneal@183 4958 var n = dom.get(t.id);
bgneal@183 4959
bgneal@183 4960 return {
bgneal@183 4961 w : parseInt(t.getStyle('width') || n.clientWidth),
bgneal@183 4962 h : parseInt(t.getStyle('height') || n.clientHeight)
bgneal@183 4963 };
bgneal@183 4964 },
bgneal@183 4965
bgneal@183 4966 moveTo : function(x, y) {
bgneal@183 4967 t.setStyles({left : x, top : y});
bgneal@183 4968 },
bgneal@183 4969
bgneal@183 4970 moveBy : function(x, y) {
bgneal@183 4971 var p = t.getXY();
bgneal@183 4972
bgneal@183 4973 t.moveTo(p.x + x, p.y + y);
bgneal@183 4974 },
bgneal@183 4975
bgneal@183 4976 resizeTo : function(w, h) {
bgneal@183 4977 t.setStyles({width : w, height : h});
bgneal@183 4978 },
bgneal@183 4979
bgneal@183 4980 resizeBy : function(w, h) {
bgneal@183 4981 var s = t.getSize();
bgneal@183 4982
bgneal@183 4983 t.resizeTo(s.w + w, s.h + h);
bgneal@183 4984 },
bgneal@183 4985
bgneal@183 4986 update : function(k) {
bgneal@183 4987 var b;
bgneal@183 4988
bgneal@183 4989 if (tinymce.isIE6 && settings.blocker) {
bgneal@183 4990 k = k || '';
bgneal@183 4991
bgneal@183 4992 // Ignore getters
bgneal@183 4993 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
bgneal@183 4994 return;
bgneal@183 4995
bgneal@183 4996 // Remove blocker on remove
bgneal@183 4997 if (k == 'remove') {
bgneal@183 4998 dom.remove(t.blocker);
bgneal@183 4999 return;
bgneal@183 5000 }
bgneal@183 5001
bgneal@183 5002 if (!t.blocker) {
bgneal@183 5003 t.blocker = dom.uniqueId();
bgneal@183 5004 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
bgneal@183 5005 dom.setStyle(b, 'opacity', 0);
bgneal@183 5006 } else
bgneal@183 5007 b = dom.get(t.blocker);
bgneal@183 5008
bgneal@183 5009 dom.setStyles(b, {
bgneal@183 5010 left : t.getStyle('left', 1),
bgneal@183 5011 top : t.getStyle('top', 1),
bgneal@183 5012 width : t.getStyle('width', 1),
bgneal@183 5013 height : t.getStyle('height', 1),
bgneal@183 5014 display : t.getStyle('display', 1),
bgneal@183 5015 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
bgneal@183 5016 });
bgneal@183 5017 }
bgneal@183 5018 }
bgneal@183 5019 });
bgneal@183 5020 };
bgneal@45 5021 })(tinymce);
bgneal@183 5022
bgneal@45 5023 (function(tinymce) {
bgneal@45 5024 function trimNl(s) {
bgneal@45 5025 return s.replace(/[\n\r]+/g, '');
bgneal@45 5026 };
bgneal@45 5027
bgneal@45 5028 // Shorten names
bgneal@45 5029 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
bgneal@45 5030
bgneal@45 5031 tinymce.create('tinymce.dom.Selection', {
bgneal@45 5032 Selection : function(dom, win, serializer) {
bgneal@45 5033 var t = this;
bgneal@45 5034
bgneal@45 5035 t.dom = dom;
bgneal@45 5036 t.win = win;
bgneal@45 5037 t.serializer = serializer;
bgneal@45 5038
bgneal@45 5039 // Add events
bgneal@45 5040 each([
bgneal@45 5041 'onBeforeSetContent',
bgneal@45 5042 'onBeforeGetContent',
bgneal@45 5043 'onSetContent',
bgneal@45 5044 'onGetContent'
bgneal@45 5045 ], function(e) {
bgneal@45 5046 t[e] = new tinymce.util.Dispatcher(t);
bgneal@45 5047 });
bgneal@45 5048
bgneal@45 5049 // No W3C Range support
bgneal@45 5050 if (!t.win.getSelection)
bgneal@45 5051 t.tridentSel = new tinymce.dom.TridentSelection(t);
bgneal@45 5052
bgneal@45 5053 // Prevent leaks
bgneal@45 5054 tinymce.addUnload(t.destroy, t);
bgneal@45 5055 },
bgneal@45 5056
bgneal@45 5057 getContent : function(s) {
bgneal@45 5058 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
bgneal@45 5059
bgneal@45 5060 s = s || {};
bgneal@45 5061 wb = wa = '';
bgneal@45 5062 s.get = true;
bgneal@45 5063 s.format = s.format || 'html';
bgneal@45 5064 t.onBeforeGetContent.dispatch(t, s);
bgneal@45 5065
bgneal@45 5066 if (s.format == 'text')
bgneal@45 5067 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
bgneal@45 5068
bgneal@45 5069 if (r.cloneContents) {
bgneal@45 5070 n = r.cloneContents();
bgneal@45 5071
bgneal@45 5072 if (n)
bgneal@45 5073 e.appendChild(n);
bgneal@45 5074 } else if (is(r.item) || is(r.htmlText))
bgneal@45 5075 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
bgneal@45 5076 else
bgneal@45 5077 e.innerHTML = r.toString();
bgneal@45 5078
bgneal@45 5079 // Keep whitespace before and after
bgneal@45 5080 if (/^\s/.test(e.innerHTML))
bgneal@45 5081 wb = ' ';
bgneal@45 5082
bgneal@45 5083 if (/\s+$/.test(e.innerHTML))
bgneal@45 5084 wa = ' ';
bgneal@45 5085
bgneal@45 5086 s.getInner = true;
bgneal@45 5087
bgneal@45 5088 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
bgneal@45 5089 t.onGetContent.dispatch(t, s);
bgneal@45 5090
bgneal@45 5091 return s.content;
bgneal@45 5092 },
bgneal@45 5093
bgneal@45 5094 setContent : function(h, s) {
bgneal@45 5095 var t = this, r = t.getRng(), c, d = t.win.document;
bgneal@45 5096
bgneal@45 5097 s = s || {format : 'html'};
bgneal@45 5098 s.set = true;
bgneal@45 5099 h = s.content = t.dom.processHTML(h);
bgneal@45 5100
bgneal@45 5101 // Dispatch before set content event
bgneal@45 5102 t.onBeforeSetContent.dispatch(t, s);
bgneal@45 5103 h = s.content;
bgneal@45 5104
bgneal@45 5105 if (r.insertNode) {
bgneal@45 5106 // Make caret marker since insertNode places the caret in the beginning of text after insert
bgneal@45 5107 h += '<span id="__caret">_</span>';
bgneal@45 5108
bgneal@45 5109 // Delete and insert new node
bgneal@217 5110
bgneal@217 5111 if (r.startContainer == d && r.endContainer == d) {
bgneal@183 5112 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
bgneal@183 5113 d.body.innerHTML = h;
bgneal@183 5114 } else {
bgneal@183 5115 r.deleteContents();
bgneal@217 5116 if (d.body.childNodes.length == 0) {
bgneal@217 5117 d.body.innerHTML = h;
bgneal@217 5118 } else {
bgneal@217 5119 r.insertNode(r.createContextualFragment(h));
bgneal@217 5120 }
bgneal@183 5121 }
bgneal@45 5122
bgneal@45 5123 // Move to caret marker
bgneal@45 5124 c = t.dom.get('__caret');
bgneal@45 5125 // Make sure we wrap it compleatly, Opera fails with a simple select call
bgneal@45 5126 r = d.createRange();
bgneal@45 5127 r.setStartBefore(c);
bgneal@183 5128 r.setEndBefore(c);
bgneal@45 5129 t.setRng(r);
bgneal@45 5130
bgneal@45 5131 // Remove the caret position
bgneal@45 5132 t.dom.remove('__caret');
bgneal@45 5133 } else {
bgneal@45 5134 if (r.item) {
bgneal@45 5135 // Delete content and get caret text selection
bgneal@45 5136 d.execCommand('Delete', false, null);
bgneal@45 5137 r = t.getRng();
bgneal@45 5138 }
bgneal@45 5139
bgneal@45 5140 r.pasteHTML(h);
bgneal@45 5141 }
bgneal@45 5142
bgneal@45 5143 // Dispatch set content event
bgneal@45 5144 t.onSetContent.dispatch(t, s);
bgneal@45 5145 },
bgneal@45 5146
bgneal@45 5147 getStart : function() {
bgneal@247 5148 var rng = this.getRng(), startElement, parentElement, checkRng, node;
bgneal@247 5149
bgneal@247 5150 if (rng.duplicate || rng.item) {
bgneal@247 5151 // Control selection, return first item
bgneal@247 5152 if (rng.item)
bgneal@247 5153 return rng.item(0);
bgneal@247 5154
bgneal@247 5155 // Get start element
bgneal@247 5156 checkRng = rng.duplicate();
bgneal@247 5157 checkRng.collapse(1);
bgneal@247 5158 startElement = checkRng.parentElement();
bgneal@247 5159
bgneal@247 5160 // Check if range parent is inside the start element, then return the inner parent element
bgneal@247 5161 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
bgneal@247 5162 parentElement = node = rng.parentElement();
bgneal@247 5163 while (node = node.parentNode) {
bgneal@247 5164 if (node == startElement) {
bgneal@247 5165 startElement = parentElement;
bgneal@247 5166 break;
bgneal@247 5167 }
bgneal@247 5168 }
bgneal@247 5169
bgneal@247 5170 // If start element is body element try to move to the first child if it exists
bgneal@247 5171 if (startElement && startElement.nodeName == 'BODY')
bgneal@247 5172 return startElement.firstChild || startElement;
bgneal@247 5173
bgneal@247 5174 return startElement;
bgneal@45 5175 } else {
bgneal@247 5176 startElement = rng.startContainer;
bgneal@247 5177
bgneal@247 5178 if (startElement.nodeType == 1 && startElement.hasChildNodes())
bgneal@247 5179 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
bgneal@247 5180
bgneal@247 5181 if (startElement && startElement.nodeType == 3)
bgneal@247 5182 return startElement.parentNode;
bgneal@247 5183
bgneal@247 5184 return startElement;
bgneal@45 5185 }
bgneal@45 5186 },
bgneal@45 5187
bgneal@45 5188 getEnd : function() {
bgneal@183 5189 var t = this, r = t.getRng(), e, eo;
bgneal@183 5190
bgneal@183 5191 if (r.duplicate || r.item) {
bgneal@45 5192 if (r.item)
bgneal@45 5193 return r.item(0);
bgneal@45 5194
bgneal@45 5195 r = r.duplicate();
bgneal@45 5196 r.collapse(0);
bgneal@45 5197 e = r.parentElement();
bgneal@45 5198
bgneal@45 5199 if (e && e.nodeName == 'BODY')
bgneal@183 5200 return e.lastChild || e;
bgneal@45 5201
bgneal@45 5202 return e;
bgneal@45 5203 } else {
bgneal@45 5204 e = r.endContainer;
bgneal@183 5205 eo = r.endOffset;
bgneal@183 5206
bgneal@183 5207 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@183 5208 e = e.childNodes[eo > 0 ? eo - 1 : eo];
bgneal@183 5209
bgneal@183 5210 if (e && e.nodeType == 3)
bgneal@183 5211 return e.parentNode;
bgneal@183 5212
bgneal@183 5213 return e;
bgneal@183 5214 }
bgneal@183 5215 },
bgneal@183 5216
bgneal@183 5217 getBookmark : function(type, normalized) {
bgneal@183 5218 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
bgneal@183 5219
bgneal@183 5220 function findIndex(name, element) {
bgneal@183 5221 var index = 0;
bgneal@183 5222
bgneal@183 5223 each(dom.select(name), function(node, i) {
bgneal@183 5224 if (node == element)
bgneal@183 5225 index = i;
bgneal@183 5226 });
bgneal@183 5227
bgneal@183 5228 return index;
bgneal@183 5229 };
bgneal@183 5230
bgneal@183 5231 if (type == 2) {
bgneal@183 5232 function getLocation() {
bgneal@183 5233 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
bgneal@183 5234
bgneal@183 5235 function getPoint(rng, start) {
bgneal@183 5236 var container = rng[start ? 'startContainer' : 'endContainer'],
bgneal@183 5237 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
bgneal@183 5238
bgneal@183 5239 if (container.nodeType == 3) {
bgneal@183 5240 if (normalized) {
bgneal@183 5241 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
bgneal@183 5242 offset += node.nodeValue.length;
bgneal@183 5243 }
bgneal@183 5244
bgneal@183 5245 point.push(offset);
bgneal@183 5246 } else {
bgneal@183 5247 childNodes = container.childNodes;
bgneal@217 5248
bgneal@217 5249 if (offset >= childNodes.length && childNodes.length) {
bgneal@183 5250 after = 1;
bgneal@217 5251 offset = Math.max(0, childNodes.length - 1);
bgneal@183 5252 }
bgneal@183 5253
bgneal@183 5254 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
bgneal@183 5255 }
bgneal@183 5256
bgneal@183 5257 for (; container && container != root; container = container.parentNode)
bgneal@183 5258 point.push(t.dom.nodeIndex(container, normalized));
bgneal@183 5259
bgneal@183 5260 return point;
bgneal@45 5261 };
bgneal@183 5262
bgneal@183 5263 bookmark.start = getPoint(rng, true);
bgneal@183 5264
bgneal@183 5265 if (!t.isCollapsed())
bgneal@183 5266 bookmark.end = getPoint(rng);
bgneal@183 5267
bgneal@183 5268 return bookmark;
bgneal@183 5269 };
bgneal@183 5270
bgneal@183 5271 return getLocation();
bgneal@183 5272 }
bgneal@183 5273
bgneal@183 5274 // Handle simple range
bgneal@183 5275 if (type)
bgneal@183 5276 return {rng : t.getRng()};
bgneal@183 5277
bgneal@183 5278 rng = t.getRng();
bgneal@183 5279 id = dom.uniqueId();
bgneal@183 5280 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
bgneal@183 5281 styles = 'overflow:hidden;line-height:0px';
bgneal@183 5282
bgneal@183 5283 // Explorer method
bgneal@183 5284 if (rng.duplicate || rng.item) {
bgneal@45 5285 // Text selection
bgneal@183 5286 if (!rng.item) {
bgneal@183 5287 rng2 = rng.duplicate();
bgneal@183 5288
bgneal@183 5289 // Insert start marker
bgneal@183 5290 rng.collapse();
bgneal@183 5291 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
bgneal@183 5292
bgneal@183 5293 // Insert end marker
bgneal@183 5294 if (!collapsed) {
bgneal@183 5295 rng2.collapse(false);
bgneal@183 5296 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
bgneal@183 5297 }
bgneal@183 5298 } else {
bgneal@183 5299 // Control selection
bgneal@183 5300 element = rng.item(0);
bgneal@183 5301 name = element.nodeName;
bgneal@183 5302
bgneal@183 5303 return {name : name, index : findIndex(name, element)};
bgneal@183 5304 }
bgneal@183 5305 } else {
bgneal@183 5306 element = t.getNode();
bgneal@183 5307 name = element.nodeName;
bgneal@183 5308 if (name == 'IMG')
bgneal@183 5309 return {name : name, index : findIndex(name, element)};
bgneal@183 5310
bgneal@183 5311 // W3C method
bgneal@183 5312 rng2 = rng.cloneRange();
bgneal@183 5313
bgneal@183 5314 // Insert end marker
bgneal@183 5315 if (!collapsed) {
bgneal@183 5316 rng2.collapse(false);
bgneal@183 5317 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
bgneal@183 5318 }
bgneal@183 5319
bgneal@183 5320 rng.collapse(true);
bgneal@183 5321 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
bgneal@183 5322 }
bgneal@183 5323
bgneal@183 5324 t.moveToBookmark({id : id, keep : 1});
bgneal@183 5325
bgneal@183 5326 return {id : id};
bgneal@183 5327 },
bgneal@183 5328
bgneal@183 5329 moveToBookmark : function(bookmark) {
bgneal@217 5330 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
bgneal@183 5331
bgneal@183 5332 // Clear selection cache
bgneal@183 5333 if (t.tridentSel)
bgneal@183 5334 t.tridentSel.destroy();
bgneal@183 5335
bgneal@183 5336 if (bookmark) {
bgneal@183 5337 if (bookmark.start) {
bgneal@183 5338 rng = dom.createRng();
bgneal@183 5339 root = dom.getRoot();
bgneal@183 5340
bgneal@183 5341 function setEndPoint(start) {
bgneal@247 5342 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
bgneal@183 5343
bgneal@183 5344 if (point) {
bgneal@183 5345 // Find container node
bgneal@247 5346 for (node = root, i = point.length - 1; i >= 1; i--) {
bgneal@247 5347 children = node.childNodes;
bgneal@247 5348
bgneal@247 5349 if (children.length)
bgneal@247 5350 node = children[point[i]];
bgneal@247 5351 }
bgneal@183 5352
bgneal@183 5353 // Set offset within container node
bgneal@183 5354 if (start)
bgneal@183 5355 rng.setStart(node, point[0]);
bgneal@183 5356 else
bgneal@183 5357 rng.setEnd(node, point[0]);
bgneal@183 5358 }
bgneal@183 5359 };
bgneal@183 5360
bgneal@183 5361 setEndPoint(true);
bgneal@183 5362 setEndPoint();
bgneal@183 5363
bgneal@183 5364 t.setRng(rng);
bgneal@183 5365 } else if (bookmark.id) {
bgneal@183 5366 function restoreEndPoint(suffix) {
bgneal@183 5367 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
bgneal@183 5368
bgneal@183 5369 if (marker) {
bgneal@183 5370 node = marker.parentNode;
bgneal@183 5371
bgneal@183 5372 if (suffix == 'start') {
bgneal@183 5373 if (!keep) {
bgneal@183 5374 idx = dom.nodeIndex(marker);
bgneal@183 5375 } else {
bgneal@247 5376 node = marker.firstChild;
bgneal@183 5377 idx = 1;
bgneal@183 5378 }
bgneal@183 5379
bgneal@217 5380 startContainer = endContainer = node;
bgneal@217 5381 startOffset = endOffset = idx;
bgneal@183 5382 } else {
bgneal@183 5383 if (!keep) {
bgneal@183 5384 idx = dom.nodeIndex(marker);
bgneal@183 5385 } else {
bgneal@247 5386 node = marker.firstChild;
bgneal@183 5387 idx = 1;
bgneal@183 5388 }
bgneal@183 5389
bgneal@217 5390 endContainer = node;
bgneal@217 5391 endOffset = idx;
bgneal@183 5392 }
bgneal@183 5393
bgneal@183 5394 if (!keep) {
bgneal@183 5395 prev = marker.previousSibling;
bgneal@183 5396 next = marker.nextSibling;
bgneal@183 5397
bgneal@183 5398 // Remove all marker text nodes
bgneal@183 5399 each(tinymce.grep(marker.childNodes), function(node) {
bgneal@183 5400 if (node.nodeType == 3)
bgneal@183 5401 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
bgneal@183 5402 });
bgneal@183 5403
bgneal@183 5404 // Remove marker but keep children if for example contents where inserted into the marker
bgneal@183 5405 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
bgneal@183 5406 while (marker = dom.get(bookmark.id + '_' + suffix))
bgneal@183 5407 dom.remove(marker, 1);
bgneal@183 5408
bgneal@183 5409 // If siblings are text nodes then merge them
bgneal@183 5410 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
bgneal@183 5411 idx = prev.nodeValue.length;
bgneal@183 5412 prev.appendData(next.nodeValue);
bgneal@183 5413 dom.remove(next);
bgneal@183 5414
bgneal@183 5415 if (suffix == 'start') {
bgneal@217 5416 startContainer = endContainer = prev;
bgneal@217 5417 startOffset = endOffset = idx;
bgneal@217 5418 } else {
bgneal@217 5419 endContainer = prev;
bgneal@217 5420 endOffset = idx;
bgneal@217 5421 }
bgneal@183 5422 }
bgneal@183 5423 }
bgneal@183 5424 }
bgneal@183 5425 };
bgneal@183 5426
bgneal@247 5427 function addBogus(node) {
bgneal@247 5428 // Adds a bogus BR element for empty block elements
bgneal@247 5429 // on non IE browsers just to have a place to put the caret
bgneal@247 5430 if (!isIE && dom.isBlock(node) && !node.innerHTML)
bgneal@247 5431 node.innerHTML = '<br _mce_bogus="1" />';
bgneal@247 5432
bgneal@247 5433 return node;
bgneal@247 5434 };
bgneal@247 5435
bgneal@183 5436 // Restore start/end points
bgneal@183 5437 restoreEndPoint('start');
bgneal@183 5438 restoreEndPoint('end');
bgneal@183 5439
bgneal@247 5440 if (startContainer) {
bgneal@247 5441 rng = dom.createRng();
bgneal@247 5442 rng.setStart(addBogus(startContainer), startOffset);
bgneal@247 5443 rng.setEnd(addBogus(endContainer), endOffset);
bgneal@247 5444 t.setRng(rng);
bgneal@247 5445 }
bgneal@183 5446 } else if (bookmark.name) {
bgneal@183 5447 t.select(dom.select(bookmark.name)[bookmark.index]);
bgneal@183 5448 } else if (bookmark.rng)
bgneal@183 5449 t.setRng(bookmark.rng);
bgneal@183 5450 }
bgneal@183 5451 },
bgneal@183 5452
bgneal@183 5453 select : function(node, content) {
bgneal@183 5454 var t = this, dom = t.dom, rng = dom.createRng(), idx;
bgneal@183 5455
bgneal@183 5456 idx = dom.nodeIndex(node);
bgneal@183 5457 rng.setStart(node.parentNode, idx);
bgneal@183 5458 rng.setEnd(node.parentNode, idx + 1);
bgneal@183 5459
bgneal@183 5460 // Find first/last text node or BR element
bgneal@183 5461 if (content) {
bgneal@183 5462 function setPoint(node, start) {
bgneal@183 5463 var walker = new tinymce.dom.TreeWalker(node, node);
bgneal@183 5464
bgneal@183 5465 do {
bgneal@183 5466 // Text node
bgneal@183 5467 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
bgneal@183 5468 if (start)
bgneal@183 5469 rng.setStart(node, 0);
bgneal@183 5470 else
bgneal@183 5471 rng.setEnd(node, node.nodeValue.length);
bgneal@183 5472
bgneal@183 5473 return;
bgneal@183 5474 }
bgneal@183 5475
bgneal@183 5476 // BR element
bgneal@183 5477 if (node.nodeName == 'BR') {
bgneal@183 5478 if (start)
bgneal@183 5479 rng.setStartBefore(node);
bgneal@183 5480 else
bgneal@183 5481 rng.setEndBefore(node);
bgneal@183 5482
bgneal@183 5483 return;
bgneal@183 5484 }
bgneal@183 5485 } while (node = (start ? walker.next() : walker.prev()));
bgneal@45 5486 };
bgneal@183 5487
bgneal@183 5488 setPoint(node, 1);
bgneal@183 5489 setPoint(node);
bgneal@183 5490 }
bgneal@183 5491
bgneal@183 5492 t.setRng(rng);
bgneal@183 5493
bgneal@183 5494 return node;
bgneal@45 5495 },
bgneal@45 5496
bgneal@45 5497 isCollapsed : function() {
bgneal@45 5498 var t = this, r = t.getRng(), s = t.getSel();
bgneal@45 5499
bgneal@45 5500 if (!r || r.item)
bgneal@45 5501 return false;
bgneal@45 5502
bgneal@183 5503 if (r.compareEndPoints)
bgneal@183 5504 return r.compareEndPoints('StartToEnd', r) === 0;
bgneal@183 5505
bgneal@183 5506 return !s || r.collapsed;
bgneal@45 5507 },
bgneal@45 5508
bgneal@45 5509 collapse : function(b) {
bgneal@45 5510 var t = this, r = t.getRng(), n;
bgneal@45 5511
bgneal@45 5512 // Control range on IE
bgneal@45 5513 if (r.item) {
bgneal@45 5514 n = r.item(0);
bgneal@45 5515 r = this.win.document.body.createTextRange();
bgneal@45 5516 r.moveToElementText(n);
bgneal@45 5517 }
bgneal@45 5518
bgneal@45 5519 r.collapse(!!b);
bgneal@45 5520 t.setRng(r);
bgneal@45 5521 },
bgneal@45 5522
bgneal@45 5523 getSel : function() {
bgneal@45 5524 var t = this, w = this.win;
bgneal@45 5525
bgneal@45 5526 return w.getSelection ? w.getSelection() : w.document.selection;
bgneal@45 5527 },
bgneal@45 5528
bgneal@45 5529 getRng : function(w3c) {
bgneal@45 5530 var t = this, s, r;
bgneal@45 5531
bgneal@45 5532 // Found tridentSel object then we need to use that one
bgneal@45 5533 if (w3c && t.tridentSel)
bgneal@45 5534 return t.tridentSel.getRangeAt(0);
bgneal@45 5535
bgneal@45 5536 try {
bgneal@45 5537 if (s = t.getSel())
bgneal@45 5538 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
bgneal@45 5539 } catch (ex) {
bgneal@45 5540 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
bgneal@45 5541 }
bgneal@45 5542
bgneal@45 5543 // No range found then create an empty one
bgneal@45 5544 // This can occur when the editor is placed in a hidden container element on Gecko
bgneal@45 5545 // Or on IE when there was an exception
bgneal@45 5546 if (!r)
bgneal@183 5547 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
bgneal@45 5548
bgneal@217 5549 if (t.selectedRange && t.explicitRange) {
bgneal@217 5550 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
bgneal@217 5551 // Safari, Opera and Chrome only ever select text which causes the range to change.
bgneal@217 5552 // This lets us use the originally set range if the selection hasn't been changed by the user.
bgneal@217 5553 r = t.explicitRange;
bgneal@217 5554 } else {
bgneal@217 5555 t.selectedRange = null;
bgneal@217 5556 t.explicitRange = null;
bgneal@217 5557 }
bgneal@217 5558 }
bgneal@45 5559 return r;
bgneal@45 5560 },
bgneal@45 5561
bgneal@45 5562 setRng : function(r) {
bgneal@45 5563 var s, t = this;
bgneal@217 5564
bgneal@45 5565 if (!t.tridentSel) {
bgneal@45 5566 s = t.getSel();
bgneal@45 5567
bgneal@45 5568 if (s) {
bgneal@217 5569 t.explicitRange = r;
bgneal@45 5570 s.removeAllRanges();
bgneal@45 5571 s.addRange(r);
bgneal@217 5572 t.selectedRange = s.getRangeAt(0);
bgneal@45 5573 }
bgneal@45 5574 } else {
bgneal@45 5575 // Is W3C Range
bgneal@45 5576 if (r.cloneRange) {
bgneal@45 5577 t.tridentSel.addRange(r);
bgneal@45 5578 return;
bgneal@45 5579 }
bgneal@45 5580
bgneal@45 5581 // Is IE specific range
bgneal@45 5582 try {
bgneal@45 5583 r.select();
bgneal@45 5584 } catch (ex) {
bgneal@45 5585 // Needed for some odd IE bug #1843306
bgneal@45 5586 }
bgneal@45 5587 }
bgneal@45 5588 },
bgneal@45 5589
bgneal@45 5590 setNode : function(n) {
bgneal@45 5591 var t = this;
bgneal@45 5592
bgneal@45 5593 t.setContent(t.dom.getOuterHTML(n));
bgneal@45 5594
bgneal@45 5595 return n;
bgneal@45 5596 },
bgneal@45 5597
bgneal@45 5598 getNode : function() {
bgneal@183 5599 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
bgneal@183 5600
bgneal@183 5601 if (rng.setStart) {
bgneal@45 5602 // Range maybe lost after the editor is made visible again
bgneal@183 5603 if (!rng)
bgneal@45 5604 return t.dom.getRoot();
bgneal@45 5605
bgneal@183 5606 elm = rng.commonAncestorContainer;
bgneal@45 5607
bgneal@45 5608 // Handle selection a image or other control like element such as anchors
bgneal@183 5609 if (!rng.collapsed) {
bgneal@183 5610 if (rng.startContainer == rng.endContainer) {
bgneal@183 5611 if (rng.startOffset - rng.endOffset < 2) {
bgneal@183 5612 if (rng.startContainer.hasChildNodes())
bgneal@183 5613 elm = rng.startContainer.childNodes[rng.startOffset];
bgneal@183 5614 }
bgneal@183 5615 }
bgneal@183 5616
bgneal@45 5617 // If the anchor node is a element instead of a text node then return this element
bgneal@183 5618 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
bgneal@183 5619 return sel.anchorNode.childNodes[sel.anchorOffset];
bgneal@183 5620 }
bgneal@183 5621
bgneal@183 5622 if (elm && elm.nodeType == 3)
bgneal@183 5623 return elm.parentNode;
bgneal@183 5624
bgneal@183 5625 return elm;
bgneal@183 5626 }
bgneal@183 5627
bgneal@183 5628 return rng.item ? rng.item(0) : rng.parentElement();
bgneal@45 5629 },
bgneal@45 5630
bgneal@45 5631 getSelectedBlocks : function(st, en) {
bgneal@45 5632 var t = this, dom = t.dom, sb, eb, n, bl = [];
bgneal@45 5633
bgneal@45 5634 sb = dom.getParent(st || t.getStart(), dom.isBlock);
bgneal@45 5635 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
bgneal@45 5636
bgneal@45 5637 if (sb)
bgneal@45 5638 bl.push(sb);
bgneal@45 5639
bgneal@45 5640 if (sb && eb && sb != eb) {
bgneal@45 5641 n = sb;
bgneal@45 5642
bgneal@45 5643 while ((n = n.nextSibling) && n != eb) {
bgneal@45 5644 if (dom.isBlock(n))
bgneal@45 5645 bl.push(n);
bgneal@45 5646 }
bgneal@45 5647 }
bgneal@45 5648
bgneal@45 5649 if (eb && sb != eb)
bgneal@45 5650 bl.push(eb);
bgneal@45 5651
bgneal@45 5652 return bl;
bgneal@45 5653 },
bgneal@45 5654
bgneal@45 5655 destroy : function(s) {
bgneal@45 5656 var t = this;
bgneal@45 5657
bgneal@45 5658 t.win = null;
bgneal@45 5659
bgneal@183 5660 if (t.tridentSel)
bgneal@183 5661 t.tridentSel.destroy();
bgneal@183 5662
bgneal@45 5663 // Manual destroy then remove unload handler
bgneal@45 5664 if (!s)
bgneal@45 5665 tinymce.removeUnload(t.destroy);
bgneal@45 5666 }
bgneal@183 5667 });
bgneal@45 5668 })(tinymce);
bgneal@183 5669
bgneal@45 5670 (function(tinymce) {
bgneal@45 5671 tinymce.create('tinymce.dom.XMLWriter', {
bgneal@45 5672 node : null,
bgneal@45 5673
bgneal@45 5674 XMLWriter : function(s) {
bgneal@45 5675 // Get XML document
bgneal@45 5676 function getXML() {
bgneal@45 5677 var i = document.implementation;
bgneal@45 5678
bgneal@45 5679 if (!i || !i.createDocument) {
bgneal@45 5680 // Try IE objects
bgneal@45 5681 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
bgneal@45 5682 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
bgneal@45 5683 } else
bgneal@45 5684 return i.createDocument('', '', null);
bgneal@45 5685 };
bgneal@45 5686
bgneal@45 5687 this.doc = getXML();
bgneal@45 5688
bgneal@45 5689 // Since Opera and WebKit doesn't escape > into &gt; we need to do it our self to normalize the output for all browsers
bgneal@45 5690 this.valid = tinymce.isOpera || tinymce.isWebKit;
bgneal@45 5691
bgneal@45 5692 this.reset();
bgneal@45 5693 },
bgneal@45 5694
bgneal@45 5695 reset : function() {
bgneal@45 5696 var t = this, d = t.doc;
bgneal@45 5697
bgneal@45 5698 if (d.firstChild)
bgneal@45 5699 d.removeChild(d.firstChild);
bgneal@45 5700
bgneal@45 5701 t.node = d.appendChild(d.createElement("html"));
bgneal@45 5702 },
bgneal@45 5703
bgneal@45 5704 writeStartElement : function(n) {
bgneal@45 5705 var t = this;
bgneal@45 5706
bgneal@45 5707 t.node = t.node.appendChild(t.doc.createElement(n));
bgneal@45 5708 },
bgneal@45 5709
bgneal@45 5710 writeAttribute : function(n, v) {
bgneal@45 5711 if (this.valid)
bgneal@45 5712 v = v.replace(/>/g, '%MCGT%');
bgneal@45 5713
bgneal@45 5714 this.node.setAttribute(n, v);
bgneal@45 5715 },
bgneal@45 5716
bgneal@45 5717 writeEndElement : function() {
bgneal@45 5718 this.node = this.node.parentNode;
bgneal@45 5719 },
bgneal@45 5720
bgneal@45 5721 writeFullEndElement : function() {
bgneal@45 5722 var t = this, n = t.node;
bgneal@45 5723
bgneal@45 5724 n.appendChild(t.doc.createTextNode(""));
bgneal@45 5725 t.node = n.parentNode;
bgneal@45 5726 },
bgneal@45 5727
bgneal@45 5728 writeText : function(v) {
bgneal@45 5729 if (this.valid)
bgneal@45 5730 v = v.replace(/>/g, '%MCGT%');
bgneal@45 5731
bgneal@45 5732 this.node.appendChild(this.doc.createTextNode(v));
bgneal@45 5733 },
bgneal@45 5734
bgneal@45 5735 writeCDATA : function(v) {
bgneal@183 5736 this.node.appendChild(this.doc.createCDATASection(v));
bgneal@45 5737 },
bgneal@45 5738
bgneal@45 5739 writeComment : function(v) {
bgneal@45 5740 // Fix for bug #2035694
bgneal@45 5741 if (tinymce.isIE)
bgneal@45 5742 v = v.replace(/^\-|\-$/g, ' ');
bgneal@45 5743
bgneal@45 5744 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
bgneal@45 5745 },
bgneal@45 5746
bgneal@45 5747 getContent : function() {
bgneal@45 5748 var h;
bgneal@45 5749
bgneal@45 5750 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
bgneal@45 5751 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
bgneal@45 5752 h = h.replace(/ ?\/>/g, ' />');
bgneal@45 5753
bgneal@45 5754 if (this.valid)
bgneal@45 5755 h = h.replace(/\%MCGT%/g, '&gt;');
bgneal@45 5756
bgneal@45 5757 return h;
bgneal@45 5758 }
bgneal@183 5759 });
bgneal@45 5760 })(tinymce);
bgneal@183 5761
bgneal@45 5762 (function(tinymce) {
bgneal@45 5763 tinymce.create('tinymce.dom.StringWriter', {
bgneal@45 5764 str : null,
bgneal@45 5765 tags : null,
bgneal@45 5766 count : 0,
bgneal@45 5767 settings : null,
bgneal@45 5768 indent : null,
bgneal@45 5769
bgneal@45 5770 StringWriter : function(s) {
bgneal@45 5771 this.settings = tinymce.extend({
bgneal@45 5772 indent_char : ' ',
bgneal@183 5773 indentation : 0
bgneal@45 5774 }, s);
bgneal@45 5775
bgneal@45 5776 this.reset();
bgneal@45 5777 },
bgneal@45 5778
bgneal@45 5779 reset : function() {
bgneal@45 5780 this.indent = '';
bgneal@45 5781 this.str = "";
bgneal@45 5782 this.tags = [];
bgneal@45 5783 this.count = 0;
bgneal@45 5784 },
bgneal@45 5785
bgneal@45 5786 writeStartElement : function(n) {
bgneal@45 5787 this._writeAttributesEnd();
bgneal@45 5788 this.writeRaw('<' + n);
bgneal@45 5789 this.tags.push(n);
bgneal@45 5790 this.inAttr = true;
bgneal@45 5791 this.count++;
bgneal@45 5792 this.elementCount = this.count;
bgneal@45 5793 },
bgneal@45 5794
bgneal@45 5795 writeAttribute : function(n, v) {
bgneal@45 5796 var t = this;
bgneal@45 5797
bgneal@45 5798 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
bgneal@45 5799 },
bgneal@45 5800
bgneal@45 5801 writeEndElement : function() {
bgneal@45 5802 var n;
bgneal@45 5803
bgneal@45 5804 if (this.tags.length > 0) {
bgneal@45 5805 n = this.tags.pop();
bgneal@45 5806
bgneal@45 5807 if (this._writeAttributesEnd(1))
bgneal@45 5808 this.writeRaw('</' + n + '>');
bgneal@45 5809
bgneal@45 5810 if (this.settings.indentation > 0)
bgneal@45 5811 this.writeRaw('\n');
bgneal@45 5812 }
bgneal@45 5813 },
bgneal@45 5814
bgneal@45 5815 writeFullEndElement : function() {
bgneal@45 5816 if (this.tags.length > 0) {
bgneal@45 5817 this._writeAttributesEnd();
bgneal@45 5818 this.writeRaw('</' + this.tags.pop() + '>');
bgneal@45 5819
bgneal@45 5820 if (this.settings.indentation > 0)
bgneal@45 5821 this.writeRaw('\n');
bgneal@45 5822 }
bgneal@45 5823 },
bgneal@45 5824
bgneal@45 5825 writeText : function(v) {
bgneal@45 5826 this._writeAttributesEnd();
bgneal@45 5827 this.writeRaw(this.encode(v));
bgneal@45 5828 this.count++;
bgneal@45 5829 },
bgneal@45 5830
bgneal@45 5831 writeCDATA : function(v) {
bgneal@45 5832 this._writeAttributesEnd();
bgneal@45 5833 this.writeRaw('<![CDATA[' + v + ']]>');
bgneal@45 5834 this.count++;
bgneal@45 5835 },
bgneal@45 5836
bgneal@45 5837 writeComment : function(v) {
bgneal@45 5838 this._writeAttributesEnd();
bgneal@45 5839 this.writeRaw('<!-- ' + v + '-->');
bgneal@45 5840 this.count++;
bgneal@45 5841 },
bgneal@45 5842
bgneal@45 5843 writeRaw : function(v) {
bgneal@45 5844 this.str += v;
bgneal@45 5845 },
bgneal@45 5846
bgneal@45 5847 encode : function(s) {
bgneal@45 5848 return s.replace(/[<>&"]/g, function(v) {
bgneal@45 5849 switch (v) {
bgneal@45 5850 case '<':
bgneal@45 5851 return '&lt;';
bgneal@45 5852
bgneal@45 5853 case '>':
bgneal@45 5854 return '&gt;';
bgneal@45 5855
bgneal@45 5856 case '&':
bgneal@45 5857 return '&amp;';
bgneal@45 5858
bgneal@45 5859 case '"':
bgneal@45 5860 return '&quot;';
bgneal@45 5861 }
bgneal@45 5862
bgneal@45 5863 return v;
bgneal@45 5864 });
bgneal@45 5865 },
bgneal@45 5866
bgneal@45 5867 getContent : function() {
bgneal@45 5868 return this.str;
bgneal@45 5869 },
bgneal@45 5870
bgneal@45 5871 _writeAttributesEnd : function(s) {
bgneal@45 5872 if (!this.inAttr)
bgneal@45 5873 return;
bgneal@45 5874
bgneal@45 5875 this.inAttr = false;
bgneal@45 5876
bgneal@45 5877 if (s && this.elementCount == this.count) {
bgneal@45 5878 this.writeRaw(' />');
bgneal@45 5879 return false;
bgneal@45 5880 }
bgneal@45 5881
bgneal@45 5882 this.writeRaw('>');
bgneal@45 5883
bgneal@45 5884 return true;
bgneal@45 5885 }
bgneal@183 5886 });
bgneal@45 5887 })(tinymce);
bgneal@183 5888
bgneal@45 5889 (function(tinymce) {
bgneal@45 5890 // Shorten names
bgneal@45 5891 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
bgneal@45 5892
bgneal@45 5893 function wildcardToRE(s) {
bgneal@45 5894 return s.replace(/([?+*])/g, '.$1');
bgneal@45 5895 };
bgneal@45 5896
bgneal@45 5897 tinymce.create('tinymce.dom.Serializer', {
bgneal@45 5898 Serializer : function(s) {
bgneal@45 5899 var t = this;
bgneal@45 5900
bgneal@45 5901 t.key = 0;
bgneal@45 5902 t.onPreProcess = new Dispatcher(t);
bgneal@45 5903 t.onPostProcess = new Dispatcher(t);
bgneal@45 5904
bgneal@45 5905 try {
bgneal@45 5906 t.writer = new tinymce.dom.XMLWriter();
bgneal@45 5907 } catch (ex) {
bgneal@45 5908 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
bgneal@45 5909 t.writer = new tinymce.dom.StringWriter();
bgneal@45 5910 }
bgneal@45 5911
bgneal@45 5912 // Default settings
bgneal@45 5913 t.settings = s = extend({
bgneal@45 5914 dom : tinymce.DOM,
bgneal@45 5915 valid_nodes : 0,
bgneal@45 5916 node_filter : 0,
bgneal@45 5917 attr_filter : 0,
bgneal@183 5918 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
bgneal@183 5919 closed : /^(br|hr|input|meta|img|link|param|area)$/,
bgneal@45 5920 entity_encoding : 'named',
bgneal@45 5921 entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
bgneal@45 5922 valid_elements : '*[*]',
bgneal@45 5923 extended_valid_elements : 0,
bgneal@45 5924 invalid_elements : 0,
bgneal@45 5925 fix_table_elements : 1,
bgneal@45 5926 fix_list_elements : true,
bgneal@45 5927 fix_content_duplication : true,
bgneal@45 5928 convert_fonts_to_spans : false,
bgneal@45 5929 font_size_classes : 0,
bgneal@45 5930 apply_source_formatting : 0,
bgneal@45 5931 indent_mode : 'simple',
bgneal@45 5932 indent_char : '\t',
bgneal@45 5933 indent_levels : 1,
bgneal@45 5934 remove_linebreaks : 1,
bgneal@45 5935 remove_redundant_brs : 1,
bgneal@45 5936 element_format : 'xhtml'
bgneal@45 5937 }, s);
bgneal@45 5938
bgneal@45 5939 t.dom = s.dom;
bgneal@183 5940 t.schema = s.schema;
bgneal@183 5941
bgneal@183 5942 // Use raw entities if no entities are defined
bgneal@183 5943 if (s.entity_encoding == 'named' && !s.entities)
bgneal@183 5944 s.entity_encoding = 'raw';
bgneal@45 5945
bgneal@45 5946 if (s.remove_redundant_brs) {
bgneal@45 5947 t.onPostProcess.add(function(se, o) {
bgneal@183 5948 // Remove single BR at end of block elements since they get rendered
bgneal@183 5949 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
bgneal@183 5950 // Check if it's a single element
bgneal@183 5951 if (/^<br \/>\s*<\//.test(a))
bgneal@183 5952 return '</' + c + '>';
bgneal@183 5953
bgneal@183 5954 return a;
bgneal@183 5955 });
bgneal@45 5956 });
bgneal@45 5957 }
bgneal@45 5958
bgneal@45 5959 // Remove XHTML element endings i.e. produce crap :) XHTML is better
bgneal@45 5960 if (s.element_format == 'html') {
bgneal@45 5961 t.onPostProcess.add(function(se, o) {
bgneal@45 5962 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
bgneal@45 5963 });
bgneal@45 5964 }
bgneal@45 5965
bgneal@45 5966 if (s.fix_list_elements) {
bgneal@45 5967 t.onPreProcess.add(function(se, o) {
bgneal@45 5968 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
bgneal@45 5969
bgneal@45 5970 function prevNode(e, n) {
bgneal@45 5971 var a = n.split(','), i;
bgneal@45 5972
bgneal@45 5973 while ((e = e.previousSibling) != null) {
bgneal@45 5974 for (i=0; i<a.length; i++) {
bgneal@45 5975 if (e.nodeName == a[i])
bgneal@45 5976 return e;
bgneal@45 5977 }
bgneal@45 5978 }
bgneal@45 5979
bgneal@45 5980 return null;
bgneal@45 5981 };
bgneal@45 5982
bgneal@45 5983 for (x=0; x<a.length; x++) {
bgneal@45 5984 nl = t.dom.select(a[x], o.node);
bgneal@45 5985
bgneal@45 5986 for (i=0; i<nl.length; i++) {
bgneal@45 5987 n = nl[i];
bgneal@45 5988 p = n.parentNode;
bgneal@45 5989
bgneal@45 5990 if (r.test(p.nodeName)) {
bgneal@45 5991 np = prevNode(n, 'LI');
bgneal@45 5992
bgneal@45 5993 if (!np) {
bgneal@45 5994 np = t.dom.create('li');
bgneal@45 5995 np.innerHTML = '&nbsp;';
bgneal@45 5996 np.appendChild(n);
bgneal@45 5997 p.insertBefore(np, p.firstChild);
bgneal@45 5998 } else
bgneal@45 5999 np.appendChild(n);
bgneal@45 6000 }
bgneal@45 6001 }
bgneal@45 6002 }
bgneal@45 6003 });
bgneal@45 6004 }
bgneal@45 6005
bgneal@45 6006 if (s.fix_table_elements) {
bgneal@45 6007 t.onPreProcess.add(function(se, o) {
bgneal@183 6008 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
bgneal@183 6009 // so Opera users with an older version will have to live with less compaible output not much we can do here
bgneal@183 6010 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
bgneal@183 6011 each(t.dom.select('p table', o.node).reverse(), function(n) {
bgneal@183 6012 var parent = t.dom.getParent(n.parentNode, 'table,p');
bgneal@183 6013
bgneal@183 6014 if (parent.nodeName != 'TABLE') {
bgneal@183 6015 try {
bgneal@183 6016 t.dom.split(parent, n);
bgneal@183 6017 } catch (ex) {
bgneal@183 6018 // IE can sometimes fire an unknown runtime error so we just ignore it
bgneal@183 6019 }
bgneal@183 6020 }
bgneal@183 6021 });
bgneal@183 6022 }
bgneal@45 6023 });
bgneal@45 6024 }
bgneal@45 6025 },
bgneal@45 6026
bgneal@45 6027 setEntities : function(s) {
bgneal@183 6028 var t = this, a, i, l = {}, v;
bgneal@45 6029
bgneal@45 6030 // No need to setup more than once
bgneal@45 6031 if (t.entityLookup)
bgneal@45 6032 return;
bgneal@45 6033
bgneal@45 6034 // Build regex and lookup array
bgneal@45 6035 a = s.split(',');
bgneal@45 6036 for (i = 0; i < a.length; i += 2) {
bgneal@45 6037 v = a[i];
bgneal@45 6038
bgneal@45 6039 // Don't add default &amp; &quot; etc.
bgneal@45 6040 if (v == 34 || v == 38 || v == 60 || v == 62)
bgneal@45 6041 continue;
bgneal@45 6042
bgneal@45 6043 l[String.fromCharCode(a[i])] = a[i + 1];
bgneal@45 6044
bgneal@45 6045 v = parseInt(a[i]).toString(16);
bgneal@183 6046 }
bgneal@183 6047
bgneal@45 6048 t.entityLookup = l;
bgneal@45 6049 },
bgneal@45 6050
bgneal@45 6051 setRules : function(s) {
bgneal@45 6052 var t = this;
bgneal@45 6053
bgneal@45 6054 t._setup();
bgneal@45 6055 t.rules = {};
bgneal@45 6056 t.wildRules = [];
bgneal@45 6057 t.validElements = {};
bgneal@45 6058
bgneal@45 6059 return t.addRules(s);
bgneal@45 6060 },
bgneal@45 6061
bgneal@45 6062 addRules : function(s) {
bgneal@45 6063 var t = this, dr;
bgneal@45 6064
bgneal@45 6065 if (!s)
bgneal@45 6066 return;
bgneal@45 6067
bgneal@45 6068 t._setup();
bgneal@45 6069
bgneal@45 6070 each(s.split(','), function(s) {
bgneal@45 6071 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
bgneal@45 6072
bgneal@45 6073 // Extend with default rules
bgneal@45 6074 if (dr)
bgneal@45 6075 at = tinymce.extend([], dr.attribs);
bgneal@45 6076
bgneal@45 6077 // Parse attributes
bgneal@45 6078 if (p.length > 1) {
bgneal@45 6079 each(p[1].split('|'), function(s) {
bgneal@45 6080 var ar = {}, i;
bgneal@45 6081
bgneal@45 6082 at = at || [];
bgneal@45 6083
bgneal@45 6084 // Parse attribute rule
bgneal@45 6085 s = s.replace(/::/g, '~');
bgneal@45 6086 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
bgneal@45 6087 s[2] = s[2].replace(/~/g, ':');
bgneal@45 6088
bgneal@45 6089 // Add required attributes
bgneal@45 6090 if (s[1] == '!') {
bgneal@45 6091 ra = ra || [];
bgneal@45 6092 ra.push(s[2]);
bgneal@45 6093 }
bgneal@45 6094
bgneal@45 6095 // Remove inherited attributes
bgneal@45 6096 if (s[1] == '-') {
bgneal@45 6097 for (i = 0; i <at.length; i++) {
bgneal@45 6098 if (at[i].name == s[2]) {
bgneal@45 6099 at.splice(i, 1);
bgneal@45 6100 return;
bgneal@45 6101 }
bgneal@45 6102 }
bgneal@45 6103 }
bgneal@45 6104
bgneal@45 6105 switch (s[3]) {
bgneal@45 6106 // Add default attrib values
bgneal@45 6107 case '=':
bgneal@45 6108 ar.defaultVal = s[4] || '';
bgneal@45 6109 break;
bgneal@45 6110
bgneal@45 6111 // Add forced attrib values
bgneal@45 6112 case ':':
bgneal@45 6113 ar.forcedVal = s[4];
bgneal@45 6114 break;
bgneal@45 6115
bgneal@45 6116 // Add validation values
bgneal@45 6117 case '<':
bgneal@45 6118 ar.validVals = s[4].split('?');
bgneal@45 6119 break;
bgneal@45 6120 }
bgneal@45 6121
bgneal@45 6122 if (/[*.?]/.test(s[2])) {
bgneal@45 6123 wat = wat || [];
bgneal@45 6124 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
bgneal@45 6125 wat.push(ar);
bgneal@45 6126 } else {
bgneal@45 6127 ar.name = s[2];
bgneal@45 6128 at.push(ar);
bgneal@45 6129 }
bgneal@45 6130
bgneal@45 6131 va.push(s[2]);
bgneal@45 6132 });
bgneal@45 6133 }
bgneal@45 6134
bgneal@45 6135 // Handle element names
bgneal@45 6136 each(tn, function(s, i) {
bgneal@45 6137 var pr = s.charAt(0), x = 1, ru = {};
bgneal@45 6138
bgneal@45 6139 // Extend with default rule data
bgneal@45 6140 if (dr) {
bgneal@45 6141 if (dr.noEmpty)
bgneal@45 6142 ru.noEmpty = dr.noEmpty;
bgneal@45 6143
bgneal@45 6144 if (dr.fullEnd)
bgneal@45 6145 ru.fullEnd = dr.fullEnd;
bgneal@45 6146
bgneal@45 6147 if (dr.padd)
bgneal@45 6148 ru.padd = dr.padd;
bgneal@45 6149 }
bgneal@45 6150
bgneal@45 6151 // Handle prefixes
bgneal@45 6152 switch (pr) {
bgneal@45 6153 case '-':
bgneal@45 6154 ru.noEmpty = true;
bgneal@45 6155 break;
bgneal@45 6156
bgneal@45 6157 case '+':
bgneal@45 6158 ru.fullEnd = true;
bgneal@45 6159 break;
bgneal@45 6160
bgneal@45 6161 case '#':
bgneal@45 6162 ru.padd = true;
bgneal@45 6163 break;
bgneal@45 6164
bgneal@45 6165 default:
bgneal@45 6166 x = 0;
bgneal@45 6167 }
bgneal@45 6168
bgneal@45 6169 tn[i] = s = s.substring(x);
bgneal@45 6170 t.validElements[s] = 1;
bgneal@45 6171
bgneal@45 6172 // Add element name or element regex
bgneal@45 6173 if (/[*.?]/.test(tn[0])) {
bgneal@45 6174 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
bgneal@45 6175 t.wildRules = t.wildRules || {};
bgneal@45 6176 t.wildRules.push(ru);
bgneal@45 6177 } else {
bgneal@45 6178 ru.name = tn[0];
bgneal@45 6179
bgneal@45 6180 // Store away default rule
bgneal@45 6181 if (tn[0] == '@')
bgneal@45 6182 dr = ru;
bgneal@45 6183
bgneal@45 6184 t.rules[s] = ru;
bgneal@45 6185 }
bgneal@45 6186
bgneal@45 6187 ru.attribs = at;
bgneal@45 6188
bgneal@45 6189 if (ra)
bgneal@45 6190 ru.requiredAttribs = ra;
bgneal@45 6191
bgneal@45 6192 if (wat) {
bgneal@45 6193 // Build valid attributes regexp
bgneal@45 6194 s = '';
bgneal@45 6195 each(va, function(v) {
bgneal@45 6196 if (s)
bgneal@45 6197 s += '|';
bgneal@45 6198
bgneal@45 6199 s += '(' + wildcardToRE(v) + ')';
bgneal@45 6200 });
bgneal@45 6201 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
bgneal@45 6202 ru.wildAttribs = wat;
bgneal@45 6203 }
bgneal@45 6204 });
bgneal@45 6205 });
bgneal@45 6206
bgneal@45 6207 // Build valid elements regexp
bgneal@45 6208 s = '';
bgneal@45 6209 each(t.validElements, function(v, k) {
bgneal@45 6210 if (s)
bgneal@45 6211 s += '|';
bgneal@45 6212
bgneal@45 6213 if (k != '@')
bgneal@45 6214 s += k;
bgneal@45 6215 });
bgneal@45 6216 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
bgneal@45 6217
bgneal@45 6218 //console.debug(t.validElementsRE.toString());
bgneal@45 6219 //console.dir(t.rules);
bgneal@45 6220 //console.dir(t.wildRules);
bgneal@45 6221 },
bgneal@45 6222
bgneal@45 6223 findRule : function(n) {
bgneal@45 6224 var t = this, rl = t.rules, i, r;
bgneal@45 6225
bgneal@45 6226 t._setup();
bgneal@45 6227
bgneal@45 6228 // Exact match
bgneal@45 6229 r = rl[n];
bgneal@45 6230 if (r)
bgneal@45 6231 return r;
bgneal@45 6232
bgneal@45 6233 // Try wildcards
bgneal@45 6234 rl = t.wildRules;
bgneal@45 6235 for (i = 0; i < rl.length; i++) {
bgneal@45 6236 if (rl[i].nameRE.test(n))
bgneal@45 6237 return rl[i];
bgneal@45 6238 }
bgneal@45 6239
bgneal@45 6240 return null;
bgneal@45 6241 },
bgneal@45 6242
bgneal@45 6243 findAttribRule : function(ru, n) {
bgneal@45 6244 var i, wa = ru.wildAttribs;
bgneal@45 6245
bgneal@45 6246 for (i = 0; i < wa.length; i++) {
bgneal@45 6247 if (wa[i].nameRE.test(n))
bgneal@45 6248 return wa[i];
bgneal@45 6249 }
bgneal@45 6250
bgneal@45 6251 return null;
bgneal@45 6252 },
bgneal@45 6253
bgneal@45 6254 serialize : function(n, o) {
bgneal@183 6255 var h, t = this, doc, oldDoc, impl, selected;
bgneal@45 6256
bgneal@45 6257 t._setup();
bgneal@45 6258 o = o || {};
bgneal@45 6259 o.format = o.format || 'html';
bgneal@45 6260 t.processObj = o;
bgneal@183 6261
bgneal@183 6262 // IE looses the selected attribute on option elements so we need to store it
bgneal@183 6263 // See: http://support.microsoft.com/kb/829907
bgneal@183 6264 if (isIE) {
bgneal@183 6265 selected = [];
bgneal@183 6266 each(n.getElementsByTagName('option'), function(n) {
bgneal@183 6267 var v = t.dom.getAttrib(n, 'selected');
bgneal@183 6268
bgneal@183 6269 selected.push(v ? v : null);
bgneal@183 6270 });
bgneal@183 6271 }
bgneal@183 6272
bgneal@45 6273 n = n.cloneNode(true);
bgneal@183 6274
bgneal@183 6275 // IE looses the selected attribute on option elements so we need to restore it
bgneal@183 6276 if (isIE) {
bgneal@183 6277 each(n.getElementsByTagName('option'), function(n, i) {
bgneal@183 6278 t.dom.setAttrib(n, 'selected', selected[i]);
bgneal@183 6279 });
bgneal@183 6280 }
bgneal@183 6281
bgneal@183 6282 // Nodes needs to be attached to something in WebKit/Opera
bgneal@183 6283 // Older builds of Opera crashes if you attach the node to an document created dynamically
bgneal@183 6284 // and since we can't feature detect a crash we need to sniff the acutal build number
bgneal@183 6285 // This fix will make DOM ranges and make Sizzle happy!
bgneal@183 6286 impl = n.ownerDocument.implementation;
bgneal@183 6287 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
bgneal@183 6288 // Create an empty HTML document
bgneal@183 6289 doc = impl.createHTMLDocument("");
bgneal@183 6290
bgneal@183 6291 // Add the element or it's children if it's a body element to the new document
bgneal@183 6292 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
bgneal@183 6293 doc.body.appendChild(doc.importNode(node, true));
bgneal@183 6294 });
bgneal@183 6295
bgneal@183 6296 // Grab first child or body element for serialization
bgneal@183 6297 if (n.nodeName != 'BODY')
bgneal@183 6298 n = doc.body.firstChild;
bgneal@183 6299 else
bgneal@183 6300 n = doc.body;
bgneal@183 6301
bgneal@183 6302 // set the new document in DOMUtils so createElement etc works
bgneal@183 6303 oldDoc = t.dom.doc;
bgneal@183 6304 t.dom.doc = doc;
bgneal@183 6305 }
bgneal@183 6306
bgneal@45 6307 t.key = '' + (parseInt(t.key) + 1);
bgneal@45 6308
bgneal@45 6309 // Pre process
bgneal@45 6310 if (!o.no_events) {
bgneal@45 6311 o.node = n;
bgneal@45 6312 t.onPreProcess.dispatch(t, o);
bgneal@45 6313 }
bgneal@45 6314
bgneal@45 6315 // Serialize HTML DOM into a string
bgneal@45 6316 t.writer.reset();
bgneal@183 6317 t._info = o;
bgneal@45 6318 t._serializeNode(n, o.getInner);
bgneal@45 6319
bgneal@45 6320 // Post process
bgneal@45 6321 o.content = t.writer.getContent();
bgneal@45 6322
bgneal@183 6323 // Restore the old document if it was changed
bgneal@183 6324 if (oldDoc)
bgneal@183 6325 t.dom.doc = oldDoc;
bgneal@183 6326
bgneal@45 6327 if (!o.no_events)
bgneal@45 6328 t.onPostProcess.dispatch(t, o);
bgneal@45 6329
bgneal@45 6330 t._postProcess(o);
bgneal@45 6331 o.node = null;
bgneal@45 6332
bgneal@45 6333 return tinymce.trim(o.content);
bgneal@45 6334 },
bgneal@45 6335
bgneal@45 6336 // Internal functions
bgneal@45 6337
bgneal@45 6338 _postProcess : function(o) {
bgneal@45 6339 var t = this, s = t.settings, h = o.content, sc = [], p;
bgneal@45 6340
bgneal@45 6341 if (o.format == 'html') {
bgneal@45 6342 // Protect some elements
bgneal@45 6343 p = t._protect({
bgneal@45 6344 content : h,
bgneal@45 6345 patterns : [
bgneal@45 6346 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
bgneal@183 6347 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
bgneal@45 6348 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
bgneal@45 6349 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
bgneal@45 6350 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
bgneal@45 6351 ]
bgneal@45 6352 });
bgneal@45 6353
bgneal@45 6354 h = p.content;
bgneal@45 6355
bgneal@45 6356 // Entity encode
bgneal@45 6357 if (s.entity_encoding !== 'raw')
bgneal@45 6358 h = t._encode(h);
bgneal@45 6359
bgneal@45 6360 // Use BR instead of &nbsp; padded P elements inside editor and use <p>&nbsp;</p> outside editor
bgneal@45 6361 /* if (o.set)
bgneal@45 6362 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
bgneal@45 6363 else
bgneal@45 6364 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
bgneal@45 6365
bgneal@45 6366 // Since Gecko and Safari keeps whitespace in the DOM we need to
bgneal@45 6367 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
bgneal@45 6368 // This process is only done when getting contents out from the editor.
bgneal@45 6369 if (!o.set) {
bgneal@45 6370 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
bgneal@45 6371 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1>&#160;</p>' : '<p$1>&nbsp;</p>');
bgneal@45 6372
bgneal@45 6373 if (s.remove_linebreaks) {
bgneal@45 6374 h = h.replace(/\r?\n|\r/g, ' ');
bgneal@45 6375 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
bgneal@45 6376 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
bgneal@45 6377 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
bgneal@45 6378 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
bgneal@45 6379 h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
bgneal@45 6380 }
bgneal@45 6381
bgneal@45 6382 // Simple indentation
bgneal@45 6383 if (s.apply_source_formatting && s.indent_mode == 'simple') {
bgneal@45 6384 // Add line breaks before and after block elements
bgneal@45 6385 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
bgneal@45 6386 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
bgneal@45 6387 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
bgneal@45 6388 h = h.replace(/\n\n/g, '\n');
bgneal@45 6389 }
bgneal@45 6390 }
bgneal@45 6391
bgneal@45 6392 h = t._unprotect(h, p);
bgneal@45 6393
bgneal@45 6394 // Restore CDATA sections
bgneal@45 6395 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
bgneal@45 6396
bgneal@45 6397 // Restore the \u00a0 character if raw mode is enabled
bgneal@45 6398 if (s.entity_encoding == 'raw')
bgneal@45 6399 h = h.replace(/<p>&nbsp;<\/p>|<p([^>]+)>&nbsp;<\/p>/g, '<p$1>\u00a0</p>');
bgneal@183 6400
bgneal@183 6401 // Restore noscript elements
bgneal@183 6402 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@183 6403 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
bgneal@183 6404 });
bgneal@45 6405 }
bgneal@45 6406
bgneal@45 6407 o.content = h;
bgneal@45 6408 },
bgneal@45 6409
bgneal@183 6410 _serializeNode : function(n, inner) {
bgneal@247 6411 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
bgneal@45 6412
bgneal@45 6413 if (!s.node_filter || s.node_filter(n)) {
bgneal@45 6414 switch (n.nodeType) {
bgneal@45 6415 case 1: // Element
bgneal@183 6416 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
bgneal@45 6417 return;
bgneal@45 6418
bgneal@183 6419 iv = keep = false;
bgneal@45 6420 hc = n.hasChildNodes();
bgneal@183 6421 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
bgneal@183 6422
bgneal@183 6423 // Get internal type
bgneal@183 6424 type = n.getAttribute('_mce_type');
bgneal@183 6425 if (type) {
bgneal@183 6426 if (!t._info.cleanup) {
bgneal@183 6427 iv = true;
bgneal@183 6428 return;
bgneal@183 6429 } else
bgneal@183 6430 keep = 1;
bgneal@183 6431 }
bgneal@45 6432
bgneal@45 6433 // Add correct prefix on IE
bgneal@45 6434 if (isIE) {
bgneal@247 6435 scopeName = n.scopeName;
bgneal@247 6436 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
bgneal@247 6437 nn = scopeName + ':' + nn;
bgneal@45 6438 }
bgneal@45 6439
bgneal@45 6440 // Remove mce prefix on IE needed for the abbr element
bgneal@45 6441 if (nn.indexOf('mce:') === 0)
bgneal@45 6442 nn = nn.substring(4);
bgneal@45 6443
bgneal@45 6444 // Check if valid
bgneal@183 6445 if (!keep) {
bgneal@183 6446 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
bgneal@183 6447 iv = true;
bgneal@183 6448 break;
bgneal@183 6449 }
bgneal@45 6450 }
bgneal@45 6451
bgneal@45 6452 if (isIE) {
bgneal@45 6453 // Fix IE content duplication (DOM can have multiple copies of the same node)
bgneal@45 6454 if (s.fix_content_duplication) {
bgneal@183 6455 if (n._mce_serialized == t.key)
bgneal@45 6456 return;
bgneal@45 6457
bgneal@183 6458 n._mce_serialized = t.key;
bgneal@45 6459 }
bgneal@45 6460
bgneal@45 6461 // IE sometimes adds a / infront of the node name
bgneal@45 6462 if (nn.charAt(0) == '/')
bgneal@45 6463 nn = nn.substring(1);
bgneal@45 6464 } else if (isGecko) {
bgneal@45 6465 // Ignore br elements
bgneal@45 6466 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
bgneal@45 6467 return;
bgneal@45 6468 }
bgneal@45 6469
bgneal@45 6470 // Check if valid child
bgneal@183 6471 if (s.validate_children) {
bgneal@183 6472 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
bgneal@183 6473 iv = true;
bgneal@183 6474 break;
bgneal@45 6475 }
bgneal@45 6476
bgneal@45 6477 t.elementName = nn;
bgneal@45 6478 }
bgneal@45 6479
bgneal@45 6480 ru = t.findRule(nn);
bgneal@183 6481
bgneal@183 6482 // No valid rule for this element could be found then skip it
bgneal@183 6483 if (!ru) {
bgneal@183 6484 iv = true;
bgneal@183 6485 break;
bgneal@183 6486 }
bgneal@183 6487
bgneal@45 6488 nn = ru.name || nn;
bgneal@183 6489 closed = s.closed.test(nn);
bgneal@45 6490
bgneal@45 6491 // Skip empty nodes or empty node name in IE
bgneal@45 6492 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
bgneal@45 6493 iv = true;
bgneal@45 6494 break;
bgneal@45 6495 }
bgneal@45 6496
bgneal@45 6497 // Check required
bgneal@45 6498 if (ru.requiredAttribs) {
bgneal@45 6499 a = ru.requiredAttribs;
bgneal@45 6500
bgneal@45 6501 for (i = a.length - 1; i >= 0; i--) {
bgneal@45 6502 if (this.dom.getAttrib(n, a[i]) !== '')
bgneal@45 6503 break;
bgneal@45 6504 }
bgneal@45 6505
bgneal@45 6506 // None of the required was there
bgneal@45 6507 if (i == -1) {
bgneal@45 6508 iv = true;
bgneal@45 6509 break;
bgneal@45 6510 }
bgneal@45 6511 }
bgneal@45 6512
bgneal@45 6513 w.writeStartElement(nn);
bgneal@45 6514
bgneal@45 6515 // Add ordered attributes
bgneal@45 6516 if (ru.attribs) {
bgneal@45 6517 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
bgneal@45 6518 a = at[i];
bgneal@45 6519 v = t._getAttrib(n, a);
bgneal@45 6520
bgneal@45 6521 if (v !== null)
bgneal@45 6522 w.writeAttribute(a.name, v);
bgneal@45 6523 }
bgneal@45 6524 }
bgneal@45 6525
bgneal@45 6526 // Add wild attributes
bgneal@45 6527 if (ru.validAttribsRE) {
bgneal@45 6528 at = t.dom.getAttribs(n);
bgneal@45 6529 for (i=at.length-1; i>-1; i--) {
bgneal@45 6530 no = at[i];
bgneal@45 6531
bgneal@45 6532 if (no.specified) {
bgneal@45 6533 a = no.nodeName.toLowerCase();
bgneal@45 6534
bgneal@45 6535 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
bgneal@45 6536 continue;
bgneal@45 6537
bgneal@45 6538 ar = t.findAttribRule(ru, a);
bgneal@45 6539 v = t._getAttrib(n, ar, a);
bgneal@45 6540
bgneal@45 6541 if (v !== null)
bgneal@45 6542 w.writeAttribute(a, v);
bgneal@45 6543 }
bgneal@45 6544 }
bgneal@45 6545 }
bgneal@45 6546
bgneal@183 6547 // Keep type attribute
bgneal@183 6548 if (type && keep)
bgneal@183 6549 w.writeAttribute('_mce_type', type);
bgneal@183 6550
bgneal@183 6551 // Write text from script
bgneal@183 6552 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
bgneal@183 6553 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
bgneal@183 6554 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
bgneal@183 6555 hc = false;
bgneal@183 6556 break;
bgneal@183 6557 }
bgneal@183 6558
bgneal@45 6559 // Padd empty nodes with a &nbsp;
bgneal@45 6560 if (ru.padd) {
bgneal@45 6561 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
bgneal@45 6562 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
bgneal@183 6563 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
bgneal@45 6564 w.writeText('\u00a0');
bgneal@45 6565 } else if (!hc)
bgneal@45 6566 w.writeText('\u00a0'); // No children then padd it
bgneal@45 6567 }
bgneal@45 6568
bgneal@45 6569 break;
bgneal@45 6570
bgneal@45 6571 case 3: // Text
bgneal@45 6572 // Check if valid child
bgneal@183 6573 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
bgneal@183 6574 return;
bgneal@45 6575
bgneal@45 6576 return w.writeText(n.nodeValue);
bgneal@45 6577
bgneal@45 6578 case 4: // CDATA
bgneal@45 6579 return w.writeCDATA(n.nodeValue);
bgneal@45 6580
bgneal@45 6581 case 8: // Comment
bgneal@45 6582 return w.writeComment(n.nodeValue);
bgneal@45 6583 }
bgneal@45 6584 } else if (n.nodeType == 1)
bgneal@45 6585 hc = n.hasChildNodes();
bgneal@45 6586
bgneal@183 6587 if (hc && !closed) {
bgneal@45 6588 cn = n.firstChild;
bgneal@45 6589
bgneal@45 6590 while (cn) {
bgneal@45 6591 t._serializeNode(cn);
bgneal@45 6592 t.elementName = nn;
bgneal@45 6593 cn = cn.nextSibling;
bgneal@45 6594 }
bgneal@45 6595 }
bgneal@45 6596
bgneal@45 6597 // Write element end
bgneal@45 6598 if (!iv) {
bgneal@183 6599 if (!closed)
bgneal@45 6600 w.writeFullEndElement();
bgneal@45 6601 else
bgneal@45 6602 w.writeEndElement();
bgneal@45 6603 }
bgneal@45 6604 },
bgneal@45 6605
bgneal@45 6606 _protect : function(o) {
bgneal@45 6607 var t = this;
bgneal@45 6608
bgneal@45 6609 o.items = o.items || [];
bgneal@45 6610
bgneal@45 6611 function enc(s) {
bgneal@45 6612 return s.replace(/[\r\n\\]/g, function(c) {
bgneal@45 6613 if (c === '\n')
bgneal@45 6614 return '\\n';
bgneal@45 6615 else if (c === '\\')
bgneal@45 6616 return '\\\\';
bgneal@45 6617
bgneal@45 6618 return '\\r';
bgneal@45 6619 });
bgneal@45 6620 };
bgneal@45 6621
bgneal@45 6622 function dec(s) {
bgneal@45 6623 return s.replace(/\\[\\rn]/g, function(c) {
bgneal@45 6624 if (c === '\\n')
bgneal@45 6625 return '\n';
bgneal@45 6626 else if (c === '\\\\')
bgneal@45 6627 return '\\';
bgneal@45 6628
bgneal@45 6629 return '\r';
bgneal@45 6630 });
bgneal@45 6631 };
bgneal@45 6632
bgneal@45 6633 each(o.patterns, function(p) {
bgneal@45 6634 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
bgneal@45 6635 b = dec(b);
bgneal@45 6636
bgneal@45 6637 if (p.encode)
bgneal@45 6638 b = t._encode(b);
bgneal@45 6639
bgneal@45 6640 o.items.push(b);
bgneal@45 6641 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
bgneal@45 6642 }));
bgneal@45 6643 });
bgneal@45 6644
bgneal@45 6645 return o;
bgneal@45 6646 },
bgneal@45 6647
bgneal@45 6648 _unprotect : function(h, o) {
bgneal@45 6649 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
bgneal@45 6650 return o.items[parseInt(b)];
bgneal@45 6651 });
bgneal@45 6652
bgneal@45 6653 o.items = [];
bgneal@45 6654
bgneal@45 6655 return h;
bgneal@45 6656 },
bgneal@45 6657
bgneal@45 6658 _encode : function(h) {
bgneal@45 6659 var t = this, s = t.settings, l;
bgneal@45 6660
bgneal@45 6661 // Entity encode
bgneal@45 6662 if (s.entity_encoding !== 'raw') {
bgneal@45 6663 if (s.entity_encoding.indexOf('named') != -1) {
bgneal@45 6664 t.setEntities(s.entities);
bgneal@45 6665 l = t.entityLookup;
bgneal@45 6666
bgneal@183 6667 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@45 6668 var v;
bgneal@45 6669
bgneal@45 6670 if (v = l[a])
bgneal@45 6671 a = '&' + v + ';';
bgneal@45 6672
bgneal@45 6673 return a;
bgneal@45 6674 });
bgneal@45 6675 }
bgneal@45 6676
bgneal@45 6677 if (s.entity_encoding.indexOf('numeric') != -1) {
bgneal@45 6678 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@45 6679 return '&#' + a.charCodeAt(0) + ';';
bgneal@45 6680 });
bgneal@45 6681 }
bgneal@45 6682 }
bgneal@45 6683
bgneal@45 6684 return h;
bgneal@45 6685 },
bgneal@45 6686
bgneal@45 6687 _setup : function() {
bgneal@45 6688 var t = this, s = this.settings;
bgneal@45 6689
bgneal@45 6690 if (t.done)
bgneal@45 6691 return;
bgneal@45 6692
bgneal@45 6693 t.done = 1;
bgneal@45 6694
bgneal@45 6695 t.setRules(s.valid_elements);
bgneal@45 6696 t.addRules(s.extended_valid_elements);
bgneal@45 6697
bgneal@45 6698 if (s.invalid_elements)
bgneal@45 6699 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
bgneal@45 6700
bgneal@45 6701 if (s.attrib_value_filter)
bgneal@45 6702 t.attribValueFilter = s.attribValueFilter;
bgneal@45 6703 },
bgneal@45 6704
bgneal@45 6705 _getAttrib : function(n, a, na) {
bgneal@45 6706 var i, v;
bgneal@45 6707
bgneal@45 6708 na = na || a.name;
bgneal@45 6709
bgneal@45 6710 if (a.forcedVal && (v = a.forcedVal)) {
bgneal@45 6711 if (v === '{$uid}')
bgneal@45 6712 return this.dom.uniqueId();
bgneal@45 6713
bgneal@45 6714 return v;
bgneal@45 6715 }
bgneal@45 6716
bgneal@45 6717 v = this.dom.getAttrib(n, na);
bgneal@45 6718
bgneal@45 6719 switch (na) {
bgneal@45 6720 case 'rowspan':
bgneal@45 6721 case 'colspan':
bgneal@45 6722 // Whats the point? Remove usless attribute value
bgneal@45 6723 if (v == '1')
bgneal@45 6724 v = '';
bgneal@45 6725
bgneal@45 6726 break;
bgneal@45 6727 }
bgneal@45 6728
bgneal@45 6729 if (this.attribValueFilter)
bgneal@45 6730 v = this.attribValueFilter(na, v, n);
bgneal@45 6731
bgneal@45 6732 if (a.validVals) {
bgneal@45 6733 for (i = a.validVals.length - 1; i >= 0; i--) {
bgneal@45 6734 if (v == a.validVals[i])
bgneal@45 6735 break;
bgneal@45 6736 }
bgneal@45 6737
bgneal@45 6738 if (i == -1)
bgneal@45 6739 return null;
bgneal@45 6740 }
bgneal@45 6741
bgneal@45 6742 if (v === '' && typeof(a.defaultVal) != 'undefined') {
bgneal@45 6743 v = a.defaultVal;
bgneal@45 6744
bgneal@45 6745 if (v === '{$uid}')
bgneal@45 6746 return this.dom.uniqueId();
bgneal@45 6747
bgneal@45 6748 return v;
bgneal@45 6749 } else {
bgneal@45 6750 // Remove internal mceItemXX classes when content is extracted from editor
bgneal@45 6751 if (na == 'class' && this.processObj.get)
bgneal@45 6752 v = v.replace(/\s?mceItem\w+\s?/g, '');
bgneal@45 6753 }
bgneal@45 6754
bgneal@45 6755 if (v === '')
bgneal@45 6756 return null;
bgneal@45 6757
bgneal@45 6758
bgneal@45 6759 return v;
bgneal@45 6760 }
bgneal@183 6761 });
bgneal@45 6762 })(tinymce);
bgneal@183 6763
bgneal@45 6764 (function(tinymce) {
bgneal@183 6765 tinymce.dom.ScriptLoader = function(settings) {
bgneal@183 6766 var QUEUED = 0,
bgneal@183 6767 LOADING = 1,
bgneal@183 6768 LOADED = 2,
bgneal@183 6769 states = {},
bgneal@183 6770 queue = [],
bgneal@183 6771 scriptLoadedCallbacks = {},
bgneal@183 6772 queueLoadedCallbacks = [],
bgneal@183 6773 loading = 0,
bgneal@183 6774 undefined;
bgneal@183 6775
bgneal@183 6776 function loadScript(url, callback) {
bgneal@183 6777 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
bgneal@183 6778
bgneal@183 6779 // Execute callback when script is loaded
bgneal@183 6780 function done() {
bgneal@183 6781 dom.remove(id);
bgneal@183 6782
bgneal@183 6783 if (elm)
bgneal@183 6784 elm.onreadystatechange = elm.onload = elm = null;
bgneal@183 6785
bgneal@183 6786 callback();
bgneal@183 6787 };
bgneal@183 6788
bgneal@183 6789 id = dom.uniqueId();
bgneal@183 6790
bgneal@183 6791 if (tinymce.isIE6) {
bgneal@183 6792 uri = new tinymce.util.URI(url);
bgneal@183 6793 loc = location;
bgneal@183 6794
bgneal@183 6795 // If script is from same domain and we
bgneal@183 6796 // use IE 6 then use XHR since it's more reliable
bgneal@183 6797 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
bgneal@45 6798 tinymce.util.XHR.send({
bgneal@183 6799 url : tinymce._addVer(uri.getURI()),
bgneal@183 6800 success : function(content) {
bgneal@183 6801 // Create new temp script element
bgneal@183 6802 var script = dom.create('script', {
bgneal@183 6803 type : 'text/javascript'
bgneal@183 6804 });
bgneal@183 6805
bgneal@183 6806 // Evaluate script in global scope
bgneal@183 6807 script.text = content;
bgneal@183 6808 document.getElementsByTagName('head')[0].appendChild(script);
bgneal@183 6809 dom.remove(script);
bgneal@183 6810
bgneal@183 6811 done();
bgneal@45 6812 }
bgneal@45 6813 });
bgneal@183 6814
bgneal@183 6815 return;
bgneal@183 6816 }
bgneal@183 6817 }
bgneal@183 6818
bgneal@183 6819 // Create new script element
bgneal@183 6820 elm = dom.create('script', {
bgneal@183 6821 id : id,
bgneal@183 6822 type : 'text/javascript',
bgneal@183 6823 src : tinymce._addVer(url)
bgneal@183 6824 });
bgneal@183 6825
bgneal@183 6826 // Add onload and readystate listeners
bgneal@183 6827 elm.onload = done;
bgneal@183 6828 elm.onreadystatechange = function() {
bgneal@183 6829 var state = elm.readyState;
bgneal@183 6830
bgneal@183 6831 // Loaded state is passed on IE 6 however there
bgneal@183 6832 // are known issues with this method but we can't use
bgneal@183 6833 // XHR in a cross domain loading
bgneal@183 6834 if (state == 'complete' || state == 'loaded')
bgneal@183 6835 done();
bgneal@45 6836 };
bgneal@45 6837
bgneal@183 6838 // Most browsers support this feature so we report errors
bgneal@183 6839 // for those at least to help users track their missing plugins etc
bgneal@183 6840 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
bgneal@183 6841 /*elm.onerror = function() {
bgneal@183 6842 alert('Failed to load: ' + url);
bgneal@183 6843 };*/
bgneal@183 6844
bgneal@183 6845 // Add script to document
bgneal@183 6846 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
bgneal@183 6847 };
bgneal@183 6848
bgneal@183 6849 this.isDone = function(url) {
bgneal@183 6850 return states[url] == LOADED;
bgneal@183 6851 };
bgneal@183 6852
bgneal@183 6853 this.markDone = function(url) {
bgneal@183 6854 states[url] = LOADED;
bgneal@183 6855 };
bgneal@183 6856
bgneal@183 6857 this.add = this.load = function(url, callback, scope) {
bgneal@183 6858 var item, state = states[url];
bgneal@183 6859
bgneal@183 6860 // Add url to load queue
bgneal@183 6861 if (state == undefined) {
bgneal@183 6862 queue.push(url);
bgneal@183 6863 states[url] = QUEUED;
bgneal@183 6864 }
bgneal@183 6865
bgneal@183 6866 if (callback) {
bgneal@183 6867 // Store away callback for later execution
bgneal@183 6868 if (!scriptLoadedCallbacks[url])
bgneal@183 6869 scriptLoadedCallbacks[url] = [];
bgneal@183 6870
bgneal@183 6871 scriptLoadedCallbacks[url].push({
bgneal@183 6872 func : callback,
bgneal@183 6873 scope : scope || this
bgneal@183 6874 });
bgneal@183 6875 }
bgneal@183 6876 };
bgneal@183 6877
bgneal@183 6878 this.loadQueue = function(callback, scope) {
bgneal@183 6879 this.loadScripts(queue, callback, scope);
bgneal@183 6880 };
bgneal@183 6881
bgneal@183 6882 this.loadScripts = function(scripts, callback, scope) {
bgneal@183 6883 var loadScripts;
bgneal@183 6884
bgneal@183 6885 function execScriptLoadedCallbacks(url) {
bgneal@183 6886 // Execute URL callback functions
bgneal@183 6887 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
bgneal@183 6888 callback.func.call(callback.scope);
bgneal@183 6889 });
bgneal@183 6890
bgneal@183 6891 scriptLoadedCallbacks[url] = undefined;
bgneal@183 6892 };
bgneal@183 6893
bgneal@183 6894 queueLoadedCallbacks.push({
bgneal@183 6895 func : callback,
bgneal@183 6896 scope : scope || this
bgneal@183 6897 });
bgneal@183 6898
bgneal@183 6899 loadScripts = function() {
bgneal@183 6900 var loadingScripts = tinymce.grep(scripts);
bgneal@183 6901
bgneal@183 6902 // Current scripts has been handled
bgneal@183 6903 scripts.length = 0;
bgneal@183 6904
bgneal@183 6905 // Load scripts that needs to be loaded
bgneal@183 6906 tinymce.each(loadingScripts, function(url) {
bgneal@183 6907 // Script is already loaded then execute script callbacks directly
bgneal@183 6908 if (states[url] == LOADED) {
bgneal@183 6909 execScriptLoadedCallbacks(url);
bgneal@183 6910 return;
bgneal@183 6911 }
bgneal@183 6912
bgneal@183 6913 // Is script not loading then start loading it
bgneal@183 6914 if (states[url] != LOADING) {
bgneal@183 6915 states[url] = LOADING;
bgneal@183 6916 loading++;
bgneal@183 6917
bgneal@183 6918 loadScript(url, function() {
bgneal@183 6919 states[url] = LOADED;
bgneal@183 6920 loading--;
bgneal@183 6921
bgneal@183 6922 execScriptLoadedCallbacks(url);
bgneal@183 6923
bgneal@183 6924 // Load more scripts if they where added by the recently loaded script
bgneal@183 6925 loadScripts();
bgneal@183 6926 });
bgneal@183 6927 }
bgneal@183 6928 });
bgneal@183 6929
bgneal@183 6930 // No scripts are currently loading then execute all pending queue loaded callbacks
bgneal@183 6931 if (!loading) {
bgneal@183 6932 tinymce.each(queueLoadedCallbacks, function(callback) {
bgneal@183 6933 callback.func.call(callback.scope);
bgneal@45 6934 });
bgneal@183 6935
bgneal@183 6936 queueLoadedCallbacks.length = 0;
bgneal@183 6937 }
bgneal@45 6938 };
bgneal@45 6939
bgneal@183 6940 loadScripts();
bgneal@183 6941 };
bgneal@183 6942 };
bgneal@45 6943
bgneal@45 6944 // Global script loader
bgneal@45 6945 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
bgneal@45 6946 })(tinymce);
bgneal@183 6947
bgneal@183 6948 tinymce.dom.TreeWalker = function(start_node, root_node) {
bgneal@183 6949 var node = start_node;
bgneal@183 6950
bgneal@183 6951 function findSibling(node, start_name, sibling_name, shallow) {
bgneal@183 6952 var sibling, parent;
bgneal@183 6953
bgneal@183 6954 if (node) {
bgneal@183 6955 // Walk into nodes if it has a start
bgneal@183 6956 if (!shallow && node[start_name])
bgneal@183 6957 return node[start_name];
bgneal@183 6958
bgneal@183 6959 // Return the sibling if it has one
bgneal@183 6960 if (node != root_node) {
bgneal@183 6961 sibling = node[sibling_name];
bgneal@183 6962 if (sibling)
bgneal@183 6963 return sibling;
bgneal@183 6964
bgneal@183 6965 // Walk up the parents to look for siblings
bgneal@183 6966 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
bgneal@183 6967 sibling = parent[sibling_name];
bgneal@183 6968 if (sibling)
bgneal@183 6969 return sibling;
bgneal@183 6970 }
bgneal@183 6971 }
bgneal@183 6972 }
bgneal@183 6973 };
bgneal@183 6974
bgneal@183 6975 this.current = function() {
bgneal@183 6976 return node;
bgneal@183 6977 };
bgneal@183 6978
bgneal@183 6979 this.next = function(shallow) {
bgneal@183 6980 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
bgneal@183 6981 };
bgneal@183 6982
bgneal@183 6983 this.prev = function(shallow) {
bgneal@183 6984 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
bgneal@183 6985 };
bgneal@183 6986 };
bgneal@183 6987
bgneal@183 6988 (function() {
bgneal@183 6989 var transitional = {};
bgneal@183 6990
bgneal@183 6991 function unpack(lookup, data) {
bgneal@183 6992 var key;
bgneal@183 6993
bgneal@183 6994 function replace(value) {
bgneal@183 6995 return value.replace(/[A-Z]+/g, function(key) {
bgneal@183 6996 return replace(lookup[key]);
bgneal@183 6997 });
bgneal@183 6998 };
bgneal@183 6999
bgneal@183 7000 // Unpack lookup
bgneal@183 7001 for (key in lookup) {
bgneal@183 7002 if (lookup.hasOwnProperty(key))
bgneal@183 7003 lookup[key] = replace(lookup[key]);
bgneal@183 7004 }
bgneal@183 7005
bgneal@183 7006 // Unpack and parse data into object map
bgneal@183 7007 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
bgneal@183 7008 var i, map = {};
bgneal@183 7009
bgneal@183 7010 children = children.split(/\|/);
bgneal@183 7011
bgneal@183 7012 for (i = children.length - 1; i >= 0; i--)
bgneal@183 7013 map[children[i]] = 1;
bgneal@183 7014
bgneal@183 7015 transitional[name] = map;
bgneal@183 7016 });
bgneal@183 7017 };
bgneal@183 7018
bgneal@183 7019 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
bgneal@183 7020 // we will later include the attributes here and use it as a default for valid elements but it
bgneal@183 7021 // requires us to rewrite the serializer engine
bgneal@183 7022 unpack({
bgneal@183 7023 Z : '#|H|K|N|O|P',
bgneal@183 7024 Y : '#|X|form|R|Q',
bgneal@183 7025 X : 'p|T|div|U|W|isindex|fieldset|table',
bgneal@183 7026 W : 'pre|hr|blockquote|address|center|noframes',
bgneal@183 7027 U : 'ul|ol|dl|menu|dir',
bgneal@183 7028 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
bgneal@183 7029 T : 'h1|h2|h3|h4|h5|h6',
bgneal@183 7030 ZB : '#|X|S|Q',
bgneal@183 7031 S : 'R|P',
bgneal@183 7032 ZA : '#|a|G|J|M|O|P',
bgneal@183 7033 R : '#|a|H|K|N|O',
bgneal@183 7034 Q : 'noscript|P',
bgneal@183 7035 P : 'ins|del|script',
bgneal@183 7036 O : 'input|select|textarea|label|button',
bgneal@183 7037 N : 'M|L',
bgneal@183 7038 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
bgneal@183 7039 L : 'sub|sup',
bgneal@183 7040 K : 'J|I',
bgneal@183 7041 J : 'tt|i|b|u|s|strike',
bgneal@183 7042 I : 'big|small|font|basefont',
bgneal@183 7043 H : 'G|F',
bgneal@183 7044 G : 'br|span|bdo',
bgneal@183 7045 F : 'object|applet|img|map|iframe'
bgneal@183 7046 }, 'script[]' +
bgneal@183 7047 'style[]' +
bgneal@183 7048 'object[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@183 7049 'param[]' +
bgneal@183 7050 'p[S]' +
bgneal@183 7051 'a[Z]' +
bgneal@183 7052 'br[]' +
bgneal@183 7053 'span[S]' +
bgneal@183 7054 'bdo[S]' +
bgneal@183 7055 'applet[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@183 7056 'h1[S]' +
bgneal@183 7057 'img[]' +
bgneal@183 7058 'map[X|form|Q|area]' +
bgneal@183 7059 'h2[S]' +
bgneal@183 7060 'iframe[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7061 'h3[S]' +
bgneal@183 7062 'tt[S]' +
bgneal@183 7063 'i[S]' +
bgneal@183 7064 'b[S]' +
bgneal@183 7065 'u[S]' +
bgneal@183 7066 's[S]' +
bgneal@183 7067 'strike[S]' +
bgneal@183 7068 'big[S]' +
bgneal@183 7069 'small[S]' +
bgneal@183 7070 'font[S]' +
bgneal@183 7071 'basefont[]' +
bgneal@183 7072 'em[S]' +
bgneal@183 7073 'strong[S]' +
bgneal@183 7074 'dfn[S]' +
bgneal@183 7075 'code[S]' +
bgneal@183 7076 'q[S]' +
bgneal@183 7077 'samp[S]' +
bgneal@183 7078 'kbd[S]' +
bgneal@183 7079 'var[S]' +
bgneal@183 7080 'cite[S]' +
bgneal@183 7081 'abbr[S]' +
bgneal@183 7082 'acronym[S]' +
bgneal@183 7083 'sub[S]' +
bgneal@183 7084 'sup[S]' +
bgneal@183 7085 'input[]' +
bgneal@183 7086 'select[optgroup|option]' +
bgneal@183 7087 'optgroup[option]' +
bgneal@183 7088 'option[]' +
bgneal@183 7089 'textarea[]' +
bgneal@183 7090 'label[S]' +
bgneal@183 7091 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
bgneal@183 7092 'h4[S]' +
bgneal@183 7093 'ins[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7094 'h5[S]' +
bgneal@183 7095 'del[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7096 'h6[S]' +
bgneal@183 7097 'div[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7098 'ul[li]' +
bgneal@183 7099 'li[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7100 'ol[li]' +
bgneal@183 7101 'dl[dt|dd]' +
bgneal@183 7102 'dt[S]' +
bgneal@183 7103 'dd[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7104 'menu[li]' +
bgneal@183 7105 'dir[li]' +
bgneal@183 7106 'pre[ZA]' +
bgneal@183 7107 'hr[]' +
bgneal@183 7108 'blockquote[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7109 'address[S|p]' +
bgneal@183 7110 'center[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7111 'noframes[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7112 'isindex[]' +
bgneal@183 7113 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
bgneal@183 7114 'legend[S]' +
bgneal@183 7115 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
bgneal@183 7116 'caption[S]' +
bgneal@183 7117 'col[]' +
bgneal@183 7118 'colgroup[col]' +
bgneal@183 7119 'thead[tr]' +
bgneal@183 7120 'tr[th|td]' +
bgneal@183 7121 'th[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7122 'form[#|X|a|H|K|N|O|Q]' +
bgneal@183 7123 'noscript[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7124 'td[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7125 'tfoot[tr]' +
bgneal@183 7126 'tbody[tr]' +
bgneal@183 7127 'area[]' +
bgneal@183 7128 'base[]' +
bgneal@183 7129 'body[#|X|form|a|H|K|N|O|Q]'
bgneal@183 7130 );
bgneal@183 7131
bgneal@183 7132 tinymce.dom.Schema = function() {
bgneal@183 7133 var t = this, elements = transitional;
bgneal@183 7134
bgneal@183 7135 t.isValid = function(name, child_name) {
bgneal@183 7136 var element = elements[name];
bgneal@183 7137
bgneal@183 7138 return !!(element && (!child_name || element[child_name]));
bgneal@183 7139 };
bgneal@183 7140 };
bgneal@183 7141 })();
bgneal@183 7142 (function(tinymce) {
bgneal@183 7143 tinymce.dom.RangeUtils = function(dom) {
bgneal@183 7144 var INVISIBLE_CHAR = '\uFEFF';
bgneal@183 7145
bgneal@183 7146 this.walk = function(rng, callback) {
bgneal@183 7147 var startContainer = rng.startContainer,
bgneal@183 7148 startOffset = rng.startOffset,
bgneal@183 7149 endContainer = rng.endContainer,
bgneal@183 7150 endOffset = rng.endOffset,
bgneal@183 7151 ancestor, startPoint,
bgneal@183 7152 endPoint, node, parent, siblings, nodes;
bgneal@183 7153
bgneal@183 7154 // Handle table cell selection the table plugin enables
bgneal@183 7155 // you to fake select table cells and perform formatting actions on them
bgneal@183 7156 nodes = dom.select('td.mceSelected,th.mceSelected');
bgneal@183 7157 if (nodes.length > 0) {
bgneal@183 7158 tinymce.each(nodes, function(node) {
bgneal@183 7159 callback([node]);
bgneal@183 7160 });
bgneal@183 7161
bgneal@183 7162 return;
bgneal@183 7163 }
bgneal@183 7164
bgneal@183 7165 function collectSiblings(node, name, end_node) {
bgneal@183 7166 var siblings = [];
bgneal@183 7167
bgneal@183 7168 for (; node && node != end_node; node = node[name])
bgneal@183 7169 siblings.push(node);
bgneal@183 7170
bgneal@183 7171 return siblings;
bgneal@183 7172 };
bgneal@183 7173
bgneal@183 7174 function findEndPoint(node, root) {
bgneal@183 7175 do {
bgneal@183 7176 if (node.parentNode == root)
bgneal@183 7177 return node;
bgneal@183 7178
bgneal@183 7179 node = node.parentNode;
bgneal@183 7180 } while(node);
bgneal@183 7181 };
bgneal@183 7182
bgneal@183 7183 function walkBoundary(start_node, end_node, next) {
bgneal@183 7184 var siblingName = next ? 'nextSibling' : 'previousSibling';
bgneal@183 7185
bgneal@183 7186 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
bgneal@183 7187 parent = node.parentNode;
bgneal@183 7188 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
bgneal@183 7189
bgneal@183 7190 if (siblings.length) {
bgneal@183 7191 if (!next)
bgneal@183 7192 siblings.reverse();
bgneal@183 7193
bgneal@183 7194 callback(siblings);
bgneal@183 7195 }
bgneal@183 7196 }
bgneal@183 7197 };
bgneal@183 7198
bgneal@183 7199 // If index based start position then resolve it
bgneal@183 7200 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
bgneal@183 7201 startContainer = startContainer.childNodes[startOffset];
bgneal@183 7202
bgneal@183 7203 // If index based end position then resolve it
bgneal@183 7204 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
bgneal@183 7205 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
bgneal@183 7206
bgneal@183 7207 // Find common ancestor and end points
bgneal@183 7208 ancestor = dom.findCommonAncestor(startContainer, endContainer);
bgneal@183 7209
bgneal@183 7210 // Same container
bgneal@183 7211 if (startContainer == endContainer)
bgneal@183 7212 return callback([startContainer]);
bgneal@183 7213
bgneal@183 7214 // Process left side
bgneal@183 7215 for (node = startContainer; node; node = node.parentNode) {
bgneal@183 7216 if (node == endContainer)
bgneal@183 7217 return walkBoundary(startContainer, ancestor, true);
bgneal@183 7218
bgneal@183 7219 if (node == ancestor)
bgneal@183 7220 break;
bgneal@183 7221 }
bgneal@183 7222
bgneal@183 7223 // Process right side
bgneal@183 7224 for (node = endContainer; node; node = node.parentNode) {
bgneal@183 7225 if (node == startContainer)
bgneal@183 7226 return walkBoundary(endContainer, ancestor);
bgneal@183 7227
bgneal@183 7228 if (node == ancestor)
bgneal@183 7229 break;
bgneal@183 7230 }
bgneal@183 7231
bgneal@183 7232 // Find start/end point
bgneal@183 7233 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
bgneal@183 7234 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
bgneal@183 7235
bgneal@183 7236 // Walk left leaf
bgneal@183 7237 walkBoundary(startContainer, startPoint, true);
bgneal@183 7238
bgneal@183 7239 // Walk the middle from start to end point
bgneal@183 7240 siblings = collectSiblings(
bgneal@183 7241 startPoint == startContainer ? startPoint : startPoint.nextSibling,
bgneal@183 7242 'nextSibling',
bgneal@183 7243 endPoint == endContainer ? endPoint.nextSibling : endPoint
bgneal@183 7244 );
bgneal@183 7245
bgneal@183 7246 if (siblings.length)
bgneal@183 7247 callback(siblings);
bgneal@183 7248
bgneal@183 7249 // Walk right leaf
bgneal@183 7250 walkBoundary(endContainer, endPoint);
bgneal@183 7251 };
bgneal@183 7252
bgneal@183 7253 /* this.split = function(rng) {
bgneal@183 7254 var startContainer = rng.startContainer,
bgneal@183 7255 startOffset = rng.startOffset,
bgneal@183 7256 endContainer = rng.endContainer,
bgneal@183 7257 endOffset = rng.endOffset;
bgneal@183 7258
bgneal@183 7259 function splitText(node, offset) {
bgneal@183 7260 if (offset == node.nodeValue.length)
bgneal@183 7261 node.appendData(INVISIBLE_CHAR);
bgneal@183 7262
bgneal@183 7263 node = node.splitText(offset);
bgneal@183 7264
bgneal@183 7265 if (node.nodeValue === INVISIBLE_CHAR)
bgneal@183 7266 node.nodeValue = '';
bgneal@183 7267
bgneal@183 7268 return node;
bgneal@183 7269 };
bgneal@183 7270
bgneal@183 7271 // Handle single text node
bgneal@183 7272 if (startContainer == endContainer) {
bgneal@183 7273 if (startContainer.nodeType == 3) {
bgneal@183 7274 if (startOffset != 0)
bgneal@183 7275 startContainer = endContainer = splitText(startContainer, startOffset);
bgneal@183 7276
bgneal@183 7277 if (endOffset - startOffset != startContainer.nodeValue.length)
bgneal@183 7278 splitText(startContainer, endOffset - startOffset);
bgneal@183 7279 }
bgneal@183 7280 } else {
bgneal@183 7281 // Split startContainer text node if needed
bgneal@183 7282 if (startContainer.nodeType == 3 && startOffset != 0) {
bgneal@183 7283 startContainer = splitText(startContainer, startOffset);
bgneal@183 7284 startOffset = 0;
bgneal@183 7285 }
bgneal@183 7286
bgneal@183 7287 // Split endContainer text node if needed
bgneal@183 7288 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
bgneal@183 7289 endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@183 7290 endOffset = endContainer.nodeValue.length;
bgneal@183 7291 }
bgneal@183 7292 }
bgneal@183 7293
bgneal@183 7294 return {
bgneal@183 7295 startContainer : startContainer,
bgneal@183 7296 startOffset : startOffset,
bgneal@183 7297 endContainer : endContainer,
bgneal@183 7298 endOffset : endOffset
bgneal@183 7299 };
bgneal@183 7300 };
bgneal@183 7301 */
bgneal@183 7302 };
bgneal@217 7303
bgneal@217 7304 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
bgneal@217 7305 if (rng1 && rng2) {
bgneal@217 7306 // Compare native IE ranges
bgneal@217 7307 if (rng1.item || rng1.duplicate) {
bgneal@217 7308 // Both are control ranges and the selected element matches
bgneal@217 7309 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
bgneal@217 7310 return true;
bgneal@217 7311
bgneal@217 7312 // Both are text ranges and the range matches
bgneal@217 7313 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
bgneal@217 7314 return true;
bgneal@217 7315 } else {
bgneal@217 7316 // Compare w3c ranges
bgneal@217 7317 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
bgneal@217 7318 }
bgneal@217 7319 }
bgneal@217 7320
bgneal@217 7321 return false;
bgneal@217 7322 };
bgneal@183 7323 })(tinymce);
bgneal@183 7324
bgneal@45 7325 (function(tinymce) {
bgneal@45 7326 // Shorten class names
bgneal@45 7327 var DOM = tinymce.DOM, is = tinymce.is;
bgneal@45 7328
bgneal@45 7329 tinymce.create('tinymce.ui.Control', {
bgneal@45 7330 Control : function(id, s) {
bgneal@45 7331 this.id = id;
bgneal@45 7332 this.settings = s = s || {};
bgneal@45 7333 this.rendered = false;
bgneal@45 7334 this.onRender = new tinymce.util.Dispatcher(this);
bgneal@45 7335 this.classPrefix = '';
bgneal@45 7336 this.scope = s.scope || this;
bgneal@45 7337 this.disabled = 0;
bgneal@45 7338 this.active = 0;
bgneal@45 7339 },
bgneal@45 7340
bgneal@45 7341 setDisabled : function(s) {
bgneal@45 7342 var e;
bgneal@45 7343
bgneal@45 7344 if (s != this.disabled) {
bgneal@45 7345 e = DOM.get(this.id);
bgneal@45 7346
bgneal@45 7347 // Add accessibility title for unavailable actions
bgneal@45 7348 if (e && this.settings.unavailable_prefix) {
bgneal@45 7349 if (s) {
bgneal@45 7350 this.prevTitle = e.title;
bgneal@45 7351 e.title = this.settings.unavailable_prefix + ": " + e.title;
bgneal@45 7352 } else
bgneal@45 7353 e.title = this.prevTitle;
bgneal@45 7354 }
bgneal@45 7355
bgneal@45 7356 this.setState('Disabled', s);
bgneal@45 7357 this.setState('Enabled', !s);
bgneal@45 7358 this.disabled = s;
bgneal@45 7359 }
bgneal@45 7360 },
bgneal@45 7361
bgneal@45 7362 isDisabled : function() {
bgneal@45 7363 return this.disabled;
bgneal@45 7364 },
bgneal@45 7365
bgneal@45 7366 setActive : function(s) {
bgneal@45 7367 if (s != this.active) {
bgneal@45 7368 this.setState('Active', s);
bgneal@45 7369 this.active = s;
bgneal@45 7370 }
bgneal@45 7371 },
bgneal@45 7372
bgneal@45 7373 isActive : function() {
bgneal@45 7374 return this.active;
bgneal@45 7375 },
bgneal@45 7376
bgneal@45 7377 setState : function(c, s) {
bgneal@45 7378 var n = DOM.get(this.id);
bgneal@45 7379
bgneal@45 7380 c = this.classPrefix + c;
bgneal@45 7381
bgneal@45 7382 if (s)
bgneal@45 7383 DOM.addClass(n, c);
bgneal@45 7384 else
bgneal@45 7385 DOM.removeClass(n, c);
bgneal@45 7386 },
bgneal@45 7387
bgneal@45 7388 isRendered : function() {
bgneal@45 7389 return this.rendered;
bgneal@45 7390 },
bgneal@45 7391
bgneal@45 7392 renderHTML : function() {
bgneal@45 7393 },
bgneal@45 7394
bgneal@45 7395 renderTo : function(n) {
bgneal@45 7396 DOM.setHTML(n, this.renderHTML());
bgneal@45 7397 },
bgneal@45 7398
bgneal@45 7399 postRender : function() {
bgneal@45 7400 var t = this, b;
bgneal@45 7401
bgneal@45 7402 // Set pending states
bgneal@45 7403 if (is(t.disabled)) {
bgneal@45 7404 b = t.disabled;
bgneal@45 7405 t.disabled = -1;
bgneal@45 7406 t.setDisabled(b);
bgneal@45 7407 }
bgneal@45 7408
bgneal@45 7409 if (is(t.active)) {
bgneal@45 7410 b = t.active;
bgneal@45 7411 t.active = -1;
bgneal@45 7412 t.setActive(b);
bgneal@45 7413 }
bgneal@45 7414 },
bgneal@45 7415
bgneal@45 7416 remove : function() {
bgneal@45 7417 DOM.remove(this.id);
bgneal@45 7418 this.destroy();
bgneal@45 7419 },
bgneal@45 7420
bgneal@45 7421 destroy : function() {
bgneal@45 7422 tinymce.dom.Event.clear(this.id);
bgneal@45 7423 }
bgneal@183 7424 });
bgneal@183 7425 })(tinymce);
bgneal@183 7426 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
bgneal@45 7427 Container : function(id, s) {
bgneal@45 7428 this.parent(id, s);
bgneal@183 7429
bgneal@45 7430 this.controls = [];
bgneal@183 7431
bgneal@45 7432 this.lookup = {};
bgneal@45 7433 },
bgneal@45 7434
bgneal@45 7435 add : function(c) {
bgneal@45 7436 this.lookup[c.id] = c;
bgneal@45 7437 this.controls.push(c);
bgneal@45 7438
bgneal@45 7439 return c;
bgneal@45 7440 },
bgneal@45 7441
bgneal@45 7442 get : function(n) {
bgneal@45 7443 return this.lookup[n];
bgneal@45 7444 }
bgneal@183 7445 });
bgneal@183 7446
bgneal@45 7447
bgneal@45 7448 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
bgneal@45 7449 Separator : function(id, s) {
bgneal@45 7450 this.parent(id, s);
bgneal@45 7451 this.classPrefix = 'mceSeparator';
bgneal@45 7452 },
bgneal@45 7453
bgneal@45 7454 renderHTML : function() {
bgneal@45 7455 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
bgneal@45 7456 }
bgneal@183 7457 });
bgneal@183 7458
bgneal@45 7459 (function(tinymce) {
bgneal@45 7460 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 7461
bgneal@45 7462 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
bgneal@45 7463 MenuItem : function(id, s) {
bgneal@45 7464 this.parent(id, s);
bgneal@45 7465 this.classPrefix = 'mceMenuItem';
bgneal@45 7466 },
bgneal@45 7467
bgneal@45 7468 setSelected : function(s) {
bgneal@45 7469 this.setState('Selected', s);
bgneal@45 7470 this.selected = s;
bgneal@45 7471 },
bgneal@45 7472
bgneal@45 7473 isSelected : function() {
bgneal@45 7474 return this.selected;
bgneal@45 7475 },
bgneal@45 7476
bgneal@45 7477 postRender : function() {
bgneal@45 7478 var t = this;
bgneal@45 7479
bgneal@45 7480 t.parent();
bgneal@45 7481
bgneal@45 7482 // Set pending state
bgneal@45 7483 if (is(t.selected))
bgneal@45 7484 t.setSelected(t.selected);
bgneal@45 7485 }
bgneal@183 7486 });
bgneal@45 7487 })(tinymce);
bgneal@183 7488
bgneal@45 7489 (function(tinymce) {
bgneal@45 7490 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 7491
bgneal@45 7492 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
bgneal@45 7493 Menu : function(id, s) {
bgneal@45 7494 var t = this;
bgneal@45 7495
bgneal@45 7496 t.parent(id, s);
bgneal@45 7497 t.items = {};
bgneal@45 7498 t.collapsed = false;
bgneal@45 7499 t.menuCount = 0;
bgneal@45 7500 t.onAddItem = new tinymce.util.Dispatcher(this);
bgneal@45 7501 },
bgneal@45 7502
bgneal@45 7503 expand : function(d) {
bgneal@45 7504 var t = this;
bgneal@45 7505
bgneal@45 7506 if (d) {
bgneal@45 7507 walk(t, function(o) {
bgneal@45 7508 if (o.expand)
bgneal@45 7509 o.expand();
bgneal@45 7510 }, 'items', t);
bgneal@45 7511 }
bgneal@45 7512
bgneal@45 7513 t.collapsed = false;
bgneal@45 7514 },
bgneal@45 7515
bgneal@45 7516 collapse : function(d) {
bgneal@45 7517 var t = this;
bgneal@45 7518
bgneal@45 7519 if (d) {
bgneal@45 7520 walk(t, function(o) {
bgneal@45 7521 if (o.collapse)
bgneal@45 7522 o.collapse();
bgneal@45 7523 }, 'items', t);
bgneal@45 7524 }
bgneal@45 7525
bgneal@45 7526 t.collapsed = true;
bgneal@45 7527 },
bgneal@45 7528
bgneal@45 7529 isCollapsed : function() {
bgneal@45 7530 return this.collapsed;
bgneal@45 7531 },
bgneal@45 7532
bgneal@45 7533 add : function(o) {
bgneal@45 7534 if (!o.settings)
bgneal@45 7535 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
bgneal@45 7536
bgneal@45 7537 this.onAddItem.dispatch(this, o);
bgneal@45 7538
bgneal@45 7539 return this.items[o.id] = o;
bgneal@45 7540 },
bgneal@45 7541
bgneal@45 7542 addSeparator : function() {
bgneal@45 7543 return this.add({separator : true});
bgneal@45 7544 },
bgneal@45 7545
bgneal@45 7546 addMenu : function(o) {
bgneal@45 7547 if (!o.collapse)
bgneal@45 7548 o = this.createMenu(o);
bgneal@45 7549
bgneal@45 7550 this.menuCount++;
bgneal@45 7551
bgneal@45 7552 return this.add(o);
bgneal@45 7553 },
bgneal@45 7554
bgneal@45 7555 hasMenus : function() {
bgneal@45 7556 return this.menuCount !== 0;
bgneal@45 7557 },
bgneal@45 7558
bgneal@45 7559 remove : function(o) {
bgneal@45 7560 delete this.items[o.id];
bgneal@45 7561 },
bgneal@45 7562
bgneal@45 7563 removeAll : function() {
bgneal@45 7564 var t = this;
bgneal@45 7565
bgneal@45 7566 walk(t, function(o) {
bgneal@45 7567 if (o.removeAll)
bgneal@45 7568 o.removeAll();
bgneal@45 7569 else
bgneal@45 7570 o.remove();
bgneal@45 7571
bgneal@45 7572 o.destroy();
bgneal@45 7573 }, 'items', t);
bgneal@45 7574
bgneal@45 7575 t.items = {};
bgneal@45 7576 },
bgneal@45 7577
bgneal@45 7578 createMenu : function(o) {
bgneal@45 7579 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
bgneal@45 7580
bgneal@45 7581 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
bgneal@45 7582
bgneal@45 7583 return m;
bgneal@45 7584 }
bgneal@183 7585 });
bgneal@183 7586 })(tinymce);
bgneal@183 7587 (function(tinymce) {
bgneal@45 7588 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
bgneal@45 7589
bgneal@45 7590 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
bgneal@45 7591 DropMenu : function(id, s) {
bgneal@45 7592 s = s || {};
bgneal@45 7593 s.container = s.container || DOM.doc.body;
bgneal@45 7594 s.offset_x = s.offset_x || 0;
bgneal@45 7595 s.offset_y = s.offset_y || 0;
bgneal@45 7596 s.vp_offset_x = s.vp_offset_x || 0;
bgneal@45 7597 s.vp_offset_y = s.vp_offset_y || 0;
bgneal@45 7598
bgneal@45 7599 if (is(s.icons) && !s.icons)
bgneal@45 7600 s['class'] += ' mceNoIcons';
bgneal@45 7601
bgneal@45 7602 this.parent(id, s);
bgneal@45 7603 this.onShowMenu = new tinymce.util.Dispatcher(this);
bgneal@45 7604 this.onHideMenu = new tinymce.util.Dispatcher(this);
bgneal@45 7605 this.classPrefix = 'mceMenu';
bgneal@45 7606 },
bgneal@45 7607
bgneal@45 7608 createMenu : function(s) {
bgneal@45 7609 var t = this, cs = t.settings, m;
bgneal@45 7610
bgneal@45 7611 s.container = s.container || cs.container;
bgneal@45 7612 s.parent = t;
bgneal@45 7613 s.constrain = s.constrain || cs.constrain;
bgneal@45 7614 s['class'] = s['class'] || cs['class'];
bgneal@45 7615 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
bgneal@45 7616 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
bgneal@45 7617 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
bgneal@45 7618
bgneal@45 7619 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
bgneal@45 7620
bgneal@45 7621 return m;
bgneal@45 7622 },
bgneal@45 7623
bgneal@45 7624 update : function() {
bgneal@45 7625 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
bgneal@45 7626
bgneal@45 7627 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
bgneal@45 7628 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
bgneal@45 7629
bgneal@45 7630 if (!DOM.boxModel)
bgneal@45 7631 t.element.setStyles({width : tw + 2, height : th + 2});
bgneal@45 7632 else
bgneal@45 7633 t.element.setStyles({width : tw, height : th});
bgneal@45 7634
bgneal@45 7635 if (s.max_width)
bgneal@45 7636 DOM.setStyle(co, 'width', tw);
bgneal@45 7637
bgneal@45 7638 if (s.max_height) {
bgneal@45 7639 DOM.setStyle(co, 'height', th);
bgneal@45 7640
bgneal@45 7641 if (tb.clientHeight < s.max_height)
bgneal@45 7642 DOM.setStyle(co, 'overflow', 'hidden');
bgneal@45 7643 }
bgneal@45 7644 },
bgneal@45 7645
bgneal@45 7646 showMenu : function(x, y, px) {
bgneal@45 7647 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
bgneal@45 7648
bgneal@45 7649 t.collapse(1);
bgneal@45 7650
bgneal@45 7651 if (t.isMenuVisible)
bgneal@45 7652 return;
bgneal@45 7653
bgneal@45 7654 if (!t.rendered) {
bgneal@45 7655 co = DOM.add(t.settings.container, t.renderNode());
bgneal@45 7656
bgneal@45 7657 each(t.items, function(o) {
bgneal@45 7658 o.postRender();
bgneal@45 7659 });
bgneal@45 7660
bgneal@45 7661 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 7662 } else
bgneal@45 7663 co = DOM.get('menu_' + t.id);
bgneal@45 7664
bgneal@45 7665 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
bgneal@45 7666 if (!tinymce.isOpera)
bgneal@45 7667 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
bgneal@45 7668
bgneal@45 7669 DOM.show(co);
bgneal@45 7670 t.update();
bgneal@45 7671
bgneal@45 7672 x += s.offset_x || 0;
bgneal@45 7673 y += s.offset_y || 0;
bgneal@45 7674 vp.w -= 4;
bgneal@45 7675 vp.h -= 4;
bgneal@45 7676
bgneal@45 7677 // Move inside viewport if not submenu
bgneal@45 7678 if (s.constrain) {
bgneal@45 7679 w = co.clientWidth - ot;
bgneal@45 7680 h = co.clientHeight - ot;
bgneal@45 7681 mx = vp.x + vp.w;
bgneal@45 7682 my = vp.y + vp.h;
bgneal@45 7683
bgneal@45 7684 if ((x + s.vp_offset_x + w) > mx)
bgneal@45 7685 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
bgneal@45 7686
bgneal@45 7687 if ((y + s.vp_offset_y + h) > my)
bgneal@45 7688 y = Math.max(0, (my - s.vp_offset_y) - h);
bgneal@45 7689 }
bgneal@45 7690
bgneal@45 7691 DOM.setStyles(co, {left : x , top : y});
bgneal@45 7692 t.element.update();
bgneal@45 7693
bgneal@45 7694 t.isMenuVisible = 1;
bgneal@45 7695 t.mouseClickFunc = Event.add(co, 'click', function(e) {
bgneal@45 7696 var m;
bgneal@45 7697
bgneal@45 7698 e = e.target;
bgneal@45 7699
bgneal@183 7700 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 7701 m = t.items[e.id];
bgneal@45 7702
bgneal@45 7703 if (m.isDisabled())
bgneal@45 7704 return;
bgneal@45 7705
bgneal@45 7706 dm = t;
bgneal@45 7707
bgneal@45 7708 while (dm) {
bgneal@45 7709 if (dm.hideMenu)
bgneal@45 7710 dm.hideMenu();
bgneal@45 7711
bgneal@45 7712 dm = dm.settings.parent;
bgneal@45 7713 }
bgneal@45 7714
bgneal@45 7715 if (m.settings.onclick)
bgneal@45 7716 m.settings.onclick(e);
bgneal@45 7717
bgneal@45 7718 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@45 7719 }
bgneal@45 7720 });
bgneal@45 7721
bgneal@45 7722 if (t.hasMenus()) {
bgneal@45 7723 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
bgneal@45 7724 var m, r, mi;
bgneal@45 7725
bgneal@45 7726 e = e.target;
bgneal@183 7727 if (e && (e = DOM.getParent(e, 'tr'))) {
bgneal@45 7728 m = t.items[e.id];
bgneal@45 7729
bgneal@45 7730 if (t.lastMenu)
bgneal@45 7731 t.lastMenu.collapse(1);
bgneal@45 7732
bgneal@45 7733 if (m.isDisabled())
bgneal@45 7734 return;
bgneal@45 7735
bgneal@45 7736 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 7737 //p = DOM.getPos(s.container);
bgneal@45 7738 r = DOM.getRect(e);
bgneal@45 7739 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
bgneal@45 7740 t.lastMenu = m;
bgneal@45 7741 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
bgneal@45 7742 }
bgneal@45 7743 }
bgneal@45 7744 });
bgneal@45 7745 }
bgneal@45 7746
bgneal@45 7747 t.onShowMenu.dispatch(t);
bgneal@45 7748
bgneal@45 7749 if (s.keyboard_focus) {
bgneal@45 7750 Event.add(co, 'keydown', t._keyHandler, t);
bgneal@45 7751 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
bgneal@45 7752 t._focusIdx = 0;
bgneal@45 7753 }
bgneal@45 7754 },
bgneal@45 7755
bgneal@45 7756 hideMenu : function(c) {
bgneal@45 7757 var t = this, co = DOM.get('menu_' + t.id), e;
bgneal@45 7758
bgneal@45 7759 if (!t.isMenuVisible)
bgneal@45 7760 return;
bgneal@45 7761
bgneal@45 7762 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 7763 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 7764 Event.remove(co, 'keydown', t._keyHandler);
bgneal@45 7765 DOM.hide(co);
bgneal@45 7766 t.isMenuVisible = 0;
bgneal@45 7767
bgneal@45 7768 if (!c)
bgneal@45 7769 t.collapse(1);
bgneal@45 7770
bgneal@45 7771 if (t.element)
bgneal@45 7772 t.element.hide();
bgneal@45 7773
bgneal@45 7774 if (e = DOM.get(t.id))
bgneal@45 7775 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
bgneal@45 7776
bgneal@45 7777 t.onHideMenu.dispatch(t);
bgneal@45 7778 },
bgneal@45 7779
bgneal@45 7780 add : function(o) {
bgneal@45 7781 var t = this, co;
bgneal@45 7782
bgneal@45 7783 o = t.parent(o);
bgneal@45 7784
bgneal@45 7785 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
bgneal@45 7786 t._add(DOM.select('tbody', co)[0], o);
bgneal@45 7787
bgneal@45 7788 return o;
bgneal@45 7789 },
bgneal@45 7790
bgneal@45 7791 collapse : function(d) {
bgneal@45 7792 this.parent(d);
bgneal@45 7793 this.hideMenu(1);
bgneal@45 7794 },
bgneal@45 7795
bgneal@45 7796 remove : function(o) {
bgneal@45 7797 DOM.remove(o.id);
bgneal@45 7798 this.destroy();
bgneal@45 7799
bgneal@45 7800 return this.parent(o);
bgneal@45 7801 },
bgneal@45 7802
bgneal@45 7803 destroy : function() {
bgneal@45 7804 var t = this, co = DOM.get('menu_' + t.id);
bgneal@45 7805
bgneal@45 7806 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 7807 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 7808
bgneal@45 7809 if (t.element)
bgneal@45 7810 t.element.remove();
bgneal@45 7811
bgneal@45 7812 DOM.remove(co);
bgneal@45 7813 },
bgneal@45 7814
bgneal@45 7815 renderNode : function() {
bgneal@45 7816 var t = this, s = t.settings, n, tb, co, w;
bgneal@45 7817
bgneal@45 7818 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
bgneal@45 7819 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
bgneal@45 7820 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 7821
bgneal@45 7822 if (s.menu_line)
bgneal@45 7823 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
bgneal@45 7824
bgneal@45 7825 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
bgneal@45 7826 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
bgneal@45 7827 tb = DOM.add(n, 'tbody');
bgneal@45 7828
bgneal@45 7829 each(t.items, function(o) {
bgneal@45 7830 t._add(tb, o);
bgneal@45 7831 });
bgneal@45 7832
bgneal@45 7833 t.rendered = true;
bgneal@45 7834
bgneal@45 7835 return w;
bgneal@45 7836 },
bgneal@45 7837
bgneal@45 7838 // Internal functions
bgneal@45 7839
bgneal@45 7840 _keyHandler : function(e) {
bgneal@45 7841 var t = this, kc = e.keyCode;
bgneal@45 7842
bgneal@45 7843 function focus(d) {
bgneal@45 7844 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
bgneal@45 7845
bgneal@45 7846 if (e) {
bgneal@45 7847 t._focusIdx = i;
bgneal@45 7848 e.focus();
bgneal@45 7849 }
bgneal@45 7850 };
bgneal@45 7851
bgneal@45 7852 switch (kc) {
bgneal@45 7853 case 38:
bgneal@45 7854 focus(-1); // Select first link
bgneal@45 7855 return;
bgneal@45 7856
bgneal@45 7857 case 40:
bgneal@45 7858 focus(1);
bgneal@45 7859 return;
bgneal@45 7860
bgneal@45 7861 case 13:
bgneal@45 7862 return;
bgneal@45 7863
bgneal@45 7864 case 27:
bgneal@45 7865 return this.hideMenu();
bgneal@45 7866 }
bgneal@45 7867 },
bgneal@45 7868
bgneal@45 7869 _add : function(tb, o) {
bgneal@45 7870 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
bgneal@45 7871
bgneal@45 7872 if (s.separator) {
bgneal@45 7873 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
bgneal@45 7874 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
bgneal@45 7875
bgneal@45 7876 if (n = ro.previousSibling)
bgneal@45 7877 DOM.addClass(n, 'mceLast');
bgneal@45 7878
bgneal@45 7879 return;
bgneal@45 7880 }
bgneal@45 7881
bgneal@45 7882 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
bgneal@45 7883 n = it = DOM.add(n, 'td');
bgneal@45 7884 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
bgneal@45 7885
bgneal@45 7886 DOM.addClass(it, s['class']);
bgneal@45 7887 // n = DOM.add(n, 'span', {'class' : 'item'});
bgneal@45 7888
bgneal@45 7889 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
bgneal@45 7890
bgneal@45 7891 if (s.icon_src)
bgneal@45 7892 DOM.add(ic, 'img', {src : s.icon_src});
bgneal@45 7893
bgneal@45 7894 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
bgneal@45 7895
bgneal@45 7896 if (o.settings.style)
bgneal@45 7897 DOM.setAttrib(n, 'style', o.settings.style);
bgneal@45 7898
bgneal@45 7899 if (tb.childNodes.length == 1)
bgneal@45 7900 DOM.addClass(ro, 'mceFirst');
bgneal@45 7901
bgneal@45 7902 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
bgneal@45 7903 DOM.addClass(ro, 'mceFirst');
bgneal@45 7904
bgneal@45 7905 if (o.collapse)
bgneal@45 7906 DOM.addClass(ro, cp + 'ItemSub');
bgneal@45 7907
bgneal@45 7908 if (n = ro.previousSibling)
bgneal@45 7909 DOM.removeClass(n, 'mceLast');
bgneal@45 7910
bgneal@45 7911 DOM.addClass(ro, 'mceLast');
bgneal@45 7912 }
bgneal@183 7913 });
bgneal@183 7914 })(tinymce);
bgneal@183 7915 (function(tinymce) {
bgneal@45 7916 var DOM = tinymce.DOM;
bgneal@45 7917
bgneal@45 7918 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
bgneal@45 7919 Button : function(id, s) {
bgneal@45 7920 this.parent(id, s);
bgneal@45 7921 this.classPrefix = 'mceButton';
bgneal@45 7922 },
bgneal@45 7923
bgneal@45 7924 renderHTML : function() {
bgneal@45 7925 var cp = this.classPrefix, s = this.settings, h, l;
bgneal@45 7926
bgneal@45 7927 l = DOM.encode(s.label || '');
bgneal@45 7928 h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
bgneal@45 7929
bgneal@45 7930 if (s.image)
bgneal@45 7931 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
bgneal@45 7932 else
bgneal@45 7933 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
bgneal@45 7934
bgneal@45 7935 return h;
bgneal@45 7936 },
bgneal@45 7937
bgneal@45 7938 postRender : function() {
bgneal@45 7939 var t = this, s = t.settings;
bgneal@45 7940
bgneal@45 7941 tinymce.dom.Event.add(t.id, 'click', function(e) {
bgneal@45 7942 if (!t.isDisabled())
bgneal@45 7943 return s.onclick.call(s.scope, e);
bgneal@45 7944 });
bgneal@45 7945 }
bgneal@183 7946 });
bgneal@45 7947 })(tinymce);
bgneal@183 7948
bgneal@45 7949 (function(tinymce) {
bgneal@45 7950 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 7951
bgneal@45 7952 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
bgneal@45 7953 ListBox : function(id, s) {
bgneal@45 7954 var t = this;
bgneal@45 7955
bgneal@45 7956 t.parent(id, s);
bgneal@183 7957
bgneal@45 7958 t.items = [];
bgneal@183 7959
bgneal@45 7960 t.onChange = new Dispatcher(t);
bgneal@183 7961
bgneal@45 7962 t.onPostRender = new Dispatcher(t);
bgneal@183 7963
bgneal@45 7964 t.onAdd = new Dispatcher(t);
bgneal@183 7965
bgneal@45 7966 t.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@183 7967
bgneal@45 7968 t.classPrefix = 'mceListBox';
bgneal@45 7969 },
bgneal@45 7970
bgneal@45 7971 select : function(va) {
bgneal@45 7972 var t = this, fv, f;
bgneal@45 7973
bgneal@45 7974 if (va == undefined)
bgneal@45 7975 return t.selectByIndex(-1);
bgneal@45 7976
bgneal@45 7977 // Is string or number make function selector
bgneal@45 7978 if (va && va.call)
bgneal@45 7979 f = va;
bgneal@45 7980 else {
bgneal@45 7981 f = function(v) {
bgneal@45 7982 return v == va;
bgneal@45 7983 };
bgneal@45 7984 }
bgneal@45 7985
bgneal@45 7986 // Do we need to do something?
bgneal@45 7987 if (va != t.selectedValue) {
bgneal@45 7988 // Find item
bgneal@45 7989 each(t.items, function(o, i) {
bgneal@45 7990 if (f(o.value)) {
bgneal@45 7991 fv = 1;
bgneal@45 7992 t.selectByIndex(i);
bgneal@45 7993 return false;
bgneal@45 7994 }
bgneal@45 7995 });
bgneal@45 7996
bgneal@45 7997 if (!fv)
bgneal@45 7998 t.selectByIndex(-1);
bgneal@45 7999 }
bgneal@45 8000 },
bgneal@45 8001
bgneal@45 8002 selectByIndex : function(idx) {
bgneal@45 8003 var t = this, e, o;
bgneal@45 8004
bgneal@45 8005 if (idx != t.selectedIndex) {
bgneal@45 8006 e = DOM.get(t.id + '_text');
bgneal@45 8007 o = t.items[idx];
bgneal@45 8008
bgneal@45 8009 if (o) {
bgneal@45 8010 t.selectedValue = o.value;
bgneal@45 8011 t.selectedIndex = idx;
bgneal@45 8012 DOM.setHTML(e, DOM.encode(o.title));
bgneal@45 8013 DOM.removeClass(e, 'mceTitle');
bgneal@45 8014 } else {
bgneal@45 8015 DOM.setHTML(e, DOM.encode(t.settings.title));
bgneal@45 8016 DOM.addClass(e, 'mceTitle');
bgneal@45 8017 t.selectedValue = t.selectedIndex = null;
bgneal@45 8018 }
bgneal@45 8019
bgneal@45 8020 e = 0;
bgneal@45 8021 }
bgneal@45 8022 },
bgneal@45 8023
bgneal@45 8024 add : function(n, v, o) {
bgneal@45 8025 var t = this;
bgneal@45 8026
bgneal@45 8027 o = o || {};
bgneal@45 8028 o = tinymce.extend(o, {
bgneal@45 8029 title : n,
bgneal@45 8030 value : v
bgneal@45 8031 });
bgneal@45 8032
bgneal@45 8033 t.items.push(o);
bgneal@45 8034 t.onAdd.dispatch(t, o);
bgneal@45 8035 },
bgneal@45 8036
bgneal@45 8037 getLength : function() {
bgneal@45 8038 return this.items.length;
bgneal@45 8039 },
bgneal@45 8040
bgneal@45 8041 renderHTML : function() {
bgneal@45 8042 var h = '', t = this, s = t.settings, cp = t.classPrefix;
bgneal@45 8043
bgneal@45 8044 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
bgneal@45 8045 h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
bgneal@45 8046 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
bgneal@45 8047 h += '</tr></tbody></table>';
bgneal@45 8048
bgneal@45 8049 return h;
bgneal@45 8050 },
bgneal@45 8051
bgneal@45 8052 showMenu : function() {
bgneal@45 8053 var t = this, p1, p2, e = DOM.get(this.id), m;
bgneal@45 8054
bgneal@45 8055 if (t.isDisabled() || t.items.length == 0)
bgneal@45 8056 return;
bgneal@45 8057
bgneal@45 8058 if (t.menu && t.menu.isMenuVisible)
bgneal@45 8059 return t.hideMenu();
bgneal@45 8060
bgneal@45 8061 if (!t.isMenuRendered) {
bgneal@45 8062 t.renderMenu();
bgneal@45 8063 t.isMenuRendered = true;
bgneal@45 8064 }
bgneal@45 8065
bgneal@45 8066 p1 = DOM.getPos(this.settings.menu_container);
bgneal@45 8067 p2 = DOM.getPos(e);
bgneal@45 8068
bgneal@45 8069 m = t.menu;
bgneal@45 8070 m.settings.offset_x = p2.x;
bgneal@45 8071 m.settings.offset_y = p2.y;
bgneal@45 8072 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
bgneal@45 8073
bgneal@45 8074 // Select in menu
bgneal@45 8075 if (t.oldID)
bgneal@45 8076 m.items[t.oldID].setSelected(0);
bgneal@45 8077
bgneal@45 8078 each(t.items, function(o) {
bgneal@45 8079 if (o.value === t.selectedValue) {
bgneal@45 8080 m.items[o.id].setSelected(1);
bgneal@45 8081 t.oldID = o.id;
bgneal@45 8082 }
bgneal@45 8083 });
bgneal@45 8084
bgneal@45 8085 m.showMenu(0, e.clientHeight);
bgneal@45 8086
bgneal@45 8087 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8088 DOM.addClass(t.id, t.classPrefix + 'Selected');
bgneal@45 8089
bgneal@45 8090 //DOM.get(t.id + '_text').focus();
bgneal@45 8091 },
bgneal@45 8092
bgneal@45 8093 hideMenu : function(e) {
bgneal@45 8094 var t = this;
bgneal@45 8095
bgneal@183 8096 if (t.menu && t.menu.isMenuVisible) {
bgneal@183 8097 // Prevent double toogles by canceling the mouse click event to the button
bgneal@183 8098 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
bgneal@183 8099 return;
bgneal@183 8100
bgneal@183 8101 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@183 8102 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@183 8103 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8104 t.menu.hideMenu();
bgneal@183 8105 }
bgneal@45 8106 }
bgneal@45 8107 },
bgneal@45 8108
bgneal@45 8109 renderMenu : function() {
bgneal@45 8110 var t = this, m;
bgneal@45 8111
bgneal@45 8112 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 8113 menu_line : 1,
bgneal@45 8114 'class' : t.classPrefix + 'Menu mceNoIcons',
bgneal@45 8115 max_width : 150,
bgneal@45 8116 max_height : 150
bgneal@45 8117 });
bgneal@45 8118
bgneal@45 8119 m.onHideMenu.add(t.hideMenu, t);
bgneal@45 8120
bgneal@45 8121 m.add({
bgneal@45 8122 title : t.settings.title,
bgneal@45 8123 'class' : 'mceMenuItemTitle',
bgneal@45 8124 onclick : function() {
bgneal@45 8125 if (t.settings.onselect('') !== false)
bgneal@45 8126 t.select(''); // Must be runned after
bgneal@45 8127 }
bgneal@45 8128 });
bgneal@45 8129
bgneal@45 8130 each(t.items, function(o) {
bgneal@183 8131 // No value then treat it as a title
bgneal@183 8132 if (o.value === undefined) {
bgneal@183 8133 m.add({
bgneal@183 8134 title : o.title,
bgneal@183 8135 'class' : 'mceMenuItemTitle',
bgneal@183 8136 onclick : function() {
bgneal@183 8137 if (t.settings.onselect('') !== false)
bgneal@183 8138 t.select(''); // Must be runned after
bgneal@183 8139 }
bgneal@183 8140 });
bgneal@183 8141 } else {
bgneal@183 8142 o.id = DOM.uniqueId();
bgneal@183 8143 o.onclick = function() {
bgneal@183 8144 if (t.settings.onselect(o.value) !== false)
bgneal@183 8145 t.select(o.value); // Must be runned after
bgneal@183 8146 };
bgneal@183 8147
bgneal@183 8148 m.add(o);
bgneal@183 8149 }
bgneal@45 8150 });
bgneal@45 8151
bgneal@45 8152 t.onRenderMenu.dispatch(t, m);
bgneal@45 8153 t.menu = m;
bgneal@45 8154 },
bgneal@45 8155
bgneal@45 8156 postRender : function() {
bgneal@45 8157 var t = this, cp = t.classPrefix;
bgneal@45 8158
bgneal@45 8159 Event.add(t.id, 'click', t.showMenu, t);
bgneal@247 8160 Event.add(t.id + '_text', 'focus', function() {
bgneal@45 8161 if (!t._focused) {
bgneal@45 8162 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
bgneal@45 8163 var idx = -1, v, kc = e.keyCode;
bgneal@45 8164
bgneal@45 8165 // Find current index
bgneal@45 8166 each(t.items, function(v, i) {
bgneal@45 8167 if (t.selectedValue == v.value)
bgneal@45 8168 idx = i;
bgneal@45 8169 });
bgneal@45 8170
bgneal@45 8171 // Move up/down
bgneal@45 8172 if (kc == 38)
bgneal@45 8173 v = t.items[idx - 1];
bgneal@45 8174 else if (kc == 40)
bgneal@45 8175 v = t.items[idx + 1];
bgneal@45 8176 else if (kc == 13) {
bgneal@45 8177 // Fake select on enter
bgneal@45 8178 v = t.selectedValue;
bgneal@45 8179 t.selectedValue = null; // Needs to be null to fake change
bgneal@45 8180 t.settings.onselect(v);
bgneal@45 8181 return Event.cancel(e);
bgneal@45 8182 }
bgneal@45 8183
bgneal@45 8184 if (v) {
bgneal@45 8185 t.hideMenu();
bgneal@45 8186 t.select(v.value);
bgneal@45 8187 }
bgneal@45 8188 });
bgneal@45 8189 }
bgneal@45 8190
bgneal@45 8191 t._focused = 1;
bgneal@45 8192 });
bgneal@45 8193 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
bgneal@45 8194
bgneal@45 8195 // Old IE doesn't have hover on all elements
bgneal@45 8196 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 8197 Event.add(t.id, 'mouseover', function() {
bgneal@45 8198 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 8199 DOM.addClass(t.id, cp + 'Hover');
bgneal@45 8200 });
bgneal@45 8201
bgneal@45 8202 Event.add(t.id, 'mouseout', function() {
bgneal@45 8203 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 8204 DOM.removeClass(t.id, cp + 'Hover');
bgneal@45 8205 });
bgneal@45 8206 }
bgneal@45 8207
bgneal@45 8208 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 8209 },
bgneal@45 8210
bgneal@45 8211 destroy : function() {
bgneal@45 8212 this.parent();
bgneal@45 8213
bgneal@45 8214 Event.clear(this.id + '_text');
bgneal@183 8215 Event.clear(this.id + '_open');
bgneal@45 8216 }
bgneal@183 8217 });
bgneal@183 8218 })(tinymce);
bgneal@183 8219 (function(tinymce) {
bgneal@45 8220 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 8221
bgneal@45 8222 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
bgneal@45 8223 NativeListBox : function(id, s) {
bgneal@45 8224 this.parent(id, s);
bgneal@45 8225 this.classPrefix = 'mceNativeListBox';
bgneal@45 8226 },
bgneal@45 8227
bgneal@45 8228 setDisabled : function(s) {
bgneal@45 8229 DOM.get(this.id).disabled = s;
bgneal@45 8230 },
bgneal@45 8231
bgneal@45 8232 isDisabled : function() {
bgneal@45 8233 return DOM.get(this.id).disabled;
bgneal@45 8234 },
bgneal@45 8235
bgneal@45 8236 select : function(va) {
bgneal@45 8237 var t = this, fv, f;
bgneal@45 8238
bgneal@45 8239 if (va == undefined)
bgneal@45 8240 return t.selectByIndex(-1);
bgneal@45 8241
bgneal@45 8242 // Is string or number make function selector
bgneal@45 8243 if (va && va.call)
bgneal@45 8244 f = va;
bgneal@45 8245 else {
bgneal@45 8246 f = function(v) {
bgneal@45 8247 return v == va;
bgneal@45 8248 };
bgneal@45 8249 }
bgneal@45 8250
bgneal@45 8251 // Do we need to do something?
bgneal@45 8252 if (va != t.selectedValue) {
bgneal@45 8253 // Find item
bgneal@45 8254 each(t.items, function(o, i) {
bgneal@45 8255 if (f(o.value)) {
bgneal@45 8256 fv = 1;
bgneal@45 8257 t.selectByIndex(i);
bgneal@45 8258 return false;
bgneal@45 8259 }
bgneal@45 8260 });
bgneal@45 8261
bgneal@45 8262 if (!fv)
bgneal@45 8263 t.selectByIndex(-1);
bgneal@45 8264 }
bgneal@45 8265 },
bgneal@45 8266
bgneal@45 8267 selectByIndex : function(idx) {
bgneal@45 8268 DOM.get(this.id).selectedIndex = idx + 1;
bgneal@45 8269 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
bgneal@45 8270 },
bgneal@45 8271
bgneal@45 8272 add : function(n, v, a) {
bgneal@45 8273 var o, t = this;
bgneal@45 8274
bgneal@45 8275 a = a || {};
bgneal@45 8276 a.value = v;
bgneal@45 8277
bgneal@45 8278 if (t.isRendered())
bgneal@45 8279 DOM.add(DOM.get(this.id), 'option', a, n);
bgneal@45 8280
bgneal@45 8281 o = {
bgneal@45 8282 title : n,
bgneal@45 8283 value : v,
bgneal@45 8284 attribs : a
bgneal@45 8285 };
bgneal@45 8286
bgneal@45 8287 t.items.push(o);
bgneal@45 8288 t.onAdd.dispatch(t, o);
bgneal@45 8289 },
bgneal@45 8290
bgneal@45 8291 getLength : function() {
bgneal@183 8292 return this.items.length;
bgneal@45 8293 },
bgneal@45 8294
bgneal@45 8295 renderHTML : function() {
bgneal@45 8296 var h, t = this;
bgneal@45 8297
bgneal@45 8298 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
bgneal@45 8299
bgneal@45 8300 each(t.items, function(it) {
bgneal@45 8301 h += DOM.createHTML('option', {value : it.value}, it.title);
bgneal@45 8302 });
bgneal@45 8303
bgneal@45 8304 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
bgneal@45 8305
bgneal@45 8306 return h;
bgneal@45 8307 },
bgneal@45 8308
bgneal@45 8309 postRender : function() {
bgneal@45 8310 var t = this, ch;
bgneal@45 8311
bgneal@45 8312 t.rendered = true;
bgneal@45 8313
bgneal@45 8314 function onChange(e) {
bgneal@45 8315 var v = t.items[e.target.selectedIndex - 1];
bgneal@45 8316
bgneal@45 8317 if (v && (v = v.value)) {
bgneal@45 8318 t.onChange.dispatch(t, v);
bgneal@45 8319
bgneal@45 8320 if (t.settings.onselect)
bgneal@45 8321 t.settings.onselect(v);
bgneal@45 8322 }
bgneal@45 8323 };
bgneal@45 8324
bgneal@45 8325 Event.add(t.id, 'change', onChange);
bgneal@45 8326
bgneal@45 8327 // Accessibility keyhandler
bgneal@45 8328 Event.add(t.id, 'keydown', function(e) {
bgneal@45 8329 var bf;
bgneal@45 8330
bgneal@45 8331 Event.remove(t.id, 'change', ch);
bgneal@45 8332
bgneal@45 8333 bf = Event.add(t.id, 'blur', function() {
bgneal@45 8334 Event.add(t.id, 'change', onChange);
bgneal@45 8335 Event.remove(t.id, 'blur', bf);
bgneal@45 8336 });
bgneal@45 8337
bgneal@45 8338 if (e.keyCode == 13 || e.keyCode == 32) {
bgneal@45 8339 onChange(e);
bgneal@45 8340 return Event.cancel(e);
bgneal@45 8341 }
bgneal@45 8342 });
bgneal@45 8343
bgneal@45 8344 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 8345 }
bgneal@183 8346 });
bgneal@183 8347 })(tinymce);
bgneal@183 8348 (function(tinymce) {
bgneal@45 8349 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 8350
bgneal@45 8351 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
bgneal@45 8352 MenuButton : function(id, s) {
bgneal@45 8353 this.parent(id, s);
bgneal@183 8354
bgneal@45 8355 this.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@183 8356
bgneal@45 8357 s.menu_container = s.menu_container || DOM.doc.body;
bgneal@45 8358 },
bgneal@45 8359
bgneal@45 8360 showMenu : function() {
bgneal@45 8361 var t = this, p1, p2, e = DOM.get(t.id), m;
bgneal@45 8362
bgneal@45 8363 if (t.isDisabled())
bgneal@45 8364 return;
bgneal@45 8365
bgneal@45 8366 if (!t.isMenuRendered) {
bgneal@45 8367 t.renderMenu();
bgneal@45 8368 t.isMenuRendered = true;
bgneal@45 8369 }
bgneal@45 8370
bgneal@45 8371 if (t.isMenuVisible)
bgneal@45 8372 return t.hideMenu();
bgneal@45 8373
bgneal@45 8374 p1 = DOM.getPos(t.settings.menu_container);
bgneal@45 8375 p2 = DOM.getPos(e);
bgneal@45 8376
bgneal@45 8377 m = t.menu;
bgneal@45 8378 m.settings.offset_x = p2.x;
bgneal@45 8379 m.settings.offset_y = p2.y;
bgneal@45 8380 m.settings.vp_offset_x = p2.x;
bgneal@45 8381 m.settings.vp_offset_y = p2.y;
bgneal@45 8382 m.settings.keyboard_focus = t._focused;
bgneal@45 8383 m.showMenu(0, e.clientHeight);
bgneal@45 8384
bgneal@45 8385 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8386 t.setState('Selected', 1);
bgneal@45 8387
bgneal@45 8388 t.isMenuVisible = 1;
bgneal@45 8389 },
bgneal@45 8390
bgneal@45 8391 renderMenu : function() {
bgneal@45 8392 var t = this, m;
bgneal@45 8393
bgneal@45 8394 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 8395 menu_line : 1,
bgneal@45 8396 'class' : this.classPrefix + 'Menu',
bgneal@45 8397 icons : t.settings.icons
bgneal@45 8398 });
bgneal@45 8399
bgneal@45 8400 m.onHideMenu.add(t.hideMenu, t);
bgneal@45 8401
bgneal@45 8402 t.onRenderMenu.dispatch(t, m);
bgneal@45 8403 t.menu = m;
bgneal@45 8404 },
bgneal@45 8405
bgneal@45 8406 hideMenu : function(e) {
bgneal@45 8407 var t = this;
bgneal@45 8408
bgneal@45 8409 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 8410 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
bgneal@45 8411 return;
bgneal@45 8412
bgneal@45 8413 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@45 8414 t.setState('Selected', 0);
bgneal@45 8415 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8416 if (t.menu)
bgneal@45 8417 t.menu.hideMenu();
bgneal@45 8418 }
bgneal@45 8419
bgneal@45 8420 t.isMenuVisible = 0;
bgneal@45 8421 },
bgneal@45 8422
bgneal@45 8423 postRender : function() {
bgneal@45 8424 var t = this, s = t.settings;
bgneal@45 8425
bgneal@45 8426 Event.add(t.id, 'click', function() {
bgneal@45 8427 if (!t.isDisabled()) {
bgneal@45 8428 if (s.onclick)
bgneal@45 8429 s.onclick(t.value);
bgneal@45 8430
bgneal@45 8431 t.showMenu();
bgneal@45 8432 }
bgneal@45 8433 });
bgneal@45 8434 }
bgneal@183 8435 });
bgneal@45 8436 })(tinymce);
bgneal@183 8437
bgneal@45 8438 (function(tinymce) {
bgneal@45 8439 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 8440
bgneal@45 8441 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
bgneal@45 8442 SplitButton : function(id, s) {
bgneal@45 8443 this.parent(id, s);
bgneal@45 8444 this.classPrefix = 'mceSplitButton';
bgneal@45 8445 },
bgneal@45 8446
bgneal@45 8447 renderHTML : function() {
bgneal@45 8448 var h, t = this, s = t.settings, h1;
bgneal@45 8449
bgneal@45 8450 h = '<tbody><tr>';
bgneal@45 8451
bgneal@45 8452 if (s.image)
bgneal@45 8453 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
bgneal@45 8454 else
bgneal@45 8455 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
bgneal@45 8456
bgneal@45 8457 h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 8458
bgneal@45 8459 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
bgneal@45 8460 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 8461
bgneal@45 8462 h += '</tr></tbody>';
bgneal@45 8463
bgneal@45 8464 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
bgneal@45 8465 },
bgneal@45 8466
bgneal@45 8467 postRender : function() {
bgneal@45 8468 var t = this, s = t.settings;
bgneal@45 8469
bgneal@45 8470 if (s.onclick) {
bgneal@45 8471 Event.add(t.id + '_action', 'click', function() {
bgneal@45 8472 if (!t.isDisabled())
bgneal@45 8473 s.onclick(t.value);
bgneal@45 8474 });
bgneal@45 8475 }
bgneal@45 8476
bgneal@45 8477 Event.add(t.id + '_open', 'click', t.showMenu, t);
bgneal@45 8478 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
bgneal@45 8479 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
bgneal@45 8480
bgneal@45 8481 // Old IE doesn't have hover on all elements
bgneal@45 8482 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 8483 Event.add(t.id, 'mouseover', function() {
bgneal@45 8484 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 8485 DOM.addClass(t.id, 'mceSplitButtonHover');
bgneal@45 8486 });
bgneal@45 8487
bgneal@45 8488 Event.add(t.id, 'mouseout', function() {
bgneal@45 8489 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 8490 DOM.removeClass(t.id, 'mceSplitButtonHover');
bgneal@45 8491 });
bgneal@45 8492 }
bgneal@45 8493 },
bgneal@45 8494
bgneal@45 8495 destroy : function() {
bgneal@45 8496 this.parent();
bgneal@45 8497
bgneal@45 8498 Event.clear(this.id + '_action');
bgneal@45 8499 Event.clear(this.id + '_open');
bgneal@45 8500 }
bgneal@183 8501 });
bgneal@45 8502 })(tinymce);
bgneal@183 8503
bgneal@45 8504 (function(tinymce) {
bgneal@45 8505 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
bgneal@45 8506
bgneal@45 8507 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
bgneal@45 8508 ColorSplitButton : function(id, s) {
bgneal@45 8509 var t = this;
bgneal@45 8510
bgneal@45 8511 t.parent(id, s);
bgneal@45 8512
bgneal@45 8513 t.settings = s = tinymce.extend({
bgneal@45 8514 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 8515 grid_width : 8,
bgneal@45 8516 default_color : '#888888'
bgneal@45 8517 }, t.settings);
bgneal@45 8518
bgneal@45 8519 t.onShowMenu = new tinymce.util.Dispatcher(t);
bgneal@183 8520
bgneal@45 8521 t.onHideMenu = new tinymce.util.Dispatcher(t);
bgneal@45 8522
bgneal@45 8523 t.value = s.default_color;
bgneal@45 8524 },
bgneal@45 8525
bgneal@45 8526 showMenu : function() {
bgneal@45 8527 var t = this, r, p, e, p2;
bgneal@45 8528
bgneal@45 8529 if (t.isDisabled())
bgneal@45 8530 return;
bgneal@45 8531
bgneal@45 8532 if (!t.isMenuRendered) {
bgneal@45 8533 t.renderMenu();
bgneal@45 8534 t.isMenuRendered = true;
bgneal@45 8535 }
bgneal@45 8536
bgneal@45 8537 if (t.isMenuVisible)
bgneal@45 8538 return t.hideMenu();
bgneal@45 8539
bgneal@45 8540 e = DOM.get(t.id);
bgneal@45 8541 DOM.show(t.id + '_menu');
bgneal@45 8542 DOM.addClass(e, 'mceSplitButtonSelected');
bgneal@45 8543 p2 = DOM.getPos(e);
bgneal@45 8544 DOM.setStyles(t.id + '_menu', {
bgneal@45 8545 left : p2.x,
bgneal@45 8546 top : p2.y + e.clientHeight,
bgneal@45 8547 zIndex : 200000
bgneal@45 8548 });
bgneal@45 8549 e = 0;
bgneal@45 8550
bgneal@45 8551 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@183 8552 t.onShowMenu.dispatch(t);
bgneal@45 8553
bgneal@45 8554 if (t._focused) {
bgneal@45 8555 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
bgneal@45 8556 if (e.keyCode == 27)
bgneal@45 8557 t.hideMenu();
bgneal@45 8558 });
bgneal@45 8559
bgneal@45 8560 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
bgneal@45 8561 }
bgneal@45 8562
bgneal@45 8563 t.isMenuVisible = 1;
bgneal@45 8564 },
bgneal@45 8565
bgneal@45 8566 hideMenu : function(e) {
bgneal@45 8567 var t = this;
bgneal@45 8568
bgneal@45 8569 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 8570 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
bgneal@45 8571 return;
bgneal@45 8572
bgneal@45 8573 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
bgneal@45 8574 DOM.removeClass(t.id, 'mceSplitButtonSelected');
bgneal@45 8575 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8576 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
bgneal@45 8577 DOM.hide(t.id + '_menu');
bgneal@45 8578 }
bgneal@45 8579
bgneal@45 8580 t.onHideMenu.dispatch(t);
bgneal@45 8581
bgneal@45 8582 t.isMenuVisible = 0;
bgneal@45 8583 },
bgneal@45 8584
bgneal@45 8585 renderMenu : function() {
bgneal@45 8586 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
bgneal@45 8587
bgneal@45 8588 w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
bgneal@45 8589 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
bgneal@45 8590 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
bgneal@45 8591
bgneal@45 8592 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
bgneal@45 8593 tb = DOM.add(n, 'tbody');
bgneal@45 8594
bgneal@45 8595 // Generate color grid
bgneal@45 8596 i = 0;
bgneal@45 8597 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
bgneal@45 8598 c = c.replace(/^#/, '');
bgneal@45 8599
bgneal@45 8600 if (!i--) {
bgneal@45 8601 tr = DOM.add(tb, 'tr');
bgneal@45 8602 i = s.grid_width - 1;
bgneal@45 8603 }
bgneal@45 8604
bgneal@45 8605 n = DOM.add(tr, 'td');
bgneal@45 8606
bgneal@45 8607 n = DOM.add(n, 'a', {
bgneal@45 8608 href : 'javascript:;',
bgneal@45 8609 style : {
bgneal@45 8610 backgroundColor : '#' + c
bgneal@45 8611 },
bgneal@183 8612 _mce_color : '#' + c
bgneal@45 8613 });
bgneal@45 8614 });
bgneal@45 8615
bgneal@45 8616 if (s.more_colors_func) {
bgneal@45 8617 n = DOM.add(tb, 'tr');
bgneal@45 8618 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
bgneal@45 8619 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
bgneal@45 8620
bgneal@45 8621 Event.add(n, 'click', function(e) {
bgneal@45 8622 s.more_colors_func.call(s.more_colors_scope || this);
bgneal@45 8623 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@45 8624 });
bgneal@45 8625 }
bgneal@45 8626
bgneal@45 8627 DOM.addClass(m, 'mceColorSplitMenu');
bgneal@45 8628
bgneal@45 8629 Event.add(t.id + '_menu', 'click', function(e) {
bgneal@45 8630 var c;
bgneal@45 8631
bgneal@45 8632 e = e.target;
bgneal@45 8633
bgneal@183 8634 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
bgneal@45 8635 t.setColor(c);
bgneal@45 8636
bgneal@45 8637 return Event.cancel(e); // Prevent IE auto save warning
bgneal@45 8638 });
bgneal@45 8639
bgneal@45 8640 return w;
bgneal@45 8641 },
bgneal@45 8642
bgneal@45 8643 setColor : function(c) {
bgneal@45 8644 var t = this;
bgneal@45 8645
bgneal@45 8646 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
bgneal@45 8647
bgneal@45 8648 t.value = c;
bgneal@45 8649 t.hideMenu();
bgneal@45 8650 t.settings.onselect(c);
bgneal@45 8651 },
bgneal@45 8652
bgneal@45 8653 postRender : function() {
bgneal@45 8654 var t = this, id = t.id;
bgneal@45 8655
bgneal@45 8656 t.parent();
bgneal@45 8657 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
bgneal@45 8658 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
bgneal@45 8659 },
bgneal@45 8660
bgneal@45 8661 destroy : function() {
bgneal@45 8662 this.parent();
bgneal@45 8663
bgneal@45 8664 Event.clear(this.id + '_menu');
bgneal@45 8665 Event.clear(this.id + '_more');
bgneal@45 8666 DOM.remove(this.id + '_menu');
bgneal@45 8667 }
bgneal@183 8668 });
bgneal@45 8669 })(tinymce);
bgneal@183 8670
bgneal@45 8671 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
bgneal@45 8672 renderHTML : function() {
bgneal@45 8673 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
bgneal@45 8674
bgneal@45 8675 cl = t.controls;
bgneal@45 8676 for (i=0; i<cl.length; i++) {
bgneal@45 8677 // Get current control, prev control, next control and if the control is a list box or not
bgneal@45 8678 co = cl[i];
bgneal@45 8679 pr = cl[i - 1];
bgneal@45 8680 nx = cl[i + 1];
bgneal@45 8681
bgneal@45 8682 // Add toolbar start
bgneal@45 8683 if (i === 0) {
bgneal@45 8684 c = 'mceToolbarStart';
bgneal@45 8685
bgneal@45 8686 if (co.Button)
bgneal@45 8687 c += ' mceToolbarStartButton';
bgneal@45 8688 else if (co.SplitButton)
bgneal@45 8689 c += ' mceToolbarStartSplitButton';
bgneal@45 8690 else if (co.ListBox)
bgneal@45 8691 c += ' mceToolbarStartListBox';
bgneal@45 8692
bgneal@45 8693 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8694 }
bgneal@45 8695
bgneal@45 8696 // Add toolbar end before list box and after the previous button
bgneal@45 8697 // This is to fix the o2k7 editor skins
bgneal@45 8698 if (pr && co.ListBox) {
bgneal@45 8699 if (pr.Button || pr.SplitButton)
bgneal@45 8700 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8701 }
bgneal@45 8702
bgneal@45 8703 // Render control HTML
bgneal@45 8704
bgneal@45 8705 // IE 8 quick fix, needed to propertly generate a hit area for anchors
bgneal@45 8706 if (dom.stdMode)
bgneal@45 8707 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
bgneal@45 8708 else
bgneal@45 8709 h += '<td>' + co.renderHTML() + '</td>';
bgneal@45 8710
bgneal@45 8711 // Add toolbar start after list box and before the next button
bgneal@45 8712 // This is to fix the o2k7 editor skins
bgneal@45 8713 if (nx && co.ListBox) {
bgneal@45 8714 if (nx.Button || nx.SplitButton)
bgneal@45 8715 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8716 }
bgneal@45 8717 }
bgneal@45 8718
bgneal@45 8719 c = 'mceToolbarEnd';
bgneal@45 8720
bgneal@45 8721 if (co.Button)
bgneal@45 8722 c += ' mceToolbarEndButton';
bgneal@45 8723 else if (co.SplitButton)
bgneal@45 8724 c += ' mceToolbarEndSplitButton';
bgneal@45 8725 else if (co.ListBox)
bgneal@45 8726 c += ' mceToolbarEndListBox';
bgneal@45 8727
bgneal@45 8728 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8729
bgneal@45 8730 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
bgneal@45 8731 }
bgneal@183 8732 });
bgneal@183 8733
bgneal@45 8734 (function(tinymce) {
bgneal@45 8735 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
bgneal@45 8736
bgneal@45 8737 tinymce.create('tinymce.AddOnManager', {
bgneal@247 8738 AddOnManager : function() {
bgneal@247 8739 var self = this;
bgneal@247 8740
bgneal@247 8741 self.items = [];
bgneal@247 8742 self.urls = {};
bgneal@247 8743 self.lookup = {};
bgneal@247 8744 self.onAdd = new Dispatcher(self);
bgneal@247 8745 },
bgneal@45 8746
bgneal@45 8747 get : function(n) {
bgneal@45 8748 return this.lookup[n];
bgneal@45 8749 },
bgneal@45 8750
bgneal@45 8751 requireLangPack : function(n) {
bgneal@183 8752 var s = tinymce.settings;
bgneal@183 8753
bgneal@183 8754 if (s && s.language)
bgneal@183 8755 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
bgneal@45 8756 },
bgneal@45 8757
bgneal@45 8758 add : function(id, o) {
bgneal@45 8759 this.items.push(o);
bgneal@45 8760 this.lookup[id] = o;
bgneal@45 8761 this.onAdd.dispatch(this, id, o);
bgneal@45 8762
bgneal@45 8763 return o;
bgneal@45 8764 },
bgneal@45 8765
bgneal@45 8766 load : function(n, u, cb, s) {
bgneal@45 8767 var t = this;
bgneal@45 8768
bgneal@45 8769 if (t.urls[n])
bgneal@45 8770 return;
bgneal@45 8771
bgneal@45 8772 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
bgneal@45 8773 u = tinymce.baseURL + '/' + u;
bgneal@45 8774
bgneal@45 8775 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
bgneal@247 8776
bgneal@247 8777 if (!t.lookup[n])
bgneal@247 8778 tinymce.ScriptLoader.add(u, cb, s);
bgneal@45 8779 }
bgneal@183 8780 });
bgneal@45 8781
bgneal@45 8782 // Create plugin and theme managers
bgneal@45 8783 tinymce.PluginManager = new tinymce.AddOnManager();
bgneal@45 8784 tinymce.ThemeManager = new tinymce.AddOnManager();
bgneal@183 8785 }(tinymce));
bgneal@183 8786
bgneal@183 8787 (function(tinymce) {
bgneal@45 8788 // Shorten names
bgneal@183 8789 var each = tinymce.each, extend = tinymce.extend,
bgneal@183 8790 DOM = tinymce.DOM, Event = tinymce.dom.Event,
bgneal@183 8791 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@183 8792 explode = tinymce.explode,
bgneal@183 8793 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
bgneal@183 8794
bgneal@183 8795 // Setup some URLs where the editor API is located and where the document is
bgneal@183 8796 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
bgneal@183 8797 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
bgneal@183 8798 tinymce.documentBaseURL += '/';
bgneal@183 8799
bgneal@183 8800 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
bgneal@183 8801
bgneal@183 8802 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
bgneal@183 8803
bgneal@183 8804 // Add before unload listener
bgneal@183 8805 // This was required since IE was leaking memory if you added and removed beforeunload listeners
bgneal@183 8806 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
bgneal@183 8807 tinymce.onBeforeUnload = new Dispatcher(tinymce);
bgneal@183 8808
bgneal@183 8809 // Must be on window or IE will leak if the editor is placed in frame or iframe
bgneal@183 8810 Event.add(window, 'beforeunload', function(e) {
bgneal@183 8811 tinymce.onBeforeUnload.dispatch(tinymce, e);
bgneal@183 8812 });
bgneal@183 8813
bgneal@183 8814 tinymce.onAddEditor = new Dispatcher(tinymce);
bgneal@183 8815
bgneal@183 8816 tinymce.onRemoveEditor = new Dispatcher(tinymce);
bgneal@183 8817
bgneal@183 8818 tinymce.EditorManager = extend(tinymce, {
bgneal@183 8819 editors : [],
bgneal@183 8820
bgneal@45 8821 i18n : {},
bgneal@183 8822
bgneal@45 8823 activeEditor : null,
bgneal@45 8824
bgneal@45 8825 init : function(s) {
bgneal@183 8826 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
bgneal@45 8827
bgneal@45 8828 function execCallback(se, n, s) {
bgneal@45 8829 var f = se[n];
bgneal@45 8830
bgneal@45 8831 if (!f)
bgneal@45 8832 return;
bgneal@45 8833
bgneal@45 8834 if (tinymce.is(f, 'string')) {
bgneal@45 8835 s = f.replace(/\.\w+$/, '');
bgneal@45 8836 s = s ? tinymce.resolve(s) : 0;
bgneal@45 8837 f = tinymce.resolve(f);
bgneal@45 8838 }
bgneal@45 8839
bgneal@45 8840 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
bgneal@45 8841 };
bgneal@45 8842
bgneal@45 8843 s = extend({
bgneal@45 8844 theme : "simple",
bgneal@183 8845 language : "en"
bgneal@45 8846 }, s);
bgneal@45 8847
bgneal@45 8848 t.settings = s;
bgneal@45 8849
bgneal@45 8850 // Legacy call
bgneal@45 8851 Event.add(document, 'init', function() {
bgneal@45 8852 var l, co;
bgneal@45 8853
bgneal@45 8854 execCallback(s, 'onpageload');
bgneal@45 8855
bgneal@45 8856 switch (s.mode) {
bgneal@45 8857 case "exact":
bgneal@45 8858 l = s.elements || '';
bgneal@45 8859
bgneal@45 8860 if(l.length > 0) {
bgneal@45 8861 each(explode(l), function(v) {
bgneal@45 8862 if (DOM.get(v)) {
bgneal@45 8863 ed = new tinymce.Editor(v, s);
bgneal@45 8864 el.push(ed);
bgneal@45 8865 ed.render(1);
bgneal@45 8866 } else {
bgneal@45 8867 each(document.forms, function(f) {
bgneal@45 8868 each(f.elements, function(e) {
bgneal@45 8869 if (e.name === v) {
bgneal@183 8870 v = 'mce_editor_' + instanceCounter++;
bgneal@45 8871 DOM.setAttrib(e, 'id', v);
bgneal@45 8872
bgneal@45 8873 ed = new tinymce.Editor(v, s);
bgneal@45 8874 el.push(ed);
bgneal@45 8875 ed.render(1);
bgneal@45 8876 }
bgneal@45 8877 });
bgneal@45 8878 });
bgneal@45 8879 }
bgneal@45 8880 });
bgneal@45 8881 }
bgneal@45 8882 break;
bgneal@45 8883
bgneal@45 8884 case "textareas":
bgneal@45 8885 case "specific_textareas":
bgneal@45 8886 function hasClass(n, c) {
bgneal@45 8887 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
bgneal@45 8888 };
bgneal@45 8889
bgneal@45 8890 each(DOM.select('textarea'), function(v) {
bgneal@45 8891 if (s.editor_deselector && hasClass(v, s.editor_deselector))
bgneal@45 8892 return;
bgneal@45 8893
bgneal@45 8894 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
bgneal@45 8895 // Can we use the name
bgneal@45 8896 e = DOM.get(v.name);
bgneal@45 8897 if (!v.id && !e)
bgneal@45 8898 v.id = v.name;
bgneal@45 8899
bgneal@45 8900 // Generate unique name if missing or already exists
bgneal@45 8901 if (!v.id || t.get(v.id))
bgneal@45 8902 v.id = DOM.uniqueId();
bgneal@45 8903
bgneal@45 8904 ed = new tinymce.Editor(v.id, s);
bgneal@45 8905 el.push(ed);
bgneal@45 8906 ed.render(1);
bgneal@45 8907 }
bgneal@45 8908 });
bgneal@45 8909 break;
bgneal@45 8910 }
bgneal@45 8911
bgneal@45 8912 // Call onInit when all editors are initialized
bgneal@45 8913 if (s.oninit) {
bgneal@45 8914 l = co = 0;
bgneal@45 8915
bgneal@183 8916 each(el, function(ed) {
bgneal@45 8917 co++;
bgneal@45 8918
bgneal@45 8919 if (!ed.initialized) {
bgneal@45 8920 // Wait for it
bgneal@45 8921 ed.onInit.add(function() {
bgneal@45 8922 l++;
bgneal@45 8923
bgneal@45 8924 // All done
bgneal@45 8925 if (l == co)
bgneal@45 8926 execCallback(s, 'oninit');
bgneal@45 8927 });
bgneal@45 8928 } else
bgneal@45 8929 l++;
bgneal@45 8930
bgneal@45 8931 // All done
bgneal@45 8932 if (l == co)
bgneal@45 8933 execCallback(s, 'oninit');
bgneal@45 8934 });
bgneal@45 8935 }
bgneal@45 8936 });
bgneal@45 8937 },
bgneal@45 8938
bgneal@45 8939 get : function(id) {
bgneal@183 8940 if (id === undefined)
bgneal@183 8941 return this.editors;
bgneal@183 8942
bgneal@45 8943 return this.editors[id];
bgneal@45 8944 },
bgneal@45 8945
bgneal@45 8946 getInstanceById : function(id) {
bgneal@45 8947 return this.get(id);
bgneal@45 8948 },
bgneal@45 8949
bgneal@183 8950 add : function(editor) {
bgneal@183 8951 var self = this, editors = self.editors;
bgneal@183 8952
bgneal@183 8953 // Add named and index editor instance
bgneal@183 8954 editors[editor.id] = editor;
bgneal@183 8955 editors.push(editor);
bgneal@183 8956
bgneal@183 8957 self._setActive(editor);
bgneal@183 8958 self.onAddEditor.dispatch(self, editor);
bgneal@183 8959
bgneal@183 8960
bgneal@183 8961 return editor;
bgneal@183 8962 },
bgneal@183 8963
bgneal@183 8964 remove : function(editor) {
bgneal@183 8965 var t = this, i, editors = t.editors;
bgneal@45 8966
bgneal@45 8967 // Not in the collection
bgneal@183 8968 if (!editors[editor.id])
bgneal@45 8969 return null;
bgneal@45 8970
bgneal@183 8971 delete editors[editor.id];
bgneal@183 8972
bgneal@183 8973 for (i = 0; i < editors.length; i++) {
bgneal@183 8974 if (editors[i] == editor) {
bgneal@183 8975 editors.splice(i, 1);
bgneal@183 8976 break;
bgneal@183 8977 }
bgneal@183 8978 }
bgneal@45 8979
bgneal@45 8980 // Select another editor since the active one was removed
bgneal@183 8981 if (t.activeEditor == editor)
bgneal@183 8982 t._setActive(editors[0]);
bgneal@183 8983
bgneal@183 8984 editor.destroy();
bgneal@183 8985 t.onRemoveEditor.dispatch(t, editor);
bgneal@183 8986
bgneal@183 8987 return editor;
bgneal@45 8988 },
bgneal@45 8989
bgneal@45 8990 execCommand : function(c, u, v) {
bgneal@45 8991 var t = this, ed = t.get(v), w;
bgneal@45 8992
bgneal@45 8993 // Manager commands
bgneal@45 8994 switch (c) {
bgneal@45 8995 case "mceFocus":
bgneal@45 8996 ed.focus();
bgneal@45 8997 return true;
bgneal@45 8998
bgneal@45 8999 case "mceAddEditor":
bgneal@45 9000 case "mceAddControl":
bgneal@45 9001 if (!t.get(v))
bgneal@45 9002 new tinymce.Editor(v, t.settings).render();
bgneal@45 9003
bgneal@45 9004 return true;
bgneal@45 9005
bgneal@45 9006 case "mceAddFrameControl":
bgneal@45 9007 w = v.window;
bgneal@45 9008
bgneal@45 9009 // Add tinyMCE global instance and tinymce namespace to specified window
bgneal@45 9010 w.tinyMCE = tinyMCE;
bgneal@45 9011 w.tinymce = tinymce;
bgneal@45 9012
bgneal@45 9013 tinymce.DOM.doc = w.document;
bgneal@45 9014 tinymce.DOM.win = w;
bgneal@45 9015
bgneal@45 9016 ed = new tinymce.Editor(v.element_id, v);
bgneal@45 9017 ed.render();
bgneal@45 9018
bgneal@45 9019 // Fix IE memory leaks
bgneal@45 9020 if (tinymce.isIE) {
bgneal@45 9021 function clr() {
bgneal@45 9022 ed.destroy();
bgneal@45 9023 w.detachEvent('onunload', clr);
bgneal@45 9024 w = w.tinyMCE = w.tinymce = null; // IE leak
bgneal@45 9025 };
bgneal@45 9026
bgneal@45 9027 w.attachEvent('onunload', clr);
bgneal@45 9028 }
bgneal@45 9029
bgneal@45 9030 v.page_window = null;
bgneal@45 9031
bgneal@45 9032 return true;
bgneal@45 9033
bgneal@45 9034 case "mceRemoveEditor":
bgneal@45 9035 case "mceRemoveControl":
bgneal@45 9036 if (ed)
bgneal@45 9037 ed.remove();
bgneal@45 9038
bgneal@45 9039 return true;
bgneal@45 9040
bgneal@45 9041 case 'mceToggleEditor':
bgneal@45 9042 if (!ed) {
bgneal@45 9043 t.execCommand('mceAddControl', 0, v);
bgneal@45 9044 return true;
bgneal@45 9045 }
bgneal@45 9046
bgneal@45 9047 if (ed.isHidden())
bgneal@45 9048 ed.show();
bgneal@45 9049 else
bgneal@45 9050 ed.hide();
bgneal@45 9051
bgneal@45 9052 return true;
bgneal@45 9053 }
bgneal@45 9054
bgneal@45 9055 // Run command on active editor
bgneal@45 9056 if (t.activeEditor)
bgneal@45 9057 return t.activeEditor.execCommand(c, u, v);
bgneal@45 9058
bgneal@45 9059 return false;
bgneal@45 9060 },
bgneal@45 9061
bgneal@45 9062 execInstanceCommand : function(id, c, u, v) {
bgneal@45 9063 var ed = this.get(id);
bgneal@45 9064
bgneal@45 9065 if (ed)
bgneal@45 9066 return ed.execCommand(c, u, v);
bgneal@45 9067
bgneal@45 9068 return false;
bgneal@45 9069 },
bgneal@45 9070
bgneal@45 9071 triggerSave : function() {
bgneal@45 9072 each(this.editors, function(e) {
bgneal@45 9073 e.save();
bgneal@45 9074 });
bgneal@45 9075 },
bgneal@45 9076
bgneal@45 9077 addI18n : function(p, o) {
bgneal@45 9078 var lo, i18n = this.i18n;
bgneal@45 9079
bgneal@45 9080 if (!tinymce.is(p, 'string')) {
bgneal@45 9081 each(p, function(o, lc) {
bgneal@45 9082 each(o, function(o, g) {
bgneal@45 9083 each(o, function(o, k) {
bgneal@45 9084 if (g === 'common')
bgneal@45 9085 i18n[lc + '.' + k] = o;
bgneal@45 9086 else
bgneal@45 9087 i18n[lc + '.' + g + '.' + k] = o;
bgneal@45 9088 });
bgneal@45 9089 });
bgneal@45 9090 });
bgneal@45 9091 } else {
bgneal@45 9092 each(o, function(o, k) {
bgneal@45 9093 i18n[p + '.' + k] = o;
bgneal@45 9094 });
bgneal@45 9095 }
bgneal@45 9096 },
bgneal@45 9097
bgneal@45 9098 // Private methods
bgneal@45 9099
bgneal@183 9100 _setActive : function(editor) {
bgneal@183 9101 this.selectedInstance = this.activeEditor = editor;
bgneal@45 9102 }
bgneal@183 9103 });
bgneal@45 9104 })(tinymce);
bgneal@45 9105
bgneal@45 9106 (function(tinymce) {
bgneal@183 9107 // Shorten these names
bgneal@183 9108 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
bgneal@183 9109 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
bgneal@183 9110 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
bgneal@183 9111 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@183 9112 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
bgneal@45 9113
bgneal@45 9114 tinymce.create('tinymce.Editor', {
bgneal@45 9115 Editor : function(id, s) {
bgneal@45 9116 var t = this;
bgneal@45 9117
bgneal@45 9118 t.id = t.editorId = id;
bgneal@183 9119
bgneal@45 9120 t.execCommands = {};
bgneal@45 9121 t.queryStateCommands = {};
bgneal@45 9122 t.queryValueCommands = {};
bgneal@183 9123
bgneal@183 9124 t.isNotDirty = false;
bgneal@183 9125
bgneal@45 9126 t.plugins = {};
bgneal@45 9127
bgneal@45 9128 // Add events to the editor
bgneal@45 9129 each([
bgneal@45 9130 'onPreInit',
bgneal@183 9131
bgneal@45 9132 'onBeforeRenderUI',
bgneal@183 9133
bgneal@45 9134 'onPostRender',
bgneal@183 9135
bgneal@45 9136 'onInit',
bgneal@183 9137
bgneal@45 9138 'onRemove',
bgneal@183 9139
bgneal@45 9140 'onActivate',
bgneal@183 9141
bgneal@45 9142 'onDeactivate',
bgneal@183 9143
bgneal@45 9144 'onClick',
bgneal@183 9145
bgneal@45 9146 'onEvent',
bgneal@183 9147
bgneal@45 9148 'onMouseUp',
bgneal@183 9149
bgneal@45 9150 'onMouseDown',
bgneal@183 9151
bgneal@45 9152 'onDblClick',
bgneal@183 9153
bgneal@45 9154 'onKeyDown',
bgneal@183 9155
bgneal@45 9156 'onKeyUp',
bgneal@183 9157
bgneal@45 9158 'onKeyPress',
bgneal@183 9159
bgneal@45 9160 'onContextMenu',
bgneal@183 9161
bgneal@45 9162 'onSubmit',
bgneal@183 9163
bgneal@45 9164 'onReset',
bgneal@183 9165
bgneal@45 9166 'onPaste',
bgneal@183 9167
bgneal@45 9168 'onPreProcess',
bgneal@183 9169
bgneal@45 9170 'onPostProcess',
bgneal@183 9171
bgneal@45 9172 'onBeforeSetContent',
bgneal@183 9173
bgneal@45 9174 'onBeforeGetContent',
bgneal@183 9175
bgneal@45 9176 'onSetContent',
bgneal@183 9177
bgneal@45 9178 'onGetContent',
bgneal@183 9179
bgneal@45 9180 'onLoadContent',
bgneal@183 9181
bgneal@45 9182 'onSaveContent',
bgneal@183 9183
bgneal@45 9184 'onNodeChange',
bgneal@183 9185
bgneal@45 9186 'onChange',
bgneal@183 9187
bgneal@45 9188 'onBeforeExecCommand',
bgneal@183 9189
bgneal@45 9190 'onExecCommand',
bgneal@183 9191
bgneal@45 9192 'onUndo',
bgneal@183 9193
bgneal@45 9194 'onRedo',
bgneal@183 9195
bgneal@45 9196 'onVisualAid',
bgneal@183 9197
bgneal@45 9198 'onSetProgressState'
bgneal@45 9199 ], function(e) {
bgneal@45 9200 t[e] = new Dispatcher(t);
bgneal@45 9201 });
bgneal@45 9202
bgneal@45 9203 t.settings = s = extend({
bgneal@45 9204 id : id,
bgneal@45 9205 language : 'en',
bgneal@45 9206 docs_language : 'en',
bgneal@45 9207 theme : 'simple',
bgneal@45 9208 skin : 'default',
bgneal@45 9209 delta_width : 0,
bgneal@45 9210 delta_height : 0,
bgneal@45 9211 popup_css : '',
bgneal@45 9212 plugins : '',
bgneal@45 9213 document_base_url : tinymce.documentBaseURL,
bgneal@45 9214 add_form_submit_trigger : 1,
bgneal@45 9215 submit_patch : 1,
bgneal@45 9216 add_unload_trigger : 1,
bgneal@45 9217 convert_urls : 1,
bgneal@45 9218 relative_urls : 1,
bgneal@45 9219 remove_script_host : 1,
bgneal@45 9220 table_inline_editing : 0,
bgneal@45 9221 object_resizing : 1,
bgneal@45 9222 cleanup : 1,
bgneal@45 9223 accessibility_focus : 1,
bgneal@45 9224 custom_shortcuts : 1,
bgneal@45 9225 custom_undo_redo_keyboard_shortcuts : 1,
bgneal@45 9226 custom_undo_redo_restore_selection : 1,
bgneal@45 9227 custom_undo_redo : 1,
bgneal@183 9228 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 9229 visual_table_class : 'mceItemTable',
bgneal@45 9230 visual : 1,
bgneal@45 9231 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
bgneal@45 9232 apply_source_formatting : 1,
bgneal@45 9233 directionality : 'ltr',
bgneal@45 9234 forced_root_block : 'p',
bgneal@183 9235 valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
bgneal@45 9236 hidden_input : 1,
bgneal@45 9237 padd_empty_editor : 1,
bgneal@45 9238 render_ui : 1,
bgneal@45 9239 init_theme : 1,
bgneal@45 9240 force_p_newlines : 1,
bgneal@45 9241 indentation : '30px',
bgneal@45 9242 keep_styles : 1,
bgneal@45 9243 fix_table_elements : 1,
bgneal@183 9244 inline_styles : 1,
bgneal@183 9245 convert_fonts_to_spans : true
bgneal@45 9246 }, s);
bgneal@45 9247
bgneal@45 9248 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
bgneal@45 9249 base_uri : tinyMCE.baseURI
bgneal@45 9250 });
bgneal@183 9251
bgneal@183 9252 t.baseURI = tinymce.baseURI;
bgneal@45 9253
bgneal@45 9254 // Call setup
bgneal@45 9255 t.execCallback('setup', t);
bgneal@45 9256 },
bgneal@45 9257
bgneal@45 9258 render : function(nst) {
bgneal@45 9259 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
bgneal@45 9260
bgneal@45 9261 // Page is not loaded yet, wait for it
bgneal@45 9262 if (!Event.domLoaded) {
bgneal@45 9263 Event.add(document, 'init', function() {
bgneal@45 9264 t.render();
bgneal@45 9265 });
bgneal@45 9266 return;
bgneal@45 9267 }
bgneal@45 9268
bgneal@183 9269 tinyMCE.settings = s;
bgneal@45 9270
bgneal@45 9271 // Element not found, then skip initialization
bgneal@45 9272 if (!t.getElement())
bgneal@45 9273 return;
bgneal@45 9274
bgneal@217 9275 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
bgneal@217 9276 // browser says it has contentEditable support but there is no visible caret
bgneal@217 9277 // We will remove this check ones Apple implements full contentEditable support
bgneal@217 9278 if (tinymce.isIDevice)
bgneal@217 9279 return;
bgneal@217 9280
bgneal@45 9281 // Add hidden input for non input elements inside form elements
bgneal@45 9282 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
bgneal@45 9283 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
bgneal@45 9284
bgneal@45 9285 if (tinymce.WindowManager)
bgneal@45 9286 t.windowManager = new tinymce.WindowManager(t);
bgneal@45 9287
bgneal@45 9288 if (s.encoding == 'xml') {
bgneal@45 9289 t.onGetContent.add(function(ed, o) {
bgneal@45 9290 if (o.save)
bgneal@45 9291 o.content = DOM.encode(o.content);
bgneal@45 9292 });
bgneal@45 9293 }
bgneal@45 9294
bgneal@45 9295 if (s.add_form_submit_trigger) {
bgneal@45 9296 t.onSubmit.addToTop(function() {
bgneal@45 9297 if (t.initialized) {
bgneal@45 9298 t.save();
bgneal@45 9299 t.isNotDirty = 1;
bgneal@45 9300 }
bgneal@45 9301 });
bgneal@45 9302 }
bgneal@45 9303
bgneal@45 9304 if (s.add_unload_trigger) {
bgneal@45 9305 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
bgneal@45 9306 if (t.initialized && !t.destroyed && !t.isHidden())
bgneal@45 9307 t.save({format : 'raw', no_events : true});
bgneal@45 9308 });
bgneal@45 9309 }
bgneal@45 9310
bgneal@45 9311 tinymce.addUnload(t.destroy, t);
bgneal@45 9312
bgneal@45 9313 if (s.submit_patch) {
bgneal@45 9314 t.onBeforeRenderUI.add(function() {
bgneal@45 9315 var n = t.getElement().form;
bgneal@45 9316
bgneal@45 9317 if (!n)
bgneal@45 9318 return;
bgneal@45 9319
bgneal@45 9320 // Already patched
bgneal@45 9321 if (n._mceOldSubmit)
bgneal@45 9322 return;
bgneal@45 9323
bgneal@45 9324 // Check page uses id="submit" or name="submit" for it's submit button
bgneal@45 9325 if (!n.submit.nodeType && !n.submit.length) {
bgneal@45 9326 t.formElement = n;
bgneal@45 9327 n._mceOldSubmit = n.submit;
bgneal@45 9328 n.submit = function() {
bgneal@45 9329 // Save all instances
bgneal@183 9330 tinymce.triggerSave();
bgneal@45 9331 t.isNotDirty = 1;
bgneal@45 9332
bgneal@183 9333 return t.formElement._mceOldSubmit(t.formElement);
bgneal@45 9334 };
bgneal@45 9335 }
bgneal@45 9336
bgneal@45 9337 n = null;
bgneal@45 9338 });
bgneal@45 9339 }
bgneal@45 9340
bgneal@45 9341 // Load scripts
bgneal@45 9342 function loadScripts() {
bgneal@45 9343 if (s.language)
bgneal@45 9344 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
bgneal@45 9345
bgneal@45 9346 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
bgneal@45 9347 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
bgneal@45 9348
bgneal@45 9349 each(explode(s.plugins), function(p) {
bgneal@45 9350 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
bgneal@183 9351 // Skip safari plugin, since it is removed as of 3.3b1
bgneal@183 9352 if (p == 'safari')
bgneal@45 9353 return;
bgneal@45 9354
bgneal@45 9355 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
bgneal@45 9356 }
bgneal@45 9357 });
bgneal@45 9358
bgneal@45 9359 // Init when que is loaded
bgneal@45 9360 sl.loadQueue(function() {
bgneal@45 9361 if (!t.removed)
bgneal@45 9362 t.init();
bgneal@45 9363 });
bgneal@45 9364 };
bgneal@45 9365
bgneal@183 9366 loadScripts();
bgneal@45 9367 },
bgneal@45 9368
bgneal@45 9369 init : function() {
bgneal@45 9370 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
bgneal@45 9371
bgneal@183 9372 tinymce.add(t);
bgneal@183 9373
bgneal@45 9374 if (s.theme) {
bgneal@45 9375 s.theme = s.theme.replace(/-/, '');
bgneal@45 9376 o = ThemeManager.get(s.theme);
bgneal@45 9377 t.theme = new o();
bgneal@45 9378
bgneal@45 9379 if (t.theme.init && s.init_theme)
bgneal@45 9380 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
bgneal@45 9381 }
bgneal@45 9382
bgneal@45 9383 // Create all plugins
bgneal@45 9384 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
bgneal@45 9385 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
bgneal@45 9386
bgneal@45 9387 if (c) {
bgneal@45 9388 po = new c(t, u);
bgneal@45 9389
bgneal@45 9390 t.plugins[p] = po;
bgneal@45 9391
bgneal@45 9392 if (po.init)
bgneal@45 9393 po.init(t, u);
bgneal@45 9394 }
bgneal@45 9395 });
bgneal@45 9396
bgneal@45 9397 // Setup popup CSS path(s)
bgneal@45 9398 if (s.popup_css !== false) {
bgneal@45 9399 if (s.popup_css)
bgneal@45 9400 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
bgneal@45 9401 else
bgneal@45 9402 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
bgneal@45 9403 }
bgneal@45 9404
bgneal@45 9405 if (s.popup_css_add)
bgneal@45 9406 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
bgneal@45 9407
bgneal@45 9408 t.controlManager = new tinymce.ControlManager(t);
bgneal@45 9409
bgneal@45 9410 if (s.custom_undo_redo) {
bgneal@183 9411 // Add initial undo level
bgneal@183 9412 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@183 9413 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
bgneal@183 9414 if (!t.undoManager.hasUndo())
bgneal@183 9415 t.undoManager.add();
bgneal@183 9416 }
bgneal@183 9417 });
bgneal@183 9418
bgneal@45 9419 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@45 9420 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@45 9421 t.undoManager.add();
bgneal@45 9422 });
bgneal@45 9423 }
bgneal@45 9424
bgneal@45 9425 t.onExecCommand.add(function(ed, c) {
bgneal@45 9426 // Don't refresh the select lists until caret move
bgneal@45 9427 if (!/^(FontName|FontSize)$/.test(c))
bgneal@45 9428 t.nodeChanged();
bgneal@45 9429 });
bgneal@45 9430
bgneal@45 9431 // Remove ghost selections on images and tables in Gecko
bgneal@45 9432 if (isGecko) {
bgneal@45 9433 function repaint(a, o) {
bgneal@45 9434 if (!o || !o.initial)
bgneal@45 9435 t.execCommand('mceRepaint');
bgneal@45 9436 };
bgneal@45 9437
bgneal@45 9438 t.onUndo.add(repaint);
bgneal@45 9439 t.onRedo.add(repaint);
bgneal@45 9440 t.onSetContent.add(repaint);
bgneal@45 9441 }
bgneal@45 9442
bgneal@45 9443 // Enables users to override the control factory
bgneal@45 9444 t.onBeforeRenderUI.dispatch(t, t.controlManager);
bgneal@45 9445
bgneal@45 9446 // Measure box
bgneal@45 9447 if (s.render_ui) {
bgneal@45 9448 w = s.width || e.style.width || e.offsetWidth;
bgneal@45 9449 h = s.height || e.style.height || e.offsetHeight;
bgneal@45 9450 t.orgDisplay = e.style.display;
bgneal@45 9451 re = /^[0-9\.]+(|px)$/i;
bgneal@45 9452
bgneal@45 9453 if (re.test('' + w))
bgneal@45 9454 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
bgneal@45 9455
bgneal@45 9456 if (re.test('' + h))
bgneal@45 9457 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
bgneal@45 9458
bgneal@45 9459 // Render UI
bgneal@45 9460 o = t.theme.renderUI({
bgneal@45 9461 targetNode : e,
bgneal@45 9462 width : w,
bgneal@45 9463 height : h,
bgneal@45 9464 deltaWidth : s.delta_width,
bgneal@45 9465 deltaHeight : s.delta_height
bgneal@45 9466 });
bgneal@45 9467
bgneal@45 9468 t.editorContainer = o.editorContainer;
bgneal@45 9469 }
bgneal@45 9470
bgneal@45 9471
bgneal@183 9472 // User specified a document.domain value
bgneal@183 9473 if (document.domain && location.hostname != document.domain)
bgneal@183 9474 tinymce.relaxedDomain = document.domain;
bgneal@183 9475
bgneal@45 9476 // Resize editor
bgneal@45 9477 DOM.setStyles(o.sizeContainer || o.editorContainer, {
bgneal@45 9478 width : w,
bgneal@45 9479 height : h
bgneal@45 9480 });
bgneal@45 9481
bgneal@45 9482 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
bgneal@45 9483 if (h < 100)
bgneal@45 9484 h = 100;
bgneal@45 9485
bgneal@183 9486 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
bgneal@183 9487
bgneal@183 9488 // We only need to override paths if we have to
bgneal@183 9489 // IE has a bug where it remove site absolute urls to relative ones if this is specified
bgneal@183 9490 if (s.document_base_url != tinymce.documentBaseURL)
bgneal@183 9491 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
bgneal@183 9492
bgneal@183 9493 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
bgneal@45 9494
bgneal@45 9495 if (tinymce.relaxedDomain)
bgneal@45 9496 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
bgneal@45 9497
bgneal@45 9498 bi = s.body_id || 'tinymce';
bgneal@45 9499 if (bi.indexOf('=') != -1) {
bgneal@45 9500 bi = t.getParam('body_id', '', 'hash');
bgneal@45 9501 bi = bi[t.id] || bi;
bgneal@45 9502 }
bgneal@45 9503
bgneal@45 9504 bc = s.body_class || '';
bgneal@45 9505 if (bc.indexOf('=') != -1) {
bgneal@45 9506 bc = t.getParam('body_class', '', 'hash');
bgneal@45 9507 bc = bc[t.id] || '';
bgneal@45 9508 }
bgneal@45 9509
bgneal@45 9510 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
bgneal@45 9511
bgneal@45 9512 // Domain relaxing enabled, then set document domain
bgneal@45 9513 if (tinymce.relaxedDomain) {
bgneal@45 9514 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
bgneal@45 9515 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
bgneal@45 9516 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 9517 else if (tinymce.isOpera)
bgneal@45 9518 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
bgneal@45 9519 }
bgneal@45 9520
bgneal@45 9521 // Create iframe
bgneal@45 9522 n = DOM.add(o.iframeContainer, 'iframe', {
bgneal@45 9523 id : t.id + "_ifr",
bgneal@45 9524 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
bgneal@45 9525 frameBorder : '0',
bgneal@45 9526 style : {
bgneal@45 9527 width : '100%',
bgneal@45 9528 height : h
bgneal@45 9529 }
bgneal@45 9530 });
bgneal@45 9531
bgneal@45 9532 t.contentAreaContainer = o.iframeContainer;
bgneal@45 9533 DOM.get(o.editorContainer).style.display = t.orgDisplay;
bgneal@45 9534 DOM.get(t.id).style.display = 'none';
bgneal@45 9535
bgneal@45 9536 if (!isIE || !tinymce.relaxedDomain)
bgneal@45 9537 t.setupIframe();
bgneal@45 9538
bgneal@45 9539 e = n = o = null; // Cleanup
bgneal@45 9540 },
bgneal@45 9541
bgneal@45 9542 setupIframe : function() {
bgneal@45 9543 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
bgneal@45 9544
bgneal@45 9545 // Setup iframe body
bgneal@45 9546 if (!isIE || !tinymce.relaxedDomain) {
bgneal@45 9547 d.open();
bgneal@45 9548 d.write(t.iframeHTML);
bgneal@45 9549 d.close();
bgneal@45 9550 }
bgneal@45 9551
bgneal@45 9552 // Design mode needs to be added here Ctrl+A will fail otherwise
bgneal@45 9553 if (!isIE) {
bgneal@45 9554 try {
bgneal@45 9555 if (!s.readonly)
bgneal@45 9556 d.designMode = 'On';
bgneal@45 9557 } catch (ex) {
bgneal@45 9558 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@45 9559 // The design mode will be set ones the editor is focused
bgneal@45 9560 }
bgneal@45 9561 }
bgneal@45 9562
bgneal@45 9563 // IE needs to use contentEditable or it will display non secure items for HTTPS
bgneal@45 9564 if (isIE) {
bgneal@45 9565 // It will not steal focus if we hide it while setting contentEditable
bgneal@45 9566 b = t.getBody();
bgneal@45 9567 DOM.hide(b);
bgneal@45 9568
bgneal@45 9569 if (!s.readonly)
bgneal@45 9570 b.contentEditable = true;
bgneal@45 9571
bgneal@45 9572 DOM.show(b);
bgneal@45 9573 }
bgneal@45 9574
bgneal@183 9575 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
bgneal@45 9576 keep_values : true,
bgneal@45 9577 url_converter : t.convertURL,
bgneal@45 9578 url_converter_scope : t,
bgneal@45 9579 hex_colors : s.force_hex_style_colors,
bgneal@45 9580 class_filter : s.class_filter,
bgneal@45 9581 update_styles : 1,
bgneal@183 9582 fix_ie_paragraphs : 1,
bgneal@183 9583 valid_styles : s.valid_styles
bgneal@183 9584 });
bgneal@183 9585
bgneal@183 9586 t.schema = new tinymce.dom.Schema();
bgneal@183 9587
bgneal@183 9588 t.serializer = new tinymce.dom.Serializer(extend(s, {
bgneal@45 9589 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
bgneal@183 9590 dom : t.dom,
bgneal@183 9591 schema : t.schema
bgneal@183 9592 }));
bgneal@45 9593
bgneal@45 9594 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
bgneal@183 9595
bgneal@183 9596 t.formatter = new tinymce.Formatter(this);
bgneal@183 9597
bgneal@183 9598 // Register default formats
bgneal@183 9599 t.formatter.register({
bgneal@183 9600 alignleft : [
bgneal@183 9601 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
bgneal@183 9602 {selector : 'img,table', styles : {'float' : 'left'}}
bgneal@183 9603 ],
bgneal@183 9604
bgneal@183 9605 aligncenter : [
bgneal@183 9606 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
bgneal@183 9607 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
bgneal@183 9608 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
bgneal@183 9609 ],
bgneal@183 9610
bgneal@183 9611 alignright : [
bgneal@183 9612 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
bgneal@183 9613 {selector : 'img,table', styles : {'float' : 'right'}}
bgneal@183 9614 ],
bgneal@183 9615
bgneal@183 9616 alignfull : [
bgneal@183 9617 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
bgneal@183 9618 ],
bgneal@183 9619
bgneal@183 9620 bold : [
bgneal@183 9621 {inline : 'strong'},
bgneal@183 9622 {inline : 'span', styles : {fontWeight : 'bold'}},
bgneal@183 9623 {inline : 'b'}
bgneal@183 9624 ],
bgneal@183 9625
bgneal@183 9626 italic : [
bgneal@183 9627 {inline : 'em'},
bgneal@183 9628 {inline : 'span', styles : {fontStyle : 'italic'}},
bgneal@183 9629 {inline : 'i'}
bgneal@183 9630 ],
bgneal@183 9631
bgneal@183 9632 underline : [
bgneal@183 9633 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
bgneal@183 9634 {inline : 'u'}
bgneal@183 9635 ],
bgneal@183 9636
bgneal@183 9637 strikethrough : [
bgneal@183 9638 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
bgneal@183 9639 {inline : 'u'}
bgneal@183 9640 ],
bgneal@183 9641
bgneal@183 9642 forecolor : {inline : 'span', styles : {color : '%value'}},
bgneal@183 9643 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
bgneal@183 9644 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
bgneal@183 9645 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
bgneal@217 9646 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
bgneal@183 9647 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
bgneal@183 9648
bgneal@183 9649 removeformat : [
bgneal@183 9650 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
bgneal@183 9651 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
bgneal@183 9652 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
bgneal@183 9653 ]
bgneal@183 9654 });
bgneal@183 9655
bgneal@183 9656 // Register default block formats
bgneal@183 9657 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
bgneal@183 9658 t.formatter.register(name, {block : name, remove : 'all'});
bgneal@183 9659 });
bgneal@183 9660
bgneal@183 9661 // Register user defined formats
bgneal@183 9662 t.formatter.register(t.settings.formats);
bgneal@183 9663
bgneal@183 9664 t.undoManager = new tinymce.UndoManager(t);
bgneal@183 9665
bgneal@183 9666 // Pass through
bgneal@183 9667 t.undoManager.onAdd.add(function(um, l) {
bgneal@183 9668 if (!l.initial)
bgneal@183 9669 return t.onChange.dispatch(t, l, um);
bgneal@183 9670 });
bgneal@183 9671
bgneal@183 9672 t.undoManager.onUndo.add(function(um, l) {
bgneal@183 9673 return t.onUndo.dispatch(t, l, um);
bgneal@183 9674 });
bgneal@183 9675
bgneal@183 9676 t.undoManager.onRedo.add(function(um, l) {
bgneal@183 9677 return t.onRedo.dispatch(t, l, um);
bgneal@183 9678 });
bgneal@183 9679
bgneal@45 9680 t.forceBlocks = new tinymce.ForceBlocks(t, {
bgneal@45 9681 forced_root_block : s.forced_root_block
bgneal@45 9682 });
bgneal@183 9683
bgneal@45 9684 t.editorCommands = new tinymce.EditorCommands(t);
bgneal@45 9685
bgneal@45 9686 // Pass through
bgneal@45 9687 t.serializer.onPreProcess.add(function(se, o) {
bgneal@45 9688 return t.onPreProcess.dispatch(t, o, se);
bgneal@45 9689 });
bgneal@45 9690
bgneal@45 9691 t.serializer.onPostProcess.add(function(se, o) {
bgneal@45 9692 return t.onPostProcess.dispatch(t, o, se);
bgneal@45 9693 });
bgneal@45 9694
bgneal@45 9695 t.onPreInit.dispatch(t);
bgneal@45 9696
bgneal@45 9697 if (!s.gecko_spellcheck)
bgneal@45 9698 t.getBody().spellcheck = 0;
bgneal@45 9699
bgneal@45 9700 if (!s.readonly)
bgneal@45 9701 t._addEvents();
bgneal@45 9702
bgneal@45 9703 t.controlManager.onPostRender.dispatch(t, t.controlManager);
bgneal@45 9704 t.onPostRender.dispatch(t);
bgneal@45 9705
bgneal@45 9706 if (s.directionality)
bgneal@45 9707 t.getBody().dir = s.directionality;
bgneal@45 9708
bgneal@45 9709 if (s.nowrap)
bgneal@45 9710 t.getBody().style.whiteSpace = "nowrap";
bgneal@45 9711
bgneal@45 9712 if (s.custom_elements) {
bgneal@45 9713 function handleCustom(ed, o) {
bgneal@45 9714 each(explode(s.custom_elements), function(v) {
bgneal@45 9715 var n;
bgneal@45 9716
bgneal@45 9717 if (v.indexOf('~') === 0) {
bgneal@45 9718 v = v.substring(1);
bgneal@45 9719 n = 'span';
bgneal@45 9720 } else
bgneal@45 9721 n = 'div';
bgneal@45 9722
bgneal@183 9723 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
bgneal@45 9724 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
bgneal@45 9725 });
bgneal@45 9726 };
bgneal@45 9727
bgneal@45 9728 t.onBeforeSetContent.add(handleCustom);
bgneal@45 9729 t.onPostProcess.add(function(ed, o) {
bgneal@45 9730 if (o.set)
bgneal@183 9731 handleCustom(ed, o);
bgneal@45 9732 });
bgneal@45 9733 }
bgneal@45 9734
bgneal@45 9735 if (s.handle_node_change_callback) {
bgneal@45 9736 t.onNodeChange.add(function(ed, cm, n) {
bgneal@45 9737 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
bgneal@45 9738 });
bgneal@45 9739 }
bgneal@45 9740
bgneal@45 9741 if (s.save_callback) {
bgneal@45 9742 t.onSaveContent.add(function(ed, o) {
bgneal@45 9743 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 9744
bgneal@45 9745 if (h)
bgneal@45 9746 o.content = h;
bgneal@45 9747 });
bgneal@45 9748 }
bgneal@45 9749
bgneal@45 9750 if (s.onchange_callback) {
bgneal@45 9751 t.onChange.add(function(ed, l) {
bgneal@45 9752 t.execCallback('onchange_callback', t, l);
bgneal@45 9753 });
bgneal@45 9754 }
bgneal@45 9755
bgneal@45 9756 if (s.convert_newlines_to_brs) {
bgneal@45 9757 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9758 if (o.initial)
bgneal@45 9759 o.content = o.content.replace(/\r?\n/g, '<br />');
bgneal@45 9760 });
bgneal@45 9761 }
bgneal@45 9762
bgneal@45 9763 if (s.fix_nesting && isIE) {
bgneal@45 9764 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9765 o.content = t._fixNesting(o.content);
bgneal@45 9766 });
bgneal@45 9767 }
bgneal@45 9768
bgneal@45 9769 if (s.preformatted) {
bgneal@45 9770 t.onPostProcess.add(function(ed, o) {
bgneal@45 9771 o.content = o.content.replace(/^\s*<pre.*?>/, '');
bgneal@45 9772 o.content = o.content.replace(/<\/pre>\s*$/, '');
bgneal@45 9773
bgneal@45 9774 if (o.set)
bgneal@45 9775 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
bgneal@45 9776 });
bgneal@45 9777 }
bgneal@45 9778
bgneal@45 9779 if (s.verify_css_classes) {
bgneal@45 9780 t.serializer.attribValueFilter = function(n, v) {
bgneal@45 9781 var s, cl;
bgneal@45 9782
bgneal@45 9783 if (n == 'class') {
bgneal@45 9784 // Build regexp for classes
bgneal@45 9785 if (!t.classesRE) {
bgneal@45 9786 cl = t.dom.getClasses();
bgneal@45 9787
bgneal@45 9788 if (cl.length > 0) {
bgneal@45 9789 s = '';
bgneal@45 9790
bgneal@45 9791 each (cl, function(o) {
bgneal@45 9792 s += (s ? '|' : '') + o['class'];
bgneal@45 9793 });
bgneal@45 9794
bgneal@45 9795 t.classesRE = new RegExp('(' + s + ')', 'gi');
bgneal@45 9796 }
bgneal@45 9797 }
bgneal@45 9798
bgneal@45 9799 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
bgneal@45 9800 }
bgneal@45 9801
bgneal@45 9802 return v;
bgneal@45 9803 };
bgneal@45 9804 }
bgneal@45 9805
bgneal@45 9806 if (s.cleanup_callback) {
bgneal@45 9807 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9808 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 9809 });
bgneal@45 9810
bgneal@45 9811 t.onPreProcess.add(function(ed, o) {
bgneal@45 9812 if (o.set)
bgneal@45 9813 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
bgneal@45 9814
bgneal@45 9815 if (o.get)
bgneal@45 9816 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
bgneal@45 9817 });
bgneal@45 9818
bgneal@45 9819 t.onPostProcess.add(function(ed, o) {
bgneal@45 9820 if (o.set)
bgneal@45 9821 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 9822
bgneal@45 9823 if (o.get)
bgneal@45 9824 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
bgneal@45 9825 });
bgneal@45 9826 }
bgneal@45 9827
bgneal@45 9828 if (s.save_callback) {
bgneal@45 9829 t.onGetContent.add(function(ed, o) {
bgneal@45 9830 if (o.save)
bgneal@45 9831 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 9832 });
bgneal@45 9833 }
bgneal@45 9834
bgneal@45 9835 if (s.handle_event_callback) {
bgneal@45 9836 t.onEvent.add(function(ed, e, o) {
bgneal@45 9837 if (t.execCallback('handle_event_callback', e, ed, o) === false)
bgneal@45 9838 Event.cancel(e);
bgneal@45 9839 });
bgneal@45 9840 }
bgneal@45 9841
bgneal@45 9842 // Add visual aids when new contents is added
bgneal@45 9843 t.onSetContent.add(function() {
bgneal@45 9844 t.addVisual(t.getBody());
bgneal@45 9845 });
bgneal@45 9846
bgneal@45 9847 // Remove empty contents
bgneal@45 9848 if (s.padd_empty_editor) {
bgneal@45 9849 t.onPostProcess.add(function(ed, o) {
bgneal@45 9850 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
bgneal@45 9851 });
bgneal@45 9852 }
bgneal@45 9853
bgneal@45 9854 if (isGecko) {
bgneal@183 9855 // Fix gecko link bug, when a link is placed at the end of block elements there is
bgneal@183 9856 // no way to move the caret behind the link. This fix adds a bogus br element after the link
bgneal@45 9857 function fixLinks(ed, o) {
bgneal@45 9858 each(ed.dom.select('a'), function(n) {
bgneal@45 9859 var pn = n.parentNode;
bgneal@45 9860
bgneal@45 9861 if (ed.dom.isBlock(pn) && pn.lastChild === n)
bgneal@183 9862 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
bgneal@45 9863 });
bgneal@45 9864 };
bgneal@45 9865
bgneal@45 9866 t.onExecCommand.add(function(ed, cmd) {
bgneal@45 9867 if (cmd === 'CreateLink')
bgneal@45 9868 fixLinks(ed);
bgneal@45 9869 });
bgneal@45 9870
bgneal@45 9871 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
bgneal@183 9872
bgneal@183 9873 if (!s.readonly) {
bgneal@183 9874 try {
bgneal@183 9875 // Design mode must be set here once again to fix a bug where
bgneal@183 9876 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
bgneal@183 9877 d.designMode = 'Off';
bgneal@183 9878 d.designMode = 'On';
bgneal@183 9879 } catch (ex) {
bgneal@183 9880 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@183 9881 // The design mode will be set ones the editor is focused
bgneal@183 9882 }
bgneal@45 9883 }
bgneal@45 9884 }
bgneal@45 9885
bgneal@45 9886 // A small timeout was needed since firefox will remove. Bug: #1838304
bgneal@45 9887 setTimeout(function () {
bgneal@45 9888 if (t.removed)
bgneal@45 9889 return;
bgneal@45 9890
bgneal@45 9891 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
bgneal@45 9892 t.startContent = t.getContent({format : 'raw'});
bgneal@45 9893 t.initialized = true;
bgneal@45 9894
bgneal@45 9895 t.onInit.dispatch(t);
bgneal@45 9896 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
bgneal@45 9897 t.execCallback('init_instance_callback', t);
bgneal@45 9898 t.focus(true);
bgneal@45 9899 t.nodeChanged({initial : 1});
bgneal@45 9900
bgneal@45 9901 // Load specified content CSS last
bgneal@45 9902 if (s.content_css) {
bgneal@45 9903 tinymce.each(explode(s.content_css), function(u) {
bgneal@45 9904 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
bgneal@45 9905 });
bgneal@45 9906 }
bgneal@45 9907
bgneal@45 9908 // Handle auto focus
bgneal@45 9909 if (s.auto_focus) {
bgneal@45 9910 setTimeout(function () {
bgneal@183 9911 var ed = tinymce.get(s.auto_focus);
bgneal@45 9912
bgneal@45 9913 ed.selection.select(ed.getBody(), 1);
bgneal@45 9914 ed.selection.collapse(1);
bgneal@45 9915 ed.getWin().focus();
bgneal@45 9916 }, 100);
bgneal@45 9917 }
bgneal@45 9918 }, 1);
bgneal@45 9919
bgneal@45 9920 e = null;
bgneal@45 9921 },
bgneal@45 9922
bgneal@45 9923
bgneal@45 9924 focus : function(sf) {
bgneal@217 9925 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
bgneal@45 9926
bgneal@45 9927 if (!sf) {
bgneal@217 9928 // Get selected control element
bgneal@217 9929 ieRng = t.selection.getRng();
bgneal@217 9930 if (ieRng.item) {
bgneal@217 9931 controlElm = ieRng.item(0);
bgneal@217 9932 }
bgneal@217 9933
bgneal@183 9934 // Is not content editable
bgneal@183 9935 if (!ce)
bgneal@45 9936 t.getWin().focus();
bgneal@45 9937
bgneal@217 9938 // Restore selected control element
bgneal@217 9939 // This is needed when for example an image is selected within a
bgneal@217 9940 // layer a call to focus will then remove the control selection
bgneal@217 9941 if (controlElm && controlElm.ownerDocument == doc) {
bgneal@217 9942 ieRng = doc.body.createControlRange();
bgneal@217 9943 ieRng.addElement(controlElm);
bgneal@217 9944 ieRng.select();
bgneal@217 9945 }
bgneal@217 9946
bgneal@45 9947 }
bgneal@45 9948
bgneal@183 9949 if (tinymce.activeEditor != t) {
bgneal@183 9950 if ((oed = tinymce.activeEditor) != null)
bgneal@45 9951 oed.onDeactivate.dispatch(oed, t);
bgneal@45 9952
bgneal@45 9953 t.onActivate.dispatch(t, oed);
bgneal@45 9954 }
bgneal@45 9955
bgneal@183 9956 tinymce._setActive(t);
bgneal@45 9957 },
bgneal@45 9958
bgneal@45 9959 execCallback : function(n) {
bgneal@45 9960 var t = this, f = t.settings[n], s;
bgneal@45 9961
bgneal@45 9962 if (!f)
bgneal@45 9963 return;
bgneal@45 9964
bgneal@45 9965 // Look through lookup
bgneal@45 9966 if (t.callbackLookup && (s = t.callbackLookup[n])) {
bgneal@45 9967 f = s.func;
bgneal@45 9968 s = s.scope;
bgneal@45 9969 }
bgneal@45 9970
bgneal@45 9971 if (is(f, 'string')) {
bgneal@45 9972 s = f.replace(/\.\w+$/, '');
bgneal@45 9973 s = s ? tinymce.resolve(s) : 0;
bgneal@45 9974 f = tinymce.resolve(f);
bgneal@45 9975 t.callbackLookup = t.callbackLookup || {};
bgneal@45 9976 t.callbackLookup[n] = {func : f, scope : s};
bgneal@45 9977 }
bgneal@45 9978
bgneal@45 9979 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
bgneal@45 9980 },
bgneal@45 9981
bgneal@45 9982 translate : function(s) {
bgneal@183 9983 var c = this.settings.language || 'en', i18n = tinymce.i18n;
bgneal@45 9984
bgneal@45 9985 if (!s)
bgneal@45 9986 return '';
bgneal@45 9987
bgneal@45 9988 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
bgneal@45 9989 return i18n[c + '.' + b] || '{#' + b + '}';
bgneal@45 9990 });
bgneal@45 9991 },
bgneal@45 9992
bgneal@45 9993 getLang : function(n, dv) {
bgneal@183 9994 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
bgneal@45 9995 },
bgneal@45 9996
bgneal@45 9997 getParam : function(n, dv, ty) {
bgneal@45 9998 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
bgneal@45 9999
bgneal@45 10000 if (ty === 'hash') {
bgneal@45 10001 o = {};
bgneal@45 10002
bgneal@45 10003 if (is(v, 'string')) {
bgneal@45 10004 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
bgneal@45 10005 v = v.split('=');
bgneal@45 10006
bgneal@45 10007 if (v.length > 1)
bgneal@45 10008 o[tr(v[0])] = tr(v[1]);
bgneal@45 10009 else
bgneal@45 10010 o[tr(v[0])] = tr(v);
bgneal@45 10011 });
bgneal@45 10012 } else
bgneal@45 10013 o = v;
bgneal@45 10014
bgneal@45 10015 return o;
bgneal@45 10016 }
bgneal@45 10017
bgneal@45 10018 return v;
bgneal@45 10019 },
bgneal@45 10020
bgneal@45 10021 nodeChanged : function(o) {
bgneal@183 10022 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
bgneal@45 10023
bgneal@45 10024 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
bgneal@45 10025 if (t.initialized) {
bgneal@183 10026 o = o || {};
bgneal@183 10027 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
bgneal@183 10028
bgneal@183 10029 // Get parents and add them to object
bgneal@183 10030 o.parents = [];
bgneal@183 10031 t.dom.getParent(n, function(node) {
bgneal@183 10032 if (node.nodeName == 'BODY')
bgneal@183 10033 return true;
bgneal@183 10034
bgneal@183 10035 o.parents.push(node);
bgneal@183 10036 });
bgneal@183 10037
bgneal@45 10038 t.onNodeChange.dispatch(
bgneal@45 10039 t,
bgneal@45 10040 o ? o.controlManager || t.controlManager : t.controlManager,
bgneal@183 10041 n,
bgneal@45 10042 s.isCollapsed(),
bgneal@45 10043 o
bgneal@45 10044 );
bgneal@45 10045 }
bgneal@45 10046 },
bgneal@45 10047
bgneal@45 10048 addButton : function(n, s) {
bgneal@45 10049 var t = this;
bgneal@45 10050
bgneal@45 10051 t.buttons = t.buttons || {};
bgneal@45 10052 t.buttons[n] = s;
bgneal@45 10053 },
bgneal@45 10054
bgneal@45 10055 addCommand : function(n, f, s) {
bgneal@45 10056 this.execCommands[n] = {func : f, scope : s || this};
bgneal@45 10057 },
bgneal@45 10058
bgneal@45 10059 addQueryStateHandler : function(n, f, s) {
bgneal@45 10060 this.queryStateCommands[n] = {func : f, scope : s || this};
bgneal@45 10061 },
bgneal@45 10062
bgneal@45 10063 addQueryValueHandler : function(n, f, s) {
bgneal@45 10064 this.queryValueCommands[n] = {func : f, scope : s || this};
bgneal@45 10065 },
bgneal@45 10066
bgneal@45 10067 addShortcut : function(pa, desc, cmd_func, sc) {
bgneal@45 10068 var t = this, c;
bgneal@45 10069
bgneal@45 10070 if (!t.settings.custom_shortcuts)
bgneal@45 10071 return false;
bgneal@45 10072
bgneal@45 10073 t.shortcuts = t.shortcuts || {};
bgneal@45 10074
bgneal@45 10075 if (is(cmd_func, 'string')) {
bgneal@45 10076 c = cmd_func;
bgneal@45 10077
bgneal@45 10078 cmd_func = function() {
bgneal@45 10079 t.execCommand(c, false, null);
bgneal@45 10080 };
bgneal@45 10081 }
bgneal@45 10082
bgneal@45 10083 if (is(cmd_func, 'object')) {
bgneal@45 10084 c = cmd_func;
bgneal@45 10085
bgneal@45 10086 cmd_func = function() {
bgneal@45 10087 t.execCommand(c[0], c[1], c[2]);
bgneal@45 10088 };
bgneal@45 10089 }
bgneal@45 10090
bgneal@45 10091 each(explode(pa), function(pa) {
bgneal@45 10092 var o = {
bgneal@45 10093 func : cmd_func,
bgneal@45 10094 scope : sc || this,
bgneal@45 10095 desc : desc,
bgneal@45 10096 alt : false,
bgneal@45 10097 ctrl : false,
bgneal@45 10098 shift : false
bgneal@45 10099 };
bgneal@45 10100
bgneal@45 10101 each(explode(pa, '+'), function(v) {
bgneal@45 10102 switch (v) {
bgneal@45 10103 case 'alt':
bgneal@45 10104 case 'ctrl':
bgneal@45 10105 case 'shift':
bgneal@45 10106 o[v] = true;
bgneal@45 10107 break;
bgneal@45 10108
bgneal@45 10109 default:
bgneal@45 10110 o.charCode = v.charCodeAt(0);
bgneal@45 10111 o.keyCode = v.toUpperCase().charCodeAt(0);
bgneal@45 10112 }
bgneal@45 10113 });
bgneal@45 10114
bgneal@45 10115 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
bgneal@45 10116 });
bgneal@45 10117
bgneal@45 10118 return true;
bgneal@45 10119 },
bgneal@45 10120
bgneal@45 10121 execCommand : function(cmd, ui, val, a) {
bgneal@45 10122 var t = this, s = 0, o, st;
bgneal@45 10123
bgneal@45 10124 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
bgneal@45 10125 t.focus();
bgneal@45 10126
bgneal@45 10127 o = {};
bgneal@45 10128 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
bgneal@45 10129 if (o.terminate)
bgneal@45 10130 return false;
bgneal@45 10131
bgneal@45 10132 // Command callback
bgneal@45 10133 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
bgneal@45 10134 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10135 return true;
bgneal@45 10136 }
bgneal@45 10137
bgneal@45 10138 // Registred commands
bgneal@45 10139 if (o = t.execCommands[cmd]) {
bgneal@45 10140 st = o.func.call(o.scope, ui, val);
bgneal@45 10141
bgneal@45 10142 // Fall through on true
bgneal@45 10143 if (st !== true) {
bgneal@45 10144 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10145 return st;
bgneal@45 10146 }
bgneal@45 10147 }
bgneal@45 10148
bgneal@45 10149 // Plugin commands
bgneal@45 10150 each(t.plugins, function(p) {
bgneal@45 10151 if (p.execCommand && p.execCommand(cmd, ui, val)) {
bgneal@45 10152 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10153 s = 1;
bgneal@45 10154 return false;
bgneal@45 10155 }
bgneal@45 10156 });
bgneal@45 10157
bgneal@45 10158 if (s)
bgneal@45 10159 return true;
bgneal@45 10160
bgneal@45 10161 // Theme commands
bgneal@45 10162 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
bgneal@45 10163 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10164 return true;
bgneal@45 10165 }
bgneal@45 10166
bgneal@45 10167 // Execute global commands
bgneal@45 10168 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
bgneal@45 10169 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10170 return true;
bgneal@45 10171 }
bgneal@45 10172
bgneal@45 10173 // Editor commands
bgneal@45 10174 if (t.editorCommands.execCommand(cmd, ui, val)) {
bgneal@45 10175 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10176 return true;
bgneal@45 10177 }
bgneal@45 10178
bgneal@45 10179 // Browser commands
bgneal@45 10180 t.getDoc().execCommand(cmd, ui, val);
bgneal@45 10181 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10182 },
bgneal@45 10183
bgneal@183 10184 queryCommandState : function(cmd) {
bgneal@45 10185 var t = this, o, s;
bgneal@45 10186
bgneal@45 10187 // Is hidden then return undefined
bgneal@45 10188 if (t._isHidden())
bgneal@45 10189 return;
bgneal@45 10190
bgneal@45 10191 // Registred commands
bgneal@183 10192 if (o = t.queryStateCommands[cmd]) {
bgneal@45 10193 s = o.func.call(o.scope);
bgneal@45 10194
bgneal@45 10195 // Fall though on true
bgneal@45 10196 if (s !== true)
bgneal@45 10197 return s;
bgneal@45 10198 }
bgneal@45 10199
bgneal@45 10200 // Registred commands
bgneal@183 10201 o = t.editorCommands.queryCommandState(cmd);
bgneal@45 10202 if (o !== -1)
bgneal@45 10203 return o;
bgneal@45 10204
bgneal@45 10205 // Browser commands
bgneal@45 10206 try {
bgneal@183 10207 return this.getDoc().queryCommandState(cmd);
bgneal@45 10208 } catch (ex) {
bgneal@45 10209 // Fails sometimes see bug: 1896577
bgneal@45 10210 }
bgneal@45 10211 },
bgneal@45 10212
bgneal@45 10213 queryCommandValue : function(c) {
bgneal@45 10214 var t = this, o, s;
bgneal@45 10215
bgneal@45 10216 // Is hidden then return undefined
bgneal@45 10217 if (t._isHidden())
bgneal@45 10218 return;
bgneal@45 10219
bgneal@45 10220 // Registred commands
bgneal@45 10221 if (o = t.queryValueCommands[c]) {
bgneal@45 10222 s = o.func.call(o.scope);
bgneal@45 10223
bgneal@45 10224 // Fall though on true
bgneal@45 10225 if (s !== true)
bgneal@45 10226 return s;
bgneal@45 10227 }
bgneal@45 10228
bgneal@45 10229 // Registred commands
bgneal@45 10230 o = t.editorCommands.queryCommandValue(c);
bgneal@45 10231 if (is(o))
bgneal@45 10232 return o;
bgneal@45 10233
bgneal@45 10234 // Browser commands
bgneal@45 10235 try {
bgneal@45 10236 return this.getDoc().queryCommandValue(c);
bgneal@45 10237 } catch (ex) {
bgneal@45 10238 // Fails sometimes see bug: 1896577
bgneal@45 10239 }
bgneal@45 10240 },
bgneal@45 10241
bgneal@45 10242 show : function() {
bgneal@45 10243 var t = this;
bgneal@45 10244
bgneal@45 10245 DOM.show(t.getContainer());
bgneal@45 10246 DOM.hide(t.id);
bgneal@45 10247 t.load();
bgneal@45 10248 },
bgneal@45 10249
bgneal@45 10250 hide : function() {
bgneal@45 10251 var t = this, d = t.getDoc();
bgneal@45 10252
bgneal@45 10253 // Fixed bug where IE has a blinking cursor left from the editor
bgneal@45 10254 if (isIE && d)
bgneal@45 10255 d.execCommand('SelectAll');
bgneal@45 10256
bgneal@45 10257 // We must save before we hide so Safari doesn't crash
bgneal@45 10258 t.save();
bgneal@45 10259 DOM.hide(t.getContainer());
bgneal@45 10260 DOM.setStyle(t.id, 'display', t.orgDisplay);
bgneal@45 10261 },
bgneal@45 10262
bgneal@45 10263 isHidden : function() {
bgneal@45 10264 return !DOM.isHidden(this.id);
bgneal@45 10265 },
bgneal@45 10266
bgneal@45 10267 setProgressState : function(b, ti, o) {
bgneal@45 10268 this.onSetProgressState.dispatch(this, b, ti, o);
bgneal@45 10269
bgneal@45 10270 return b;
bgneal@45 10271 },
bgneal@45 10272
bgneal@45 10273 load : function(o) {
bgneal@45 10274 var t = this, e = t.getElement(), h;
bgneal@45 10275
bgneal@45 10276 if (e) {
bgneal@45 10277 o = o || {};
bgneal@45 10278 o.load = true;
bgneal@45 10279
bgneal@45 10280 // Double encode existing entities in the value
bgneal@45 10281 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
bgneal@45 10282 o.element = e;
bgneal@45 10283
bgneal@45 10284 if (!o.no_events)
bgneal@45 10285 t.onLoadContent.dispatch(t, o);
bgneal@45 10286
bgneal@45 10287 o.element = e = null;
bgneal@45 10288
bgneal@45 10289 return h;
bgneal@45 10290 }
bgneal@45 10291 },
bgneal@45 10292
bgneal@45 10293 save : function(o) {
bgneal@45 10294 var t = this, e = t.getElement(), h, f;
bgneal@45 10295
bgneal@45 10296 if (!e || !t.initialized)
bgneal@45 10297 return;
bgneal@45 10298
bgneal@45 10299 o = o || {};
bgneal@45 10300 o.save = true;
bgneal@45 10301
bgneal@45 10302 // Add undo level will trigger onchange event
bgneal@45 10303 if (!o.no_events) {
bgneal@45 10304 t.undoManager.typing = 0;
bgneal@45 10305 t.undoManager.add();
bgneal@45 10306 }
bgneal@45 10307
bgneal@45 10308 o.element = e;
bgneal@45 10309 h = o.content = t.getContent(o);
bgneal@45 10310
bgneal@45 10311 if (!o.no_events)
bgneal@45 10312 t.onSaveContent.dispatch(t, o);
bgneal@45 10313
bgneal@45 10314 h = o.content;
bgneal@45 10315
bgneal@45 10316 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
bgneal@45 10317 e.innerHTML = h;
bgneal@45 10318
bgneal@45 10319 // Update hidden form element
bgneal@45 10320 if (f = DOM.getParent(t.id, 'form')) {
bgneal@45 10321 each(f.elements, function(e) {
bgneal@45 10322 if (e.name == t.id) {
bgneal@45 10323 e.value = h;
bgneal@45 10324 return false;
bgneal@45 10325 }
bgneal@45 10326 });
bgneal@45 10327 }
bgneal@45 10328 } else
bgneal@45 10329 e.value = h;
bgneal@45 10330
bgneal@45 10331 o.element = e = null;
bgneal@45 10332
bgneal@45 10333 return h;
bgneal@45 10334 },
bgneal@45 10335
bgneal@45 10336 setContent : function(h, o) {
bgneal@45 10337 var t = this;
bgneal@45 10338
bgneal@45 10339 o = o || {};
bgneal@45 10340 o.format = o.format || 'html';
bgneal@45 10341 o.set = true;
bgneal@45 10342 o.content = h;
bgneal@45 10343
bgneal@45 10344 if (!o.no_events)
bgneal@45 10345 t.onBeforeSetContent.dispatch(t, o);
bgneal@45 10346
bgneal@45 10347 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
bgneal@45 10348 // It will also be impossible to place the caret in the editor unless there is a BR element present
bgneal@45 10349 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
bgneal@183 10350 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
bgneal@45 10351 o.format = 'raw';
bgneal@45 10352 }
bgneal@45 10353
bgneal@45 10354 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
bgneal@45 10355
bgneal@45 10356 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@45 10357 o.getInner = true;
bgneal@45 10358 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
bgneal@45 10359 }
bgneal@45 10360
bgneal@45 10361 if (!o.no_events)
bgneal@45 10362 t.onSetContent.dispatch(t, o);
bgneal@45 10363
bgneal@45 10364 return o.content;
bgneal@45 10365 },
bgneal@45 10366
bgneal@45 10367 getContent : function(o) {
bgneal@45 10368 var t = this, h;
bgneal@45 10369
bgneal@45 10370 o = o || {};
bgneal@45 10371 o.format = o.format || 'html';
bgneal@45 10372 o.get = true;
bgneal@45 10373
bgneal@45 10374 if (!o.no_events)
bgneal@45 10375 t.onBeforeGetContent.dispatch(t, o);
bgneal@45 10376
bgneal@45 10377 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@45 10378 o.getInner = true;
bgneal@45 10379 h = t.serializer.serialize(t.getBody(), o);
bgneal@45 10380 } else
bgneal@45 10381 h = t.getBody().innerHTML;
bgneal@45 10382
bgneal@45 10383 h = h.replace(/^\s*|\s*$/g, '');
bgneal@45 10384 o.content = h;
bgneal@45 10385
bgneal@45 10386 if (!o.no_events)
bgneal@45 10387 t.onGetContent.dispatch(t, o);
bgneal@45 10388
bgneal@45 10389 return o.content;
bgneal@45 10390 },
bgneal@45 10391
bgneal@45 10392 isDirty : function() {
bgneal@45 10393 var t = this;
bgneal@45 10394
bgneal@45 10395 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
bgneal@45 10396 },
bgneal@45 10397
bgneal@45 10398 getContainer : function() {
bgneal@45 10399 var t = this;
bgneal@45 10400
bgneal@45 10401 if (!t.container)
bgneal@45 10402 t.container = DOM.get(t.editorContainer || t.id + '_parent');
bgneal@45 10403
bgneal@45 10404 return t.container;
bgneal@45 10405 },
bgneal@45 10406
bgneal@45 10407 getContentAreaContainer : function() {
bgneal@45 10408 return this.contentAreaContainer;
bgneal@45 10409 },
bgneal@45 10410
bgneal@45 10411 getElement : function() {
bgneal@45 10412 return DOM.get(this.settings.content_element || this.id);
bgneal@45 10413 },
bgneal@45 10414
bgneal@45 10415 getWin : function() {
bgneal@45 10416 var t = this, e;
bgneal@45 10417
bgneal@45 10418 if (!t.contentWindow) {
bgneal@45 10419 e = DOM.get(t.id + "_ifr");
bgneal@45 10420
bgneal@45 10421 if (e)
bgneal@45 10422 t.contentWindow = e.contentWindow;
bgneal@45 10423 }
bgneal@45 10424
bgneal@45 10425 return t.contentWindow;
bgneal@45 10426 },
bgneal@45 10427
bgneal@45 10428 getDoc : function() {
bgneal@45 10429 var t = this, w;
bgneal@45 10430
bgneal@45 10431 if (!t.contentDocument) {
bgneal@45 10432 w = t.getWin();
bgneal@45 10433
bgneal@45 10434 if (w)
bgneal@45 10435 t.contentDocument = w.document;
bgneal@45 10436 }
bgneal@45 10437
bgneal@45 10438 return t.contentDocument;
bgneal@45 10439 },
bgneal@45 10440
bgneal@45 10441 getBody : function() {
bgneal@45 10442 return this.bodyElement || this.getDoc().body;
bgneal@45 10443 },
bgneal@45 10444
bgneal@45 10445 convertURL : function(u, n, e) {
bgneal@45 10446 var t = this, s = t.settings;
bgneal@45 10447
bgneal@45 10448 // Use callback instead
bgneal@45 10449 if (s.urlconverter_callback)
bgneal@45 10450 return t.execCallback('urlconverter_callback', u, e, true, n);
bgneal@45 10451
bgneal@45 10452 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
bgneal@45 10453 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
bgneal@45 10454 return u;
bgneal@45 10455
bgneal@45 10456 // Convert to relative
bgneal@45 10457 if (s.relative_urls)
bgneal@45 10458 return t.documentBaseURI.toRelative(u);
bgneal@45 10459
bgneal@45 10460 // Convert to absolute
bgneal@45 10461 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
bgneal@45 10462
bgneal@45 10463 return u;
bgneal@45 10464 },
bgneal@45 10465
bgneal@45 10466 addVisual : function(e) {
bgneal@45 10467 var t = this, s = t.settings;
bgneal@45 10468
bgneal@45 10469 e = e || t.getBody();
bgneal@45 10470
bgneal@45 10471 if (!is(t.hasVisual))
bgneal@45 10472 t.hasVisual = s.visual;
bgneal@45 10473
bgneal@45 10474 each(t.dom.select('table,a', e), function(e) {
bgneal@45 10475 var v;
bgneal@45 10476
bgneal@45 10477 switch (e.nodeName) {
bgneal@45 10478 case 'TABLE':
bgneal@45 10479 v = t.dom.getAttrib(e, 'border');
bgneal@45 10480
bgneal@45 10481 if (!v || v == '0') {
bgneal@45 10482 if (t.hasVisual)
bgneal@45 10483 t.dom.addClass(e, s.visual_table_class);
bgneal@45 10484 else
bgneal@45 10485 t.dom.removeClass(e, s.visual_table_class);
bgneal@45 10486 }
bgneal@45 10487
bgneal@45 10488 return;
bgneal@45 10489
bgneal@45 10490 case 'A':
bgneal@45 10491 v = t.dom.getAttrib(e, 'name');
bgneal@45 10492
bgneal@45 10493 if (v) {
bgneal@45 10494 if (t.hasVisual)
bgneal@45 10495 t.dom.addClass(e, 'mceItemAnchor');
bgneal@45 10496 else
bgneal@45 10497 t.dom.removeClass(e, 'mceItemAnchor');
bgneal@45 10498 }
bgneal@45 10499
bgneal@45 10500 return;
bgneal@45 10501 }
bgneal@45 10502 });
bgneal@45 10503
bgneal@45 10504 t.onVisualAid.dispatch(t, e, t.hasVisual);
bgneal@45 10505 },
bgneal@45 10506
bgneal@45 10507 remove : function() {
bgneal@45 10508 var t = this, e = t.getContainer();
bgneal@45 10509
bgneal@45 10510 t.removed = 1; // Cancels post remove event execution
bgneal@45 10511 t.hide();
bgneal@45 10512
bgneal@45 10513 t.execCallback('remove_instance_callback', t);
bgneal@45 10514 t.onRemove.dispatch(t);
bgneal@45 10515
bgneal@45 10516 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
bgneal@45 10517 t.onExecCommand.listeners = [];
bgneal@45 10518
bgneal@183 10519 tinymce.remove(t);
bgneal@45 10520 DOM.remove(e);
bgneal@45 10521 },
bgneal@45 10522
bgneal@45 10523 destroy : function(s) {
bgneal@45 10524 var t = this;
bgneal@45 10525
bgneal@45 10526 // One time is enough
bgneal@45 10527 if (t.destroyed)
bgneal@45 10528 return;
bgneal@45 10529
bgneal@45 10530 if (!s) {
bgneal@45 10531 tinymce.removeUnload(t.destroy);
bgneal@45 10532 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
bgneal@45 10533
bgneal@45 10534 // Manual destroy
bgneal@45 10535 if (t.theme && t.theme.destroy)
bgneal@45 10536 t.theme.destroy();
bgneal@45 10537
bgneal@45 10538 // Destroy controls, selection and dom
bgneal@45 10539 t.controlManager.destroy();
bgneal@45 10540 t.selection.destroy();
bgneal@45 10541 t.dom.destroy();
bgneal@45 10542
bgneal@45 10543 // Remove all events
bgneal@45 10544
bgneal@45 10545 // Don't clear the window or document if content editable
bgneal@45 10546 // is enabled since other instances might still be present
bgneal@45 10547 if (!t.settings.content_editable) {
bgneal@45 10548 Event.clear(t.getWin());
bgneal@45 10549 Event.clear(t.getDoc());
bgneal@45 10550 }
bgneal@45 10551
bgneal@45 10552 Event.clear(t.getBody());
bgneal@45 10553 Event.clear(t.formElement);
bgneal@45 10554 }
bgneal@45 10555
bgneal@45 10556 if (t.formElement) {
bgneal@45 10557 t.formElement.submit = t.formElement._mceOldSubmit;
bgneal@45 10558 t.formElement._mceOldSubmit = null;
bgneal@45 10559 }
bgneal@45 10560
bgneal@45 10561 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
bgneal@45 10562
bgneal@45 10563 if (t.selection)
bgneal@45 10564 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
bgneal@45 10565
bgneal@45 10566 t.destroyed = 1;
bgneal@45 10567 },
bgneal@45 10568
bgneal@45 10569 // Internal functions
bgneal@45 10570
bgneal@45 10571 _addEvents : function() {
bgneal@45 10572 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
bgneal@45 10573 var t = this, i, s = t.settings, lo = {
bgneal@45 10574 mouseup : 'onMouseUp',
bgneal@45 10575 mousedown : 'onMouseDown',
bgneal@45 10576 click : 'onClick',
bgneal@45 10577 keyup : 'onKeyUp',
bgneal@45 10578 keydown : 'onKeyDown',
bgneal@45 10579 keypress : 'onKeyPress',
bgneal@45 10580 submit : 'onSubmit',
bgneal@45 10581 reset : 'onReset',
bgneal@45 10582 contextmenu : 'onContextMenu',
bgneal@45 10583 dblclick : 'onDblClick',
bgneal@45 10584 paste : 'onPaste' // Doesn't work in all browsers yet
bgneal@45 10585 };
bgneal@45 10586
bgneal@45 10587 function eventHandler(e, o) {
bgneal@45 10588 var ty = e.type;
bgneal@45 10589
bgneal@45 10590 // Don't fire events when it's removed
bgneal@45 10591 if (t.removed)
bgneal@45 10592 return;
bgneal@45 10593
bgneal@45 10594 // Generic event handler
bgneal@45 10595 if (t.onEvent.dispatch(t, e, o) !== false) {
bgneal@45 10596 // Specific event handler
bgneal@45 10597 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
bgneal@45 10598 }
bgneal@45 10599 };
bgneal@45 10600
bgneal@45 10601 // Add DOM events
bgneal@45 10602 each(lo, function(v, k) {
bgneal@45 10603 switch (k) {
bgneal@45 10604 case 'contextmenu':
bgneal@45 10605 if (tinymce.isOpera) {
bgneal@45 10606 // Fake contextmenu on Opera
bgneal@183 10607 t.dom.bind(t.getBody(), 'mousedown', function(e) {
bgneal@45 10608 if (e.ctrlKey) {
bgneal@45 10609 e.fakeType = 'contextmenu';
bgneal@45 10610 eventHandler(e);
bgneal@45 10611 }
bgneal@45 10612 });
bgneal@45 10613 } else
bgneal@183 10614 t.dom.bind(t.getBody(), k, eventHandler);
bgneal@45 10615 break;
bgneal@45 10616
bgneal@45 10617 case 'paste':
bgneal@183 10618 t.dom.bind(t.getBody(), k, function(e) {
bgneal@183 10619 eventHandler(e);
bgneal@45 10620 });
bgneal@45 10621 break;
bgneal@45 10622
bgneal@45 10623 case 'submit':
bgneal@45 10624 case 'reset':
bgneal@183 10625 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
bgneal@45 10626 break;
bgneal@45 10627
bgneal@45 10628 default:
bgneal@183 10629 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
bgneal@183 10630 }
bgneal@183 10631 });
bgneal@183 10632
bgneal@183 10633 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
bgneal@45 10634 t.focus(true);
bgneal@45 10635 });
bgneal@45 10636
bgneal@45 10637
bgneal@45 10638 // Fixes bug where a specified document_base_uri could result in broken images
bgneal@45 10639 // This will also fix drag drop of images in Gecko
bgneal@45 10640 if (tinymce.isGecko) {
bgneal@45 10641 // Convert all images to absolute URLs
bgneal@45 10642 /* t.onSetContent.add(function(ed, o) {
bgneal@45 10643 each(ed.dom.select('img'), function(e) {
bgneal@45 10644 var v;
bgneal@45 10645
bgneal@183 10646 if (v = e.getAttribute('_mce_src'))
bgneal@45 10647 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@45 10648 })
bgneal@45 10649 });*/
bgneal@45 10650
bgneal@183 10651 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
bgneal@45 10652 var v;
bgneal@45 10653
bgneal@45 10654 e = e.target;
bgneal@45 10655
bgneal@183 10656 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
bgneal@45 10657 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@45 10658 });
bgneal@45 10659 }
bgneal@45 10660
bgneal@45 10661 // Set various midas options in Gecko
bgneal@45 10662 if (isGecko) {
bgneal@45 10663 function setOpts() {
bgneal@45 10664 var t = this, d = t.getDoc(), s = t.settings;
bgneal@45 10665
bgneal@45 10666 if (isGecko && !s.readonly) {
bgneal@45 10667 if (t._isHidden()) {
bgneal@45 10668 try {
bgneal@45 10669 if (!s.content_editable)
bgneal@45 10670 d.designMode = 'On';
bgneal@45 10671 } catch (ex) {
bgneal@45 10672 // Fails if it's hidden
bgneal@45 10673 }
bgneal@45 10674 }
bgneal@45 10675
bgneal@45 10676 try {
bgneal@45 10677 // Try new Gecko method
bgneal@45 10678 d.execCommand("styleWithCSS", 0, false);
bgneal@45 10679 } catch (ex) {
bgneal@45 10680 // Use old method
bgneal@45 10681 if (!t._isHidden())
bgneal@45 10682 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
bgneal@45 10683 }
bgneal@45 10684
bgneal@45 10685 if (!s.table_inline_editing)
bgneal@45 10686 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
bgneal@45 10687
bgneal@45 10688 if (!s.object_resizing)
bgneal@45 10689 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
bgneal@45 10690 }
bgneal@45 10691 };
bgneal@45 10692
bgneal@45 10693 t.onBeforeExecCommand.add(setOpts);
bgneal@45 10694 t.onMouseDown.add(setOpts);
bgneal@45 10695 }
bgneal@45 10696
bgneal@183 10697 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
bgneal@183 10698 // WebKit can't even do simple things like selecting an image
bgneal@183 10699 // This also fixes so it's possible to select mceItemAnchors
bgneal@183 10700 if (tinymce.isWebKit) {
bgneal@183 10701 t.onClick.add(function(ed, e) {
bgneal@183 10702 e = e.target;
bgneal@183 10703
bgneal@183 10704 // Needs tobe the setBaseAndExtend or it will fail to select floated images
bgneal@183 10705 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
bgneal@183 10706 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
bgneal@183 10707 });
bgneal@183 10708 }
bgneal@183 10709
bgneal@45 10710 // Add node change handlers
bgneal@45 10711 t.onMouseUp.add(t.nodeChanged);
bgneal@247 10712 //t.onClick.add(t.nodeChanged);
bgneal@45 10713 t.onKeyUp.add(function(ed, e) {
bgneal@45 10714 var c = e.keyCode;
bgneal@45 10715
bgneal@45 10716 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 10717 t.nodeChanged();
bgneal@45 10718 });
bgneal@45 10719
bgneal@45 10720 // Add reset handler
bgneal@45 10721 t.onReset.add(function() {
bgneal@45 10722 t.setContent(t.startContent, {format : 'raw'});
bgneal@45 10723 });
bgneal@45 10724
bgneal@45 10725 // Add shortcuts
bgneal@45 10726 if (s.custom_shortcuts) {
bgneal@45 10727 if (s.custom_undo_redo_keyboard_shortcuts) {
bgneal@45 10728 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
bgneal@45 10729 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
bgneal@45 10730 }
bgneal@45 10731
bgneal@45 10732 // Add default shortcuts for gecko
bgneal@217 10733 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
bgneal@217 10734 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
bgneal@217 10735 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
bgneal@45 10736
bgneal@45 10737 // BlockFormat shortcuts keys
bgneal@45 10738 for (i=1; i<=6; i++)
bgneal@183 10739 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
bgneal@45 10740
bgneal@45 10741 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
bgneal@45 10742 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
bgneal@45 10743 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
bgneal@45 10744
bgneal@45 10745 function find(e) {
bgneal@45 10746 var v = null;
bgneal@45 10747
bgneal@45 10748 if (!e.altKey && !e.ctrlKey && !e.metaKey)
bgneal@45 10749 return v;
bgneal@45 10750
bgneal@45 10751 each(t.shortcuts, function(o) {
bgneal@45 10752 if (tinymce.isMac && o.ctrl != e.metaKey)
bgneal@45 10753 return;
bgneal@45 10754 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
bgneal@45 10755 return;
bgneal@45 10756
bgneal@45 10757 if (o.alt != e.altKey)
bgneal@45 10758 return;
bgneal@45 10759
bgneal@45 10760 if (o.shift != e.shiftKey)
bgneal@45 10761 return;
bgneal@45 10762
bgneal@45 10763 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
bgneal@45 10764 v = o;
bgneal@45 10765 return false;
bgneal@45 10766 }
bgneal@45 10767 });
bgneal@45 10768
bgneal@45 10769 return v;
bgneal@45 10770 };
bgneal@45 10771
bgneal@45 10772 t.onKeyUp.add(function(ed, e) {
bgneal@45 10773 var o = find(e);
bgneal@45 10774
bgneal@45 10775 if (o)
bgneal@45 10776 return Event.cancel(e);
bgneal@45 10777 });
bgneal@45 10778
bgneal@45 10779 t.onKeyPress.add(function(ed, e) {
bgneal@45 10780 var o = find(e);
bgneal@45 10781
bgneal@45 10782 if (o)
bgneal@45 10783 return Event.cancel(e);
bgneal@45 10784 });
bgneal@45 10785
bgneal@45 10786 t.onKeyDown.add(function(ed, e) {
bgneal@45 10787 var o = find(e);
bgneal@45 10788
bgneal@45 10789 if (o) {
bgneal@45 10790 o.func.call(o.scope);
bgneal@45 10791 return Event.cancel(e);
bgneal@45 10792 }
bgneal@45 10793 });
bgneal@45 10794 }
bgneal@45 10795
bgneal@45 10796 if (tinymce.isIE) {
bgneal@45 10797 // Fix so resize will only update the width and height attributes not the styles of an image
bgneal@45 10798 // It will also block mceItemNoResize items
bgneal@183 10799 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
bgneal@45 10800 var re = t.resizeInfo, cb;
bgneal@45 10801
bgneal@45 10802 e = e.target;
bgneal@45 10803
bgneal@45 10804 // Don't do this action for non image elements
bgneal@45 10805 if (e.nodeName !== 'IMG')
bgneal@45 10806 return;
bgneal@45 10807
bgneal@45 10808 if (re)
bgneal@183 10809 t.dom.unbind(re.node, re.ev, re.cb);
bgneal@45 10810
bgneal@45 10811 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
bgneal@45 10812 ev = 'resizeend';
bgneal@183 10813 cb = t.dom.bind(e, ev, function(e) {
bgneal@45 10814 var v;
bgneal@45 10815
bgneal@45 10816 e = e.target;
bgneal@45 10817
bgneal@45 10818 if (v = t.dom.getStyle(e, 'width')) {
bgneal@45 10819 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
bgneal@45 10820 t.dom.setStyle(e, 'width', '');
bgneal@45 10821 }
bgneal@45 10822
bgneal@45 10823 if (v = t.dom.getStyle(e, 'height')) {
bgneal@45 10824 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
bgneal@45 10825 t.dom.setStyle(e, 'height', '');
bgneal@45 10826 }
bgneal@45 10827 });
bgneal@45 10828 } else {
bgneal@45 10829 ev = 'resizestart';
bgneal@183 10830 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
bgneal@45 10831 }
bgneal@45 10832
bgneal@45 10833 re = t.resizeInfo = {
bgneal@45 10834 node : e,
bgneal@45 10835 ev : ev,
bgneal@45 10836 cb : cb
bgneal@45 10837 };
bgneal@45 10838 });
bgneal@45 10839
bgneal@45 10840 t.onKeyDown.add(function(ed, e) {
bgneal@45 10841 switch (e.keyCode) {
bgneal@45 10842 case 8:
bgneal@45 10843 // Fix IE control + backspace browser bug
bgneal@45 10844 if (t.selection.getRng().item) {
bgneal@183 10845 ed.dom.remove(t.selection.getRng().item(0));
bgneal@45 10846 return Event.cancel(e);
bgneal@45 10847 }
bgneal@45 10848 }
bgneal@45 10849 });
bgneal@183 10850
bgneal@183 10851 /*if (t.dom.boxModel) {
bgneal@183 10852 t.getBody().style.height = '100%';
bgneal@183 10853
bgneal@183 10854 Event.add(t.getWin(), 'resize', function(e) {
bgneal@183 10855 var docElm = t.getDoc().documentElement;
bgneal@183 10856
bgneal@183 10857 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
bgneal@183 10858 });
bgneal@183 10859 }*/
bgneal@45 10860 }
bgneal@45 10861
bgneal@45 10862 if (tinymce.isOpera) {
bgneal@45 10863 t.onClick.add(function(ed, e) {
bgneal@45 10864 Event.prevent(e);
bgneal@45 10865 });
bgneal@45 10866 }
bgneal@45 10867
bgneal@45 10868 // Add custom undo/redo handlers
bgneal@45 10869 if (s.custom_undo_redo) {
bgneal@45 10870 function addUndo() {
bgneal@45 10871 t.undoManager.typing = 0;
bgneal@45 10872 t.undoManager.add();
bgneal@45 10873 };
bgneal@45 10874
bgneal@183 10875 t.dom.bind(t.getDoc(), 'focusout', function(e) {
bgneal@183 10876 if (!t.removed && t.undoManager.typing)
bgneal@183 10877 addUndo();
bgneal@183 10878 });
bgneal@45 10879
bgneal@45 10880 t.onKeyUp.add(function(ed, e) {
bgneal@183 10881 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
bgneal@183 10882 addUndo();
bgneal@45 10883 });
bgneal@45 10884
bgneal@45 10885 t.onKeyDown.add(function(ed, e) {
bgneal@247 10886 var rng, parent, bookmark;
bgneal@247 10887
bgneal@247 10888 // IE has a really odd bug where the DOM might include an node that doesn't have
bgneal@247 10889 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
bgneal@247 10890 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
bgneal@247 10891 // after you delete contents from it. See: #3008923
bgneal@247 10892 if (isIE && e.keyCode == 46) {
bgneal@247 10893 rng = t.selection.getRng();
bgneal@247 10894
bgneal@247 10895 if (rng.parentElement) {
bgneal@247 10896 parent = rng.parentElement();
bgneal@247 10897
bgneal@247 10898 // Select next word when ctrl key is used in combo with delete
bgneal@247 10899 if (e.ctrlKey) {
bgneal@247 10900 rng.moveEnd('word', 1);
bgneal@247 10901 rng.select();
bgneal@247 10902 }
bgneal@247 10903
bgneal@247 10904 // Delete contents
bgneal@247 10905 t.selection.getSel().clear();
bgneal@247 10906
bgneal@247 10907 // Check if we are within the same parent
bgneal@247 10908 if (rng.parentElement() == parent) {
bgneal@247 10909 bookmark = t.selection.getBookmark();
bgneal@247 10910
bgneal@247 10911 try {
bgneal@247 10912 // Update the HTML and hopefully it will remove the artifacts
bgneal@247 10913 parent.innerHTML = parent.innerHTML;
bgneal@247 10914 } catch (ex) {
bgneal@247 10915 // And since it's IE it can sometimes produce an unknown runtime error
bgneal@247 10916 }
bgneal@247 10917
bgneal@247 10918 // Restore the caret position
bgneal@247 10919 t.selection.moveToBookmark(bookmark);
bgneal@247 10920 }
bgneal@247 10921
bgneal@247 10922 // Block the default delete behavior since it might be broken
bgneal@247 10923 e.preventDefault();
bgneal@247 10924 return;
bgneal@247 10925 }
bgneal@247 10926 }
bgneal@247 10927
bgneal@45 10928 // Is caracter positon keys
bgneal@45 10929 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
bgneal@183 10930 if (t.undoManager.typing)
bgneal@183 10931 addUndo();
bgneal@45 10932
bgneal@45 10933 return;
bgneal@45 10934 }
bgneal@45 10935
bgneal@45 10936 if (!t.undoManager.typing) {
bgneal@45 10937 t.undoManager.add();
bgneal@45 10938 t.undoManager.typing = 1;
bgneal@45 10939 }
bgneal@45 10940 });
bgneal@183 10941
bgneal@183 10942 t.onMouseDown.add(function() {
bgneal@183 10943 if (t.undoManager.typing)
bgneal@183 10944 addUndo();
bgneal@183 10945 });
bgneal@183 10946 }
bgneal@45 10947 },
bgneal@45 10948
bgneal@45 10949 _isHidden : function() {
bgneal@45 10950 var s;
bgneal@45 10951
bgneal@45 10952 if (!isGecko)
bgneal@45 10953 return 0;
bgneal@45 10954
bgneal@45 10955 // Weird, wheres that cursor selection?
bgneal@45 10956 s = this.selection.getSel();
bgneal@45 10957 return (!s || !s.rangeCount || s.rangeCount == 0);
bgneal@45 10958 },
bgneal@45 10959
bgneal@45 10960 // Fix for bug #1867292
bgneal@45 10961 _fixNesting : function(s) {
bgneal@45 10962 var d = [], i;
bgneal@45 10963
bgneal@45 10964 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
bgneal@45 10965 var e;
bgneal@45 10966
bgneal@45 10967 // Handle end element
bgneal@45 10968 if (b === '/') {
bgneal@45 10969 if (!d.length)
bgneal@45 10970 return '';
bgneal@45 10971
bgneal@45 10972 if (c !== d[d.length - 1].tag) {
bgneal@45 10973 for (i=d.length - 1; i>=0; i--) {
bgneal@45 10974 if (d[i].tag === c) {
bgneal@45 10975 d[i].close = 1;
bgneal@45 10976 break;
bgneal@45 10977 }
bgneal@45 10978 }
bgneal@45 10979
bgneal@45 10980 return '';
bgneal@45 10981 } else {
bgneal@45 10982 d.pop();
bgneal@45 10983
bgneal@45 10984 if (d.length && d[d.length - 1].close) {
bgneal@45 10985 a = a + '</' + d[d.length - 1].tag + '>';
bgneal@45 10986 d.pop();
bgneal@45 10987 }
bgneal@45 10988 }
bgneal@45 10989 } else {
bgneal@45 10990 // Ignore these
bgneal@45 10991 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
bgneal@45 10992 return a;
bgneal@45 10993
bgneal@45 10994 // Ignore closed ones
bgneal@45 10995 if (/\/>$/.test(a))
bgneal@45 10996 return a;
bgneal@45 10997
bgneal@45 10998 d.push({tag : c}); // Push start element
bgneal@45 10999 }
bgneal@45 11000
bgneal@45 11001 return a;
bgneal@45 11002 });
bgneal@45 11003
bgneal@45 11004 // End all open tags
bgneal@45 11005 for (i=d.length - 1; i>=0; i--)
bgneal@45 11006 s += '</' + d[i].tag + '>';
bgneal@45 11007
bgneal@45 11008 return s;
bgneal@45 11009 }
bgneal@183 11010 });
bgneal@183 11011 })(tinymce);
bgneal@183 11012
bgneal@183 11013 (function(tinymce) {
bgneal@183 11014 // Added for compression purposes
bgneal@183 11015 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
bgneal@183 11016
bgneal@183 11017 tinymce.EditorCommands = function(editor) {
bgneal@183 11018 var dom = editor.dom,
bgneal@183 11019 selection = editor.selection,
bgneal@183 11020 commands = {state: {}, exec : {}, value : {}},
bgneal@183 11021 settings = editor.settings,
bgneal@183 11022 bookmark;
bgneal@183 11023
bgneal@183 11024 function execCommand(command, ui, value) {
bgneal@183 11025 var func;
bgneal@183 11026
bgneal@183 11027 command = command.toLowerCase();
bgneal@183 11028 if (func = commands.exec[command]) {
bgneal@183 11029 func(command, ui, value);
bgneal@183 11030 return TRUE;
bgneal@183 11031 }
bgneal@183 11032
bgneal@183 11033 return FALSE;
bgneal@183 11034 };
bgneal@183 11035
bgneal@183 11036 function queryCommandState(command) {
bgneal@183 11037 var func;
bgneal@183 11038
bgneal@183 11039 command = command.toLowerCase();
bgneal@183 11040 if (func = commands.state[command])
bgneal@183 11041 return func(command);
bgneal@183 11042
bgneal@183 11043 return -1;
bgneal@183 11044 };
bgneal@183 11045
bgneal@183 11046 function queryCommandValue(command) {
bgneal@183 11047 var func;
bgneal@183 11048
bgneal@183 11049 command = command.toLowerCase();
bgneal@183 11050 if (func = commands.value[command])
bgneal@183 11051 return func(command);
bgneal@183 11052
bgneal@183 11053 return FALSE;
bgneal@183 11054 };
bgneal@183 11055
bgneal@183 11056 function addCommands(command_list, type) {
bgneal@183 11057 type = type || 'exec';
bgneal@183 11058
bgneal@183 11059 each(command_list, function(callback, command) {
bgneal@183 11060 each(command.toLowerCase().split(','), function(command) {
bgneal@183 11061 commands[type][command] = callback;
bgneal@183 11062 });
bgneal@183 11063 });
bgneal@183 11064 };
bgneal@183 11065
bgneal@183 11066 // Expose public methods
bgneal@183 11067 tinymce.extend(this, {
bgneal@183 11068 execCommand : execCommand,
bgneal@183 11069 queryCommandState : queryCommandState,
bgneal@183 11070 queryCommandValue : queryCommandValue,
bgneal@183 11071 addCommands : addCommands
bgneal@45 11072 });
bgneal@183 11073
bgneal@183 11074 // Private methods
bgneal@183 11075
bgneal@183 11076 function execNativeCommand(command, ui, value) {
bgneal@183 11077 if (ui === undefined)
bgneal@183 11078 ui = FALSE;
bgneal@183 11079
bgneal@183 11080 if (value === undefined)
bgneal@183 11081 value = null;
bgneal@183 11082
bgneal@183 11083 return editor.getDoc().execCommand(command, ui, value);
bgneal@183 11084 };
bgneal@183 11085
bgneal@183 11086 function isFormatMatch(name) {
bgneal@183 11087 return editor.formatter.match(name);
bgneal@183 11088 };
bgneal@183 11089
bgneal@183 11090 function toggleFormat(name, value) {
bgneal@183 11091 editor.formatter.toggle(name, value ? {value : value} : undefined);
bgneal@183 11092 };
bgneal@183 11093
bgneal@183 11094 function storeSelection(type) {
bgneal@183 11095 bookmark = selection.getBookmark(type);
bgneal@183 11096 };
bgneal@183 11097
bgneal@183 11098 function restoreSelection() {
bgneal@183 11099 selection.moveToBookmark(bookmark);
bgneal@183 11100 };
bgneal@183 11101
bgneal@183 11102 // Add execCommand overrides
bgneal@183 11103 addCommands({
bgneal@183 11104 // Ignore these, added for compatibility
bgneal@183 11105 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
bgneal@183 11106
bgneal@183 11107 // Add undo manager logic
bgneal@183 11108 'mceEndUndoLevel,mceAddUndoLevel' : function() {
bgneal@183 11109 editor.undoManager.add();
bgneal@183 11110 },
bgneal@183 11111
bgneal@183 11112 'Cut,Copy,Paste' : function(command) {
bgneal@183 11113 var doc = editor.getDoc(), failed;
bgneal@183 11114
bgneal@183 11115 // Try executing the native command
bgneal@183 11116 try {
bgneal@183 11117 execNativeCommand(command);
bgneal@183 11118 } catch (ex) {
bgneal@183 11119 // Command failed
bgneal@183 11120 failed = TRUE;
bgneal@183 11121 }
bgneal@183 11122
bgneal@183 11123 // Present alert message about clipboard access not being available
bgneal@217 11124 if (failed || !doc.queryCommandSupported(command)) {
bgneal@183 11125 if (tinymce.isGecko) {
bgneal@183 11126 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
bgneal@183 11127 if (state)
bgneal@183 11128 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
bgneal@183 11129 });
bgneal@183 11130 } else
bgneal@183 11131 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
bgneal@183 11132 }
bgneal@183 11133 },
bgneal@183 11134
bgneal@183 11135 // Override unlink command
bgneal@183 11136 unlink : function(command) {
bgneal@183 11137 if (selection.isCollapsed())
bgneal@183 11138 selection.select(selection.getNode());
bgneal@183 11139
bgneal@183 11140 execNativeCommand(command);
bgneal@183 11141 selection.collapse(FALSE);
bgneal@183 11142 },
bgneal@183 11143
bgneal@183 11144 // Override justify commands to use the text formatter engine
bgneal@183 11145 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@183 11146 var align = command.substring(7);
bgneal@183 11147
bgneal@183 11148 // Remove all other alignments first
bgneal@183 11149 each('left,center,right,full'.split(','), function(name) {
bgneal@183 11150 if (align != name)
bgneal@183 11151 editor.formatter.remove('align' + name);
bgneal@183 11152 });
bgneal@183 11153
bgneal@183 11154 toggleFormat('align' + align);
bgneal@183 11155 },
bgneal@183 11156
bgneal@183 11157 // Override list commands to fix WebKit bug
bgneal@183 11158 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@183 11159 var listElm, listParent;
bgneal@183 11160
bgneal@183 11161 execNativeCommand(command);
bgneal@183 11162
bgneal@183 11163 // WebKit produces lists within block elements so we need to split them
bgneal@183 11164 // we will replace the native list creation logic to custom logic later on
bgneal@183 11165 // TODO: Remove this when the list creation logic is removed
bgneal@183 11166 listElm = dom.getParent(selection.getNode(), 'ol,ul');
bgneal@183 11167 if (listElm) {
bgneal@183 11168 listParent = listElm.parentNode;
bgneal@183 11169
bgneal@183 11170 // If list is within a text block then split that block
bgneal@183 11171 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
bgneal@183 11172 storeSelection();
bgneal@183 11173 dom.split(listParent, listElm);
bgneal@183 11174 restoreSelection();
bgneal@183 11175 }
bgneal@183 11176 }
bgneal@183 11177 },
bgneal@183 11178
bgneal@183 11179 // Override commands to use the text formatter engine
bgneal@183 11180 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@183 11181 toggleFormat(command);
bgneal@183 11182 },
bgneal@183 11183
bgneal@183 11184 // Override commands to use the text formatter engine
bgneal@183 11185 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
bgneal@183 11186 toggleFormat(command, value);
bgneal@183 11187 },
bgneal@183 11188
bgneal@183 11189 FontSize : function(command, ui, value) {
bgneal@183 11190 var fontClasses, fontSizes;
bgneal@183 11191
bgneal@183 11192 // Convert font size 1-7 to styles
bgneal@183 11193 if (value >= 1 && value <= 7) {
bgneal@183 11194 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@183 11195 fontClasses = tinymce.explode(settings.font_size_classes);
bgneal@183 11196
bgneal@183 11197 if (fontClasses)
bgneal@183 11198 value = fontClasses[value - 1] || value;
bgneal@183 11199 else
bgneal@183 11200 value = fontSizes[value - 1] || value;
bgneal@183 11201 }
bgneal@183 11202
bgneal@183 11203 toggleFormat(command, value);
bgneal@183 11204 },
bgneal@183 11205
bgneal@183 11206 RemoveFormat : function(command) {
bgneal@183 11207 editor.formatter.remove(command);
bgneal@183 11208 },
bgneal@183 11209
bgneal@183 11210 mceBlockQuote : function(command) {
bgneal@183 11211 toggleFormat('blockquote');
bgneal@183 11212 },
bgneal@183 11213
bgneal@183 11214 FormatBlock : function(command, ui, value) {
bgneal@247 11215 return toggleFormat(value || 'p');
bgneal@183 11216 },
bgneal@183 11217
bgneal@183 11218 mceCleanup : function() {
bgneal@247 11219 var bookmark = selection.getBookmark();
bgneal@247 11220
bgneal@183 11221 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
bgneal@247 11222
bgneal@247 11223 selection.moveToBookmark(bookmark);
bgneal@183 11224 },
bgneal@183 11225
bgneal@183 11226 mceRemoveNode : function(command, ui, value) {
bgneal@183 11227 var node = value || selection.getNode();
bgneal@183 11228
bgneal@183 11229 // Make sure that the body node isn't removed
bgneal@217 11230 if (node != editor.getBody()) {
bgneal@183 11231 storeSelection();
bgneal@183 11232 editor.dom.remove(node, TRUE);
bgneal@183 11233 restoreSelection();
bgneal@183 11234 }
bgneal@183 11235 },
bgneal@183 11236
bgneal@183 11237 mceSelectNodeDepth : function(command, ui, value) {
bgneal@183 11238 var counter = 0;
bgneal@183 11239
bgneal@183 11240 dom.getParent(selection.getNode(), function(node) {
bgneal@183 11241 if (node.nodeType == 1 && counter++ == value) {
bgneal@183 11242 selection.select(node);
bgneal@183 11243 return FALSE;
bgneal@183 11244 }
bgneal@183 11245 }, editor.getBody());
bgneal@183 11246 },
bgneal@183 11247
bgneal@183 11248 mceSelectNode : function(command, ui, value) {
bgneal@183 11249 selection.select(value);
bgneal@183 11250 },
bgneal@183 11251
bgneal@183 11252 mceInsertContent : function(command, ui, value) {
bgneal@183 11253 selection.setContent(value);
bgneal@183 11254 },
bgneal@183 11255
bgneal@183 11256 mceInsertRawHTML : function(command, ui, value) {
bgneal@183 11257 selection.setContent('tiny_mce_marker');
bgneal@247 11258 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
bgneal@183 11259 },
bgneal@183 11260
bgneal@183 11261 mceSetContent : function(command, ui, value) {
bgneal@183 11262 editor.setContent(value);
bgneal@183 11263 },
bgneal@183 11264
bgneal@183 11265 'Indent,Outdent' : function(command) {
bgneal@183 11266 var intentValue, indentUnit, value;
bgneal@183 11267
bgneal@183 11268 // Setup indent level
bgneal@183 11269 intentValue = settings.indentation;
bgneal@183 11270 indentUnit = /[a-z%]+$/i.exec(intentValue);
bgneal@183 11271 intentValue = parseInt(intentValue);
bgneal@183 11272
bgneal@183 11273 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
bgneal@183 11274 each(selection.getSelectedBlocks(), function(element) {
bgneal@183 11275 if (command == 'outdent') {
bgneal@183 11276 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
bgneal@183 11277 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
bgneal@183 11278 } else
bgneal@183 11279 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
bgneal@183 11280 });
bgneal@183 11281 } else
bgneal@183 11282 execNativeCommand(command);
bgneal@183 11283 },
bgneal@183 11284
bgneal@183 11285 mceRepaint : function() {
bgneal@183 11286 var bookmark;
bgneal@183 11287
bgneal@183 11288 if (tinymce.isGecko) {
bgneal@183 11289 try {
bgneal@183 11290 storeSelection(TRUE);
bgneal@183 11291
bgneal@183 11292 if (selection.getSel())
bgneal@183 11293 selection.getSel().selectAllChildren(editor.getBody());
bgneal@183 11294
bgneal@183 11295 selection.collapse(TRUE);
bgneal@183 11296 restoreSelection();
bgneal@183 11297 } catch (ex) {
bgneal@183 11298 // Ignore
bgneal@183 11299 }
bgneal@183 11300 }
bgneal@183 11301 },
bgneal@183 11302
bgneal@183 11303 mceToggleFormat : function(command, ui, value) {
bgneal@183 11304 editor.formatter.toggle(value);
bgneal@183 11305 },
bgneal@183 11306
bgneal@183 11307 InsertHorizontalRule : function() {
bgneal@183 11308 selection.setContent('<hr />');
bgneal@183 11309 },
bgneal@183 11310
bgneal@183 11311 mceToggleVisualAid : function() {
bgneal@183 11312 editor.hasVisual = !editor.hasVisual;
bgneal@183 11313 editor.addVisual();
bgneal@183 11314 },
bgneal@183 11315
bgneal@183 11316 mceReplaceContent : function(command, ui, value) {
bgneal@183 11317 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
bgneal@183 11318 },
bgneal@183 11319
bgneal@183 11320 mceInsertLink : function(command, ui, value) {
bgneal@183 11321 var link = dom.getParent(selection.getNode(), 'a');
bgneal@183 11322
bgneal@183 11323 if (tinymce.is(value, 'string'))
bgneal@183 11324 value = {href : value};
bgneal@183 11325
bgneal@183 11326 if (!link) {
bgneal@183 11327 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
bgneal@183 11328 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
bgneal@183 11329 dom.setAttribs(link, value);
bgneal@183 11330 });
bgneal@183 11331 } else {
bgneal@183 11332 if (value.href)
bgneal@183 11333 dom.setAttribs(link, value);
bgneal@183 11334 else
bgneal@217 11335 editor.dom.remove(link, TRUE);
bgneal@217 11336 }
bgneal@217 11337 },
bgneal@217 11338
bgneal@217 11339 selectAll : function() {
bgneal@247 11340 var root = dom.getRoot(), rng = dom.createRng();
bgneal@247 11341
bgneal@217 11342 rng.setStart(root, 0);
bgneal@217 11343 rng.setEnd(root, root.childNodes.length);
bgneal@247 11344
bgneal@217 11345 editor.selection.setRng(rng);
bgneal@183 11346 }
bgneal@183 11347 });
bgneal@183 11348
bgneal@183 11349 // Add queryCommandState overrides
bgneal@183 11350 addCommands({
bgneal@183 11351 // Override justify commands
bgneal@183 11352 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@183 11353 return isFormatMatch('align' + command.substring(7));
bgneal@183 11354 },
bgneal@183 11355
bgneal@183 11356 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@183 11357 return isFormatMatch(command);
bgneal@183 11358 },
bgneal@183 11359
bgneal@183 11360 mceBlockQuote : function() {
bgneal@183 11361 return isFormatMatch('blockquote');
bgneal@183 11362 },
bgneal@183 11363
bgneal@183 11364 Outdent : function() {
bgneal@183 11365 var node;
bgneal@183 11366
bgneal@183 11367 if (settings.inline_styles) {
bgneal@183 11368 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@183 11369 return TRUE;
bgneal@183 11370
bgneal@183 11371 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@183 11372 return TRUE;
bgneal@183 11373 }
bgneal@183 11374
bgneal@183 11375 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
bgneal@183 11376 },
bgneal@183 11377
bgneal@183 11378 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@183 11379 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
bgneal@183 11380 }
bgneal@183 11381 }, 'state');
bgneal@183 11382
bgneal@183 11383 // Add queryCommandValue overrides
bgneal@183 11384 addCommands({
bgneal@183 11385 'FontSize,FontName' : function(command) {
bgneal@183 11386 var value = 0, parent;
bgneal@183 11387
bgneal@183 11388 if (parent = dom.getParent(selection.getNode(), 'span')) {
bgneal@183 11389 if (command == 'fontsize')
bgneal@183 11390 value = parent.style.fontSize;
bgneal@183 11391 else
bgneal@183 11392 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
bgneal@183 11393 }
bgneal@183 11394
bgneal@183 11395 return value;
bgneal@183 11396 }
bgneal@183 11397 }, 'value');
bgneal@183 11398
bgneal@183 11399 // Add undo manager logic
bgneal@183 11400 if (settings.custom_undo_redo) {
bgneal@183 11401 addCommands({
bgneal@183 11402 Undo : function() {
bgneal@183 11403 editor.undoManager.undo();
bgneal@183 11404 },
bgneal@183 11405
bgneal@183 11406 Redo : function() {
bgneal@183 11407 editor.undoManager.redo();
bgneal@183 11408 }
bgneal@183 11409 });
bgneal@183 11410 }
bgneal@183 11411 };
bgneal@45 11412 })(tinymce);
bgneal@45 11413 (function(tinymce) {
bgneal@183 11414 var Dispatcher = tinymce.util.Dispatcher;
bgneal@183 11415
bgneal@183 11416 tinymce.UndoManager = function(editor) {
bgneal@183 11417 var self, index = 0, data = [];
bgneal@183 11418
bgneal@183 11419 function getContent() {
bgneal@183 11420 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
bgneal@183 11421 };
bgneal@183 11422
bgneal@183 11423 return self = {
bgneal@183 11424 typing : 0,
bgneal@183 11425
bgneal@183 11426 onAdd : new Dispatcher(self),
bgneal@183 11427 onUndo : new Dispatcher(self),
bgneal@183 11428 onRedo : new Dispatcher(self),
bgneal@183 11429
bgneal@183 11430 add : function(level) {
bgneal@183 11431 var i, settings = editor.settings, lastLevel;
bgneal@183 11432
bgneal@183 11433 level = level || {};
bgneal@183 11434 level.content = getContent();
bgneal@183 11435
bgneal@183 11436 // Add undo level if needed
bgneal@183 11437 lastLevel = data[index];
bgneal@183 11438 if (lastLevel && lastLevel.content == level.content) {
bgneal@183 11439 if (index > 0 || data.length == 1)
bgneal@183 11440 return null;
bgneal@183 11441 }
bgneal@183 11442
bgneal@183 11443 // Time to compress
bgneal@183 11444 if (settings.custom_undo_redo_levels) {
bgneal@183 11445 if (data.length > settings.custom_undo_redo_levels) {
bgneal@183 11446 for (i = 0; i < data.length - 1; i++)
bgneal@183 11447 data[i] = data[i + 1];
bgneal@183 11448
bgneal@183 11449 data.length--;
bgneal@183 11450 index = data.length;
bgneal@183 11451 }
bgneal@183 11452 }
bgneal@183 11453
bgneal@183 11454 // Get a non intrusive normalized bookmark
bgneal@183 11455 level.bookmark = editor.selection.getBookmark(2, true);
bgneal@183 11456
bgneal@183 11457 // Crop array if needed
bgneal@183 11458 if (index < data.length - 1) {
bgneal@183 11459 // Treat first level as initial
bgneal@183 11460 if (index == 0)
bgneal@183 11461 data = [];
bgneal@183 11462 else
bgneal@183 11463 data.length = index + 1;
bgneal@183 11464 }
bgneal@183 11465
bgneal@183 11466 data.push(level);
bgneal@183 11467 index = data.length - 1;
bgneal@183 11468
bgneal@183 11469 self.onAdd.dispatch(self, level);
bgneal@183 11470 editor.isNotDirty = 0;
bgneal@183 11471
bgneal@183 11472 return level;
bgneal@183 11473 },
bgneal@183 11474
bgneal@183 11475 undo : function() {
bgneal@183 11476 var level, i;
bgneal@183 11477
bgneal@183 11478 if (self.typing) {
bgneal@183 11479 self.add();
bgneal@183 11480 self.typing = 0;
bgneal@183 11481 }
bgneal@183 11482
bgneal@183 11483 if (index > 0) {
bgneal@183 11484 level = data[--index];
bgneal@183 11485
bgneal@183 11486 editor.setContent(level.content, {format : 'raw'});
bgneal@183 11487 editor.selection.moveToBookmark(level.bookmark);
bgneal@183 11488
bgneal@183 11489 self.onUndo.dispatch(self, level);
bgneal@183 11490 }
bgneal@183 11491
bgneal@183 11492 return level;
bgneal@183 11493 },
bgneal@183 11494
bgneal@183 11495 redo : function() {
bgneal@183 11496 var level;
bgneal@183 11497
bgneal@183 11498 if (index < data.length - 1) {
bgneal@183 11499 level = data[++index];
bgneal@183 11500
bgneal@183 11501 editor.setContent(level.content, {format : 'raw'});
bgneal@183 11502 editor.selection.moveToBookmark(level.bookmark);
bgneal@183 11503
bgneal@183 11504 self.onRedo.dispatch(self, level);
bgneal@183 11505 }
bgneal@183 11506
bgneal@183 11507 return level;
bgneal@183 11508 },
bgneal@183 11509
bgneal@183 11510 clear : function() {
bgneal@183 11511 data = [];
bgneal@183 11512 index = self.typing = 0;
bgneal@183 11513 },
bgneal@183 11514
bgneal@183 11515 hasUndo : function() {
bgneal@183 11516 return index > 0 || self.typing;
bgneal@183 11517 },
bgneal@183 11518
bgneal@183 11519 hasRedo : function() {
bgneal@183 11520 return index < data.length - 1;
bgneal@183 11521 }
bgneal@183 11522 };
bgneal@183 11523 };
bgneal@45 11524 })(tinymce);
bgneal@183 11525
bgneal@45 11526 (function(tinymce) {
bgneal@45 11527 // Shorten names
bgneal@183 11528 var Event = tinymce.dom.Event,
bgneal@183 11529 isIE = tinymce.isIE,
bgneal@183 11530 isGecko = tinymce.isGecko,
bgneal@183 11531 isOpera = tinymce.isOpera,
bgneal@183 11532 each = tinymce.each,
bgneal@183 11533 extend = tinymce.extend,
bgneal@183 11534 TRUE = true,
bgneal@183 11535 FALSE = false;
bgneal@183 11536
bgneal@247 11537 function cloneFormats(node) {
bgneal@247 11538 var clone, temp, inner;
bgneal@247 11539
bgneal@247 11540 do {
bgneal@247 11541 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
bgneal@247 11542 if (clone) {
bgneal@247 11543 temp = node.cloneNode(false);
bgneal@247 11544 temp.appendChild(clone);
bgneal@247 11545 clone = temp;
bgneal@247 11546 } else {
bgneal@247 11547 clone = inner = node.cloneNode(false);
bgneal@247 11548 }
bgneal@247 11549
bgneal@247 11550 clone.removeAttribute('id');
bgneal@247 11551 }
bgneal@247 11552 } while (node = node.parentNode);
bgneal@247 11553
bgneal@247 11554 if (clone)
bgneal@247 11555 return {wrapper : clone, inner : inner};
bgneal@247 11556 };
bgneal@247 11557
bgneal@183 11558 // Checks if the selection/caret is at the end of the specified block element
bgneal@183 11559 function isAtEnd(rng, par) {
bgneal@183 11560 var rng2 = par.ownerDocument.createRange();
bgneal@183 11561
bgneal@183 11562 rng2.setStart(rng.endContainer, rng.endOffset);
bgneal@183 11563 rng2.setEndAfter(par);
bgneal@183 11564
bgneal@183 11565 // 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@183 11566 return rng2.cloneContents().textContent.length == 0;
bgneal@183 11567 };
bgneal@183 11568
bgneal@183 11569 function isEmpty(n) {
bgneal@183 11570 n = n.innerHTML;
bgneal@183 11571
bgneal@183 11572 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
bgneal@183 11573 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
bgneal@183 11574
bgneal@183 11575 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
bgneal@183 11576 };
bgneal@183 11577
bgneal@183 11578 function splitList(selection, dom, li) {
bgneal@183 11579 var listBlock, block;
bgneal@183 11580
bgneal@183 11581 if (isEmpty(li)) {
bgneal@183 11582 listBlock = dom.getParent(li, 'ul,ol');
bgneal@183 11583
bgneal@183 11584 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
bgneal@183 11585 dom.split(listBlock, li);
bgneal@183 11586 block = dom.create('p', 0, '<br _mce_bogus="1" />');
bgneal@183 11587 dom.replace(block, li);
bgneal@183 11588 selection.select(block, 1);
bgneal@183 11589 }
bgneal@183 11590
bgneal@183 11591 return FALSE;
bgneal@183 11592 }
bgneal@183 11593
bgneal@183 11594 return TRUE;
bgneal@183 11595 };
bgneal@45 11596
bgneal@45 11597 tinymce.create('tinymce.ForceBlocks', {
bgneal@45 11598 ForceBlocks : function(ed) {
bgneal@45 11599 var t = this, s = ed.settings, elm;
bgneal@45 11600
bgneal@45 11601 t.editor = ed;
bgneal@45 11602 t.dom = ed.dom;
bgneal@45 11603 elm = (s.forced_root_block || 'p').toLowerCase();
bgneal@45 11604 s.element = elm.toUpperCase();
bgneal@45 11605
bgneal@45 11606 ed.onPreInit.add(t.setup, t);
bgneal@45 11607
bgneal@45 11608 t.reOpera = new RegExp('(\\u00a0|&#160;|&nbsp;)<\/' + elm + '>', 'gi');
bgneal@45 11609 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11610 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11611 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>(&nbsp;|&#160;)<\\\/%p>|<%p>(&nbsp;|&#160;)<\\\/%p>'.replace(/%p/g, elm), 'gi');
bgneal@45 11612 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11613
bgneal@45 11614 function padd(ed, o) {
bgneal@45 11615 if (isOpera)
bgneal@45 11616 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
bgneal@45 11617
bgneal@45 11618 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
bgneal@45 11619
bgneal@45 11620 if (!isIE && !isOpera && o.set) {
bgneal@45 11621 // Use &nbsp; instead of BR in padded paragraphs
bgneal@45 11622 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@45 11623 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@183 11624 } else
bgneal@45 11625 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
bgneal@45 11626 };
bgneal@45 11627
bgneal@45 11628 ed.onBeforeSetContent.add(padd);
bgneal@45 11629 ed.onPostProcess.add(padd);
bgneal@45 11630
bgneal@45 11631 if (s.forced_root_block) {
bgneal@45 11632 ed.onInit.add(t.forceRoots, t);
bgneal@45 11633 ed.onSetContent.add(t.forceRoots, t);
bgneal@45 11634 ed.onBeforeGetContent.add(t.forceRoots, t);
bgneal@45 11635 }
bgneal@45 11636 },
bgneal@45 11637
bgneal@45 11638 setup : function() {
bgneal@183 11639 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
bgneal@45 11640
bgneal@45 11641 // Force root blocks when typing and when getting output
bgneal@45 11642 if (s.forced_root_block) {
bgneal@183 11643 ed.onBeforeExecCommand.add(t.forceRoots, t);
bgneal@45 11644 ed.onKeyUp.add(t.forceRoots, t);
bgneal@45 11645 ed.onPreProcess.add(t.forceRoots, t);
bgneal@45 11646 }
bgneal@45 11647
bgneal@45 11648 if (s.force_br_newlines) {
bgneal@45 11649 // Force IE to produce BRs on enter
bgneal@45 11650 if (isIE) {
bgneal@45 11651 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11652 var n;
bgneal@183 11653
bgneal@183 11654 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
bgneal@183 11655 selection.setContent('<br id="__" /> ', {format : 'raw'});
bgneal@183 11656 n = dom.get('__');
bgneal@45 11657 n.removeAttribute('id');
bgneal@183 11658 selection.select(n);
bgneal@183 11659 selection.collapse();
bgneal@45 11660 return Event.cancel(e);
bgneal@45 11661 }
bgneal@45 11662 });
bgneal@45 11663 }
bgneal@45 11664 }
bgneal@45 11665
bgneal@247 11666 if (s.force_p_newlines) {
bgneal@247 11667 if (!isIE) {
bgneal@247 11668 ed.onKeyPress.add(function(ed, e) {
bgneal@247 11669 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
bgneal@247 11670 Event.cancel(e);
bgneal@247 11671 });
bgneal@247 11672 } else {
bgneal@247 11673 // Ungly hack to for IE to preserve the formatting when you press
bgneal@247 11674 // enter at the end of a block element with formatted contents
bgneal@247 11675 // This logic overrides the browsers default logic with
bgneal@247 11676 // custom logic that enables us to control the output
bgneal@247 11677 tinymce.addUnload(function() {
bgneal@247 11678 t._previousFormats = 0; // Fix IE leak
bgneal@247 11679 });
bgneal@247 11680
bgneal@247 11681 ed.onKeyPress.add(function(ed, e) {
bgneal@247 11682 t._previousFormats = 0;
bgneal@247 11683
bgneal@247 11684 // Clone the current formats, this will later be applied to the new block contents
bgneal@247 11685 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
bgneal@247 11686 t._previousFormats = cloneFormats(ed.selection.getStart());
bgneal@247 11687 });
bgneal@247 11688
bgneal@247 11689 ed.onKeyUp.add(function(ed, e) {
bgneal@247 11690 // Let IE break the element and the wrap the new caret location in the previous formats
bgneal@247 11691 if (e.keyCode == 13 && !e.shiftKey) {
bgneal@247 11692 var parent = ed.selection.getStart(), fmt = t._previousFormats;
bgneal@247 11693
bgneal@247 11694 // Parent is an empty block
bgneal@247 11695 if (!parent.hasChildNodes() && fmt) {
bgneal@247 11696 parent = dom.getParent(parent, dom.isBlock);
bgneal@247 11697
bgneal@247 11698 if (parent && parent.nodeName != 'LI') {
bgneal@247 11699 parent.innerHTML = '';
bgneal@247 11700
bgneal@247 11701 if (t._previousFormats) {
bgneal@247 11702 parent.appendChild(fmt.wrapper);
bgneal@247 11703 fmt.inner.innerHTML = '\uFEFF';
bgneal@247 11704 } else
bgneal@247 11705 parent.innerHTML = '\uFEFF';
bgneal@247 11706
bgneal@247 11707 selection.select(parent, 1);
bgneal@247 11708 ed.getDoc().execCommand('Delete', false, null);
bgneal@247 11709 t._previousFormats = 0;
bgneal@247 11710 }
bgneal@247 11711 }
bgneal@247 11712 }
bgneal@247 11713 });
bgneal@247 11714 }
bgneal@45 11715
bgneal@45 11716 if (isGecko) {
bgneal@45 11717 ed.onKeyDown.add(function(ed, e) {
bgneal@45 11718 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
bgneal@45 11719 t.backspaceDelete(e, e.keyCode == 8);
bgneal@45 11720 });
bgneal@45 11721 }
bgneal@45 11722 }
bgneal@45 11723
bgneal@183 11724 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
bgneal@183 11725 if (tinymce.isWebKit) {
bgneal@183 11726 function insertBr(ed) {
bgneal@183 11727 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
bgneal@183 11728
bgneal@183 11729 // Insert BR element
bgneal@183 11730 rng.insertNode(br = dom.create('br'));
bgneal@183 11731
bgneal@183 11732 // Place caret after BR
bgneal@183 11733 rng.setStartAfter(br);
bgneal@183 11734 rng.setEndAfter(br);
bgneal@183 11735 selection.setRng(rng);
bgneal@183 11736
bgneal@183 11737 // Could not place caret after BR then insert an nbsp entity and move the caret
bgneal@183 11738 if (selection.getSel().focusNode == br.previousSibling) {
bgneal@183 11739 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
bgneal@183 11740 selection.collapse(TRUE);
bgneal@183 11741 }
bgneal@183 11742
bgneal@183 11743 // Create a temporary DIV after the BR and get the position as it
bgneal@183 11744 // seems like getPos() returns 0 for text nodes and BR elements.
bgneal@183 11745 dom.insertAfter(div, br);
bgneal@183 11746 divYPos = dom.getPos(div).y;
bgneal@183 11747 dom.remove(div);
bgneal@183 11748
bgneal@183 11749 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
bgneal@183 11750 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
bgneal@183 11751 ed.getWin().scrollTo(0, divYPos);
bgneal@183 11752 };
bgneal@183 11753
bgneal@45 11754 ed.onKeyPress.add(function(ed, e) {
bgneal@217 11755 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
bgneal@183 11756 insertBr(ed);
bgneal@183 11757 Event.cancel(e);
bgneal@183 11758 }
bgneal@183 11759 });
bgneal@183 11760 }
bgneal@183 11761
bgneal@183 11762 // Padd empty inline elements within block elements
bgneal@183 11763 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
bgneal@183 11764 ed.onPreProcess.add(function(ed, o) {
bgneal@183 11765 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
bgneal@183 11766 if (isEmpty(p)) {
bgneal@183 11767 each(dom.select('span,em,strong,b,i', o.node), function(n) {
bgneal@183 11768 if (!n.hasChildNodes()) {
bgneal@183 11769 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
bgneal@183 11770 return FALSE; // Break the loop one padding is enough
bgneal@183 11771 }
bgneal@183 11772 });
bgneal@183 11773 }
bgneal@183 11774 });
bgneal@183 11775 });
bgneal@183 11776
bgneal@183 11777 // IE specific fixes
bgneal@183 11778 if (isIE) {
bgneal@183 11779 // Replaces IE:s auto generated paragraphs with the specified element name
bgneal@183 11780 if (s.element != 'P') {
bgneal@183 11781 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11782 t.lastElm = selection.getNode().nodeName;
bgneal@183 11783 });
bgneal@183 11784
bgneal@183 11785 ed.onKeyUp.add(function(ed, e) {
bgneal@183 11786 var bl, n = selection.getNode(), b = ed.getBody();
bgneal@183 11787
bgneal@183 11788 if (b.childNodes.length === 1 && n.nodeName == 'P') {
bgneal@183 11789 n = dom.rename(n, s.element);
bgneal@183 11790 selection.select(n);
bgneal@183 11791 selection.collapse();
bgneal@45 11792 ed.nodeChanged();
bgneal@183 11793 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bgneal@183 11794 bl = dom.getParent(n, 'p');
bgneal@183 11795
bgneal@183 11796 if (bl) {
bgneal@183 11797 dom.rename(bl, s.element);
bgneal@183 11798 ed.nodeChanged();
bgneal@183 11799 }
bgneal@183 11800 }
bgneal@183 11801 });
bgneal@183 11802 }
bgneal@45 11803 }
bgneal@45 11804 },
bgneal@45 11805
bgneal@45 11806 find : function(n, t, s) {
bgneal@183 11807 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
bgneal@45 11808
bgneal@45 11809 while (n = w.nextNode()) {
bgneal@45 11810 c++;
bgneal@45 11811
bgneal@45 11812 // Index by node
bgneal@45 11813 if (t == 0 && n == s)
bgneal@45 11814 return c;
bgneal@45 11815
bgneal@45 11816 // Node by index
bgneal@45 11817 if (t == 1 && c == s)
bgneal@45 11818 return n;
bgneal@45 11819 }
bgneal@45 11820
bgneal@45 11821 return -1;
bgneal@45 11822 },
bgneal@45 11823
bgneal@45 11824 forceRoots : function(ed, e) {
bgneal@45 11825 var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
bgneal@45 11826 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
bgneal@45 11827
bgneal@45 11828 // Fix for bug #1863847
bgneal@45 11829 //if (e && e.keyCode == 13)
bgneal@183 11830 // return TRUE;
bgneal@45 11831
bgneal@45 11832 // Wrap non blocks into blocks
bgneal@45 11833 for (i = nl.length - 1; i >= 0; i--) {
bgneal@45 11834 nx = nl[i];
bgneal@45 11835
bgneal@183 11836 // Ignore internal elements
bgneal@183 11837 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
bgneal@183 11838 bl = null;
bgneal@183 11839 continue;
bgneal@183 11840 }
bgneal@183 11841
bgneal@45 11842 // Is text or non block element
bgneal@183 11843 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
bgneal@45 11844 if (!bl) {
bgneal@45 11845 // Create new block but ignore whitespace
bgneal@45 11846 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
bgneal@45 11847 // Store selection
bgneal@45 11848 if (si == -2 && r) {
bgneal@45 11849 if (!isIE) {
bgneal@45 11850 // If selection is element then mark it
bgneal@45 11851 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
bgneal@45 11852 // Save the id of the selected element
bgneal@45 11853 eid = n.getAttribute("id");
bgneal@45 11854 n.setAttribute("id", "__mce");
bgneal@45 11855 } else {
bgneal@45 11856 // If element is inside body, might not be the case in contentEdiable mode
bgneal@45 11857 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
bgneal@45 11858 so = r.startOffset;
bgneal@45 11859 eo = r.endOffset;
bgneal@45 11860 si = t.find(b, 0, r.startContainer);
bgneal@45 11861 ei = t.find(b, 0, r.endContainer);
bgneal@45 11862 }
bgneal@45 11863 }
bgneal@45 11864 } else {
bgneal@217 11865 // Force control range into text range
bgneal@217 11866 if (r.item) {
bgneal@217 11867 tr = d.body.createTextRange();
bgneal@217 11868 tr.moveToElementText(r.item(0));
bgneal@217 11869 r = tr;
bgneal@217 11870 }
bgneal@217 11871
bgneal@45 11872 tr = d.body.createTextRange();
bgneal@45 11873 tr.moveToElementText(b);
bgneal@45 11874 tr.collapse(1);
bgneal@45 11875 bp = tr.move('character', c) * -1;
bgneal@45 11876
bgneal@45 11877 tr = r.duplicate();
bgneal@45 11878 tr.collapse(1);
bgneal@45 11879 sp = tr.move('character', c) * -1;
bgneal@45 11880
bgneal@45 11881 tr = r.duplicate();
bgneal@45 11882 tr.collapse(0);
bgneal@45 11883 le = (tr.move('character', c) * -1) - sp;
bgneal@45 11884
bgneal@45 11885 si = sp - bp;
bgneal@45 11886 ei = le;
bgneal@45 11887 }
bgneal@45 11888 }
bgneal@45 11889
bgneal@183 11890 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
bgneal@183 11891 // See: http://support.microsoft.com/kb/829907
bgneal@45 11892 bl = ed.dom.create(ed.settings.forced_root_block);
bgneal@45 11893 nx.parentNode.replaceChild(bl, nx);
bgneal@183 11894 bl.appendChild(nx);
bgneal@45 11895 }
bgneal@45 11896 } else {
bgneal@45 11897 if (bl.hasChildNodes())
bgneal@45 11898 bl.insertBefore(nx, bl.firstChild);
bgneal@45 11899 else
bgneal@45 11900 bl.appendChild(nx);
bgneal@45 11901 }
bgneal@45 11902 } else
bgneal@45 11903 bl = null; // Time to create new block
bgneal@45 11904 }
bgneal@45 11905
bgneal@45 11906 // Restore selection
bgneal@45 11907 if (si != -2) {
bgneal@45 11908 if (!isIE) {
bgneal@45 11909 bl = b.getElementsByTagName(ed.settings.element)[0];
bgneal@45 11910 r = d.createRange();
bgneal@45 11911
bgneal@45 11912 // Select last location or generated block
bgneal@45 11913 if (si != -1)
bgneal@45 11914 r.setStart(t.find(b, 1, si), so);
bgneal@45 11915 else
bgneal@45 11916 r.setStart(bl, 0);
bgneal@45 11917
bgneal@45 11918 // Select last location or generated block
bgneal@45 11919 if (ei != -1)
bgneal@45 11920 r.setEnd(t.find(b, 1, ei), eo);
bgneal@45 11921 else
bgneal@45 11922 r.setEnd(bl, 0);
bgneal@45 11923
bgneal@45 11924 if (s) {
bgneal@45 11925 s.removeAllRanges();
bgneal@45 11926 s.addRange(r);
bgneal@45 11927 }
bgneal@45 11928 } else {
bgneal@45 11929 try {
bgneal@45 11930 r = s.createRange();
bgneal@45 11931 r.moveToElementText(b);
bgneal@45 11932 r.collapse(1);
bgneal@45 11933 r.moveStart('character', si);
bgneal@45 11934 r.moveEnd('character', ei);
bgneal@45 11935 r.select();
bgneal@45 11936 } catch (ex) {
bgneal@45 11937 // Ignore
bgneal@45 11938 }
bgneal@45 11939 }
bgneal@45 11940 } else if (!isIE && (n = ed.dom.get('__mce'))) {
bgneal@45 11941 // Restore the id of the selected element
bgneal@45 11942 if (eid)
bgneal@45 11943 n.setAttribute('id', eid);
bgneal@45 11944 else
bgneal@45 11945 n.removeAttribute('id');
bgneal@45 11946
bgneal@45 11947 // Move caret before selected element
bgneal@45 11948 r = d.createRange();
bgneal@45 11949 r.setStartBefore(n);
bgneal@45 11950 r.setEndBefore(n);
bgneal@45 11951 se.setRng(r);
bgneal@45 11952 }
bgneal@45 11953 },
bgneal@45 11954
bgneal@45 11955 getParentBlock : function(n) {
bgneal@45 11956 var d = this.dom;
bgneal@45 11957
bgneal@45 11958 return d.getParent(n, d.isBlock);
bgneal@45 11959 },
bgneal@45 11960
bgneal@45 11961 insertPara : function(e) {
bgneal@45 11962 var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
bgneal@45 11963 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
bgneal@45 11964
bgneal@45 11965 // If root blocks are forced then use Operas default behavior since it's really good
bgneal@45 11966 // Removed due to bug: #1853816
bgneal@45 11967 // if (se.forced_root_block && isOpera)
bgneal@183 11968 // return TRUE;
bgneal@45 11969
bgneal@45 11970 // Setup before range
bgneal@45 11971 rb = d.createRange();
bgneal@45 11972
bgneal@45 11973 // If is before the first block element and in body, then move it into first block element
bgneal@45 11974 rb.setStart(s.anchorNode, s.anchorOffset);
bgneal@183 11975 rb.collapse(TRUE);
bgneal@45 11976
bgneal@45 11977 // Setup after range
bgneal@45 11978 ra = d.createRange();
bgneal@45 11979
bgneal@45 11980 // If is before the first block element and in body, then move it into first block element
bgneal@45 11981 ra.setStart(s.focusNode, s.focusOffset);
bgneal@183 11982 ra.collapse(TRUE);
bgneal@45 11983
bgneal@45 11984 // Setup start/end points
bgneal@45 11985 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
bgneal@45 11986 sn = dir ? s.anchorNode : s.focusNode;
bgneal@45 11987 so = dir ? s.anchorOffset : s.focusOffset;
bgneal@45 11988 en = dir ? s.focusNode : s.anchorNode;
bgneal@45 11989 eo = dir ? s.focusOffset : s.anchorOffset;
bgneal@45 11990
bgneal@45 11991 // If selection is in empty table cell
bgneal@45 11992 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
bgneal@183 11993 if (sn.firstChild.nodeName == 'BR')
bgneal@183 11994 dom.remove(sn.firstChild); // Remove BR
bgneal@45 11995
bgneal@45 11996 // Create two new block elements
bgneal@183 11997 if (sn.childNodes.length == 0) {
bgneal@183 11998 ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 11999 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 12000 } else {
bgneal@183 12001 n = sn.innerHTML;
bgneal@183 12002 sn.innerHTML = '';
bgneal@183 12003 ed.dom.add(sn, se.element, null, n);
bgneal@183 12004 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 12005 }
bgneal@45 12006
bgneal@45 12007 // Move caret into the last one
bgneal@45 12008 r = d.createRange();
bgneal@45 12009 r.selectNodeContents(aft);
bgneal@45 12010 r.collapse(1);
bgneal@45 12011 ed.selection.setRng(r);
bgneal@45 12012
bgneal@183 12013 return FALSE;
bgneal@45 12014 }
bgneal@45 12015
bgneal@45 12016 // If the caret is in an invalid location in FF we need to move it into the first block
bgneal@45 12017 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
bgneal@45 12018 sn = en = sn.firstChild;
bgneal@45 12019 so = eo = 0;
bgneal@45 12020 rb = d.createRange();
bgneal@45 12021 rb.setStart(sn, 0);
bgneal@45 12022 ra = d.createRange();
bgneal@45 12023 ra.setStart(en, 0);
bgneal@45 12024 }
bgneal@45 12025
bgneal@45 12026 // Never use body as start or end node
bgneal@45 12027 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 12028 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
bgneal@45 12029 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 12030 en = en.nodeName == "BODY" ? en.firstChild : en;
bgneal@45 12031
bgneal@45 12032 // Get start and end blocks
bgneal@45 12033 sb = t.getParentBlock(sn);
bgneal@45 12034 eb = t.getParentBlock(en);
bgneal@45 12035 bn = sb ? sb.nodeName : se.element; // Get block name to create
bgneal@45 12036
bgneal@45 12037 // Return inside list use default browser behavior
bgneal@183 12038 if (n = t.dom.getParent(sb, 'li,pre')) {
bgneal@183 12039 if (n.nodeName == 'LI')
bgneal@183 12040 return splitList(ed.selection, t.dom, n);
bgneal@183 12041
bgneal@183 12042 return TRUE;
bgneal@183 12043 }
bgneal@45 12044
bgneal@45 12045 // If caption or absolute layers then always generate new blocks within
bgneal@45 12046 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 12047 bn = se.element;
bgneal@45 12048 sb = null;
bgneal@45 12049 }
bgneal@45 12050
bgneal@45 12051 // If caption or absolute layers then always generate new blocks within
bgneal@45 12052 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 12053 bn = se.element;
bgneal@45 12054 eb = null;
bgneal@45 12055 }
bgneal@45 12056
bgneal@45 12057 // Use P instead
bgneal@45 12058 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bgneal@45 12059 bn = se.element;
bgneal@45 12060 sb = eb = null;
bgneal@45 12061 }
bgneal@45 12062
bgneal@45 12063 // Setup new before and after blocks
bgneal@45 12064 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
bgneal@45 12065 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
bgneal@45 12066
bgneal@45 12067 // Remove id from after clone
bgneal@45 12068 aft.removeAttribute('id');
bgneal@45 12069
bgneal@45 12070 // Is header and cursor is at the end, then force paragraph under
bgneal@183 12071 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
bgneal@45 12072 aft = ed.dom.create(se.element);
bgneal@45 12073
bgneal@45 12074 // Find start chop node
bgneal@45 12075 n = sc = sn;
bgneal@45 12076 do {
bgneal@45 12077 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 12078 break;
bgneal@45 12079
bgneal@45 12080 sc = n;
bgneal@45 12081 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
bgneal@45 12082
bgneal@45 12083 // Find end chop node
bgneal@45 12084 n = ec = en;
bgneal@45 12085 do {
bgneal@45 12086 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 12087 break;
bgneal@45 12088
bgneal@45 12089 ec = n;
bgneal@45 12090 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
bgneal@45 12091
bgneal@45 12092 // Place first chop part into before block element
bgneal@45 12093 if (sc.nodeName == bn)
bgneal@45 12094 rb.setStart(sc, 0);
bgneal@45 12095 else
bgneal@45 12096 rb.setStartBefore(sc);
bgneal@45 12097
bgneal@45 12098 rb.setEnd(sn, so);
bgneal@45 12099 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 12100
bgneal@45 12101 // Place secnd chop part within new block element
bgneal@45 12102 try {
bgneal@45 12103 ra.setEndAfter(ec);
bgneal@45 12104 } catch(ex) {
bgneal@45 12105 //console.debug(s.focusNode, s.focusOffset);
bgneal@45 12106 }
bgneal@45 12107
bgneal@45 12108 ra.setStart(en, eo);
bgneal@45 12109 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 12110
bgneal@45 12111 // Create range around everything
bgneal@45 12112 r = d.createRange();
bgneal@45 12113 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
bgneal@45 12114 r.setStartBefore(sc.parentNode);
bgneal@45 12115 } else {
bgneal@45 12116 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
bgneal@45 12117 r.setStartBefore(rb.startContainer);
bgneal@45 12118 else
bgneal@45 12119 r.setStart(rb.startContainer, rb.startOffset);
bgneal@45 12120 }
bgneal@45 12121
bgneal@45 12122 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
bgneal@45 12123 r.setEndAfter(ec.parentNode);
bgneal@45 12124 else
bgneal@45 12125 r.setEnd(ra.endContainer, ra.endOffset);
bgneal@45 12126
bgneal@45 12127 // Delete and replace it with new block elements
bgneal@45 12128 r.deleteContents();
bgneal@45 12129
bgneal@45 12130 if (isOpera)
bgneal@45 12131 ed.getWin().scrollTo(0, vp.y);
bgneal@45 12132
bgneal@45 12133 // Never wrap blocks in blocks
bgneal@45 12134 if (bef.firstChild && bef.firstChild.nodeName == bn)
bgneal@45 12135 bef.innerHTML = bef.firstChild.innerHTML;
bgneal@45 12136
bgneal@45 12137 if (aft.firstChild && aft.firstChild.nodeName == bn)
bgneal@45 12138 aft.innerHTML = aft.firstChild.innerHTML;
bgneal@45 12139
bgneal@45 12140 // Padd empty blocks
bgneal@45 12141 if (isEmpty(bef))
bgneal@45 12142 bef.innerHTML = '<br />';
bgneal@45 12143
bgneal@45 12144 function appendStyles(e, en) {
bgneal@45 12145 var nl = [], nn, n, i;
bgneal@45 12146
bgneal@45 12147 e.innerHTML = '';
bgneal@45 12148
bgneal@45 12149 // Make clones of style elements
bgneal@45 12150 if (se.keep_styles) {
bgneal@45 12151 n = en;
bgneal@45 12152 do {
bgneal@45 12153 // We only want style specific elements
bgneal@45 12154 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
bgneal@183 12155 nn = n.cloneNode(FALSE);
bgneal@45 12156 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
bgneal@45 12157 nl.push(nn);
bgneal@45 12158 }
bgneal@45 12159 } while (n = n.parentNode);
bgneal@45 12160 }
bgneal@45 12161
bgneal@45 12162 // Append style elements to aft
bgneal@45 12163 if (nl.length > 0) {
bgneal@45 12164 for (i = nl.length - 1, nn = e; i >= 0; i--)
bgneal@45 12165 nn = nn.appendChild(nl[i]);
bgneal@45 12166
bgneal@45 12167 // Padd most inner style element
bgneal@45 12168 nl[0].innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@45 12169 return nl[0]; // Move caret to most inner element
bgneal@45 12170 } else
bgneal@45 12171 e.innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@45 12172 };
bgneal@45 12173
bgneal@45 12174 // Fill empty afterblook with current style
bgneal@45 12175 if (isEmpty(aft))
bgneal@45 12176 car = appendStyles(aft, en);
bgneal@45 12177
bgneal@45 12178 // Opera needs this one backwards for older versions
bgneal@45 12179 if (isOpera && parseFloat(opera.version()) < 9.5) {
bgneal@45 12180 r.insertNode(bef);
bgneal@45 12181 r.insertNode(aft);
bgneal@45 12182 } else {
bgneal@45 12183 r.insertNode(aft);
bgneal@45 12184 r.insertNode(bef);
bgneal@45 12185 }
bgneal@45 12186
bgneal@45 12187 // Normalize
bgneal@45 12188 aft.normalize();
bgneal@45 12189 bef.normalize();
bgneal@45 12190
bgneal@45 12191 function first(n) {
bgneal@183 12192 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
bgneal@45 12193 };
bgneal@45 12194
bgneal@45 12195 // Move cursor and scroll into view
bgneal@45 12196 r = d.createRange();
bgneal@45 12197 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
bgneal@45 12198 r.collapse(1);
bgneal@45 12199 s.removeAllRanges();
bgneal@45 12200 s.addRange(r);
bgneal@45 12201
bgneal@45 12202 // 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 12203 y = ed.dom.getPos(aft).y;
bgneal@45 12204 ch = aft.clientHeight;
bgneal@45 12205
bgneal@45 12206 // Is element within viewport
bgneal@45 12207 if (y < vp.y || y + ch > vp.y + vp.h) {
bgneal@45 12208 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 12209 //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
bgneal@45 12210 }
bgneal@45 12211
bgneal@183 12212 return FALSE;
bgneal@45 12213 },
bgneal@45 12214
bgneal@45 12215 backspaceDelete : function(e, bs) {
bgneal@247 12216 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@247 12217
bgneal@247 12218 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
bgneal@247 12219 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
bgneal@247 12220 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
bgneal@247 12221
bgneal@247 12222 // Walk the dom backwards until we find a text node
bgneal@247 12223 for (n = sc.lastChild; n; n = walker.prev()) {
bgneal@247 12224 if (n.nodeType == 3) {
bgneal@247 12225 r.setStart(n, n.nodeValue.length);
bgneal@247 12226 r.collapse(true);
bgneal@247 12227 se.setRng(r);
bgneal@247 12228 return;
bgneal@247 12229 }
bgneal@247 12230 }
bgneal@247 12231 }
bgneal@45 12232
bgneal@45 12233 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
bgneal@45 12234 // This workaround removes the element by hand and moves the caret to the previous element
bgneal@45 12235 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
bgneal@45 12236 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
bgneal@45 12237 // Find previous block element
bgneal@45 12238 n = sc;
bgneal@45 12239 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
bgneal@45 12240
bgneal@45 12241 if (n) {
bgneal@45 12242 if (sc != b.firstChild) {
bgneal@45 12243 // Find last text node
bgneal@183 12244 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
bgneal@45 12245 while (tn = w.nextNode())
bgneal@45 12246 n = tn;
bgneal@45 12247
bgneal@45 12248 // Place caret at the end of last text node
bgneal@45 12249 r = ed.getDoc().createRange();
bgneal@45 12250 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 12251 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 12252 se.setRng(r);
bgneal@45 12253
bgneal@45 12254 // Remove the target container
bgneal@45 12255 ed.dom.remove(sc);
bgneal@45 12256 }
bgneal@45 12257
bgneal@45 12258 return Event.cancel(e);
bgneal@45 12259 }
bgneal@45 12260 }
bgneal@45 12261 }
bgneal@45 12262 }
bgneal@45 12263 });
bgneal@45 12264 })(tinymce);
bgneal@183 12265
bgneal@45 12266 (function(tinymce) {
bgneal@45 12267 // Shorten names
bgneal@45 12268 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
bgneal@45 12269
bgneal@45 12270 tinymce.create('tinymce.ControlManager', {
bgneal@45 12271 ControlManager : function(ed, s) {
bgneal@45 12272 var t = this, i;
bgneal@45 12273
bgneal@45 12274 s = s || {};
bgneal@45 12275 t.editor = ed;
bgneal@45 12276 t.controls = {};
bgneal@45 12277 t.onAdd = new tinymce.util.Dispatcher(t);
bgneal@45 12278 t.onPostRender = new tinymce.util.Dispatcher(t);
bgneal@45 12279 t.prefix = s.prefix || ed.id + '_';
bgneal@45 12280 t._cls = {};
bgneal@45 12281
bgneal@45 12282 t.onPostRender.add(function() {
bgneal@45 12283 each(t.controls, function(c) {
bgneal@45 12284 c.postRender();
bgneal@45 12285 });
bgneal@45 12286 });
bgneal@45 12287 },
bgneal@45 12288
bgneal@45 12289 get : function(id) {
bgneal@45 12290 return this.controls[this.prefix + id] || this.controls[id];
bgneal@45 12291 },
bgneal@45 12292
bgneal@45 12293 setActive : function(id, s) {
bgneal@45 12294 var c = null;
bgneal@45 12295
bgneal@45 12296 if (c = this.get(id))
bgneal@45 12297 c.setActive(s);
bgneal@45 12298
bgneal@45 12299 return c;
bgneal@45 12300 },
bgneal@45 12301
bgneal@45 12302 setDisabled : function(id, s) {
bgneal@45 12303 var c = null;
bgneal@45 12304
bgneal@45 12305 if (c = this.get(id))
bgneal@45 12306 c.setDisabled(s);
bgneal@45 12307
bgneal@45 12308 return c;
bgneal@45 12309 },
bgneal@45 12310
bgneal@45 12311 add : function(c) {
bgneal@45 12312 var t = this;
bgneal@45 12313
bgneal@45 12314 if (c) {
bgneal@45 12315 t.controls[c.id] = c;
bgneal@45 12316 t.onAdd.dispatch(c, t);
bgneal@45 12317 }
bgneal@45 12318
bgneal@45 12319 return c;
bgneal@45 12320 },
bgneal@45 12321
bgneal@45 12322 createControl : function(n) {
bgneal@45 12323 var c, t = this, ed = t.editor;
bgneal@45 12324
bgneal@45 12325 each(ed.plugins, function(p) {
bgneal@45 12326 if (p.createControl) {
bgneal@45 12327 c = p.createControl(n, t);
bgneal@45 12328
bgneal@45 12329 if (c)
bgneal@45 12330 return false;
bgneal@45 12331 }
bgneal@45 12332 });
bgneal@45 12333
bgneal@45 12334 switch (n) {
bgneal@45 12335 case "|":
bgneal@45 12336 case "separator":
bgneal@45 12337 return t.createSeparator();
bgneal@45 12338 }
bgneal@45 12339
bgneal@45 12340 if (!c && ed.buttons && (c = ed.buttons[n]))
bgneal@45 12341 return t.createButton(n, c);
bgneal@45 12342
bgneal@45 12343 return t.add(c);
bgneal@45 12344 },
bgneal@45 12345
bgneal@45 12346 createDropMenu : function(id, s, cc) {
bgneal@45 12347 var t = this, ed = t.editor, c, bm, v, cls;
bgneal@45 12348
bgneal@45 12349 s = extend({
bgneal@45 12350 'class' : 'mceDropDown',
bgneal@45 12351 constrain : ed.settings.constrain_menus
bgneal@45 12352 }, s);
bgneal@45 12353
bgneal@45 12354 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
bgneal@45 12355 if (v = ed.getParam('skin_variant'))
bgneal@45 12356 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
bgneal@45 12357
bgneal@45 12358 id = t.prefix + id;
bgneal@45 12359 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
bgneal@45 12360 c = t.controls[id] = new cls(id, s);
bgneal@45 12361 c.onAddItem.add(function(c, o) {
bgneal@45 12362 var s = o.settings;
bgneal@45 12363
bgneal@45 12364 s.title = ed.getLang(s.title, s.title);
bgneal@45 12365
bgneal@45 12366 if (!s.onclick) {
bgneal@45 12367 s.onclick = function(v) {
bgneal@183 12368 if (s.cmd)
bgneal@183 12369 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 12370 };
bgneal@45 12371 }
bgneal@45 12372 });
bgneal@45 12373
bgneal@45 12374 ed.onRemove.add(function() {
bgneal@45 12375 c.destroy();
bgneal@45 12376 });
bgneal@45 12377
bgneal@45 12378 // Fix for bug #1897785, #1898007
bgneal@45 12379 if (tinymce.isIE) {
bgneal@45 12380 c.onShowMenu.add(function() {
bgneal@183 12381 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@183 12382 ed.focus();
bgneal@183 12383
bgneal@45 12384 bm = ed.selection.getBookmark(1);
bgneal@45 12385 });
bgneal@45 12386
bgneal@45 12387 c.onHideMenu.add(function() {
bgneal@45 12388 if (bm) {
bgneal@45 12389 ed.selection.moveToBookmark(bm);
bgneal@45 12390 bm = 0;
bgneal@45 12391 }
bgneal@45 12392 });
bgneal@45 12393 }
bgneal@45 12394
bgneal@45 12395 return t.add(c);
bgneal@45 12396 },
bgneal@45 12397
bgneal@45 12398 createListBox : function(id, s, cc) {
bgneal@45 12399 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 12400
bgneal@45 12401 if (t.get(id))
bgneal@45 12402 return null;
bgneal@45 12403
bgneal@45 12404 s.title = ed.translate(s.title);
bgneal@45 12405 s.scope = s.scope || ed;
bgneal@45 12406
bgneal@45 12407 if (!s.onselect) {
bgneal@45 12408 s.onselect = function(v) {
bgneal@45 12409 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12410 };
bgneal@45 12411 }
bgneal@45 12412
bgneal@45 12413 s = extend({
bgneal@45 12414 title : s.title,
bgneal@45 12415 'class' : 'mce_' + id,
bgneal@45 12416 scope : s.scope,
bgneal@45 12417 control_manager : t
bgneal@45 12418 }, s);
bgneal@45 12419
bgneal@45 12420 id = t.prefix + id;
bgneal@45 12421
bgneal@45 12422 if (ed.settings.use_native_selects)
bgneal@45 12423 c = new tinymce.ui.NativeListBox(id, s);
bgneal@45 12424 else {
bgneal@45 12425 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
bgneal@45 12426 c = new cls(id, s);
bgneal@45 12427 }
bgneal@45 12428
bgneal@45 12429 t.controls[id] = c;
bgneal@45 12430
bgneal@45 12431 // Fix focus problem in Safari
bgneal@45 12432 if (tinymce.isWebKit) {
bgneal@45 12433 c.onPostRender.add(function(c, n) {
bgneal@45 12434 // Store bookmark on mousedown
bgneal@45 12435 Event.add(n, 'mousedown', function() {
bgneal@183 12436 ed.bookmark = ed.selection.getBookmark(1);
bgneal@45 12437 });
bgneal@45 12438
bgneal@45 12439 // Restore on focus, since it might be lost
bgneal@45 12440 Event.add(n, 'focus', function() {
bgneal@45 12441 ed.selection.moveToBookmark(ed.bookmark);
bgneal@45 12442 ed.bookmark = null;
bgneal@45 12443 });
bgneal@45 12444 });
bgneal@45 12445 }
bgneal@45 12446
bgneal@45 12447 if (c.hideMenu)
bgneal@45 12448 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12449
bgneal@45 12450 return t.add(c);
bgneal@45 12451 },
bgneal@45 12452
bgneal@45 12453 createButton : function(id, s, cc) {
bgneal@45 12454 var t = this, ed = t.editor, o, c, cls;
bgneal@45 12455
bgneal@45 12456 if (t.get(id))
bgneal@45 12457 return null;
bgneal@45 12458
bgneal@45 12459 s.title = ed.translate(s.title);
bgneal@45 12460 s.label = ed.translate(s.label);
bgneal@45 12461 s.scope = s.scope || ed;
bgneal@45 12462
bgneal@45 12463 if (!s.onclick && !s.menu_button) {
bgneal@45 12464 s.onclick = function() {
bgneal@45 12465 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 12466 };
bgneal@45 12467 }
bgneal@45 12468
bgneal@45 12469 s = extend({
bgneal@45 12470 title : s.title,
bgneal@45 12471 'class' : 'mce_' + id,
bgneal@45 12472 unavailable_prefix : ed.getLang('unavailable', ''),
bgneal@45 12473 scope : s.scope,
bgneal@45 12474 control_manager : t
bgneal@45 12475 }, s);
bgneal@45 12476
bgneal@45 12477 id = t.prefix + id;
bgneal@45 12478
bgneal@45 12479 if (s.menu_button) {
bgneal@45 12480 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
bgneal@45 12481 c = new cls(id, s);
bgneal@45 12482 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12483 } else {
bgneal@45 12484 cls = t._cls.button || tinymce.ui.Button;
bgneal@45 12485 c = new cls(id, s);
bgneal@45 12486 }
bgneal@45 12487
bgneal@45 12488 return t.add(c);
bgneal@45 12489 },
bgneal@45 12490
bgneal@45 12491 createMenuButton : function(id, s, cc) {
bgneal@45 12492 s = s || {};
bgneal@45 12493 s.menu_button = 1;
bgneal@45 12494
bgneal@45 12495 return this.createButton(id, s, cc);
bgneal@45 12496 },
bgneal@45 12497
bgneal@45 12498 createSplitButton : function(id, s, cc) {
bgneal@45 12499 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 12500
bgneal@45 12501 if (t.get(id))
bgneal@45 12502 return null;
bgneal@45 12503
bgneal@45 12504 s.title = ed.translate(s.title);
bgneal@45 12505 s.scope = s.scope || ed;
bgneal@45 12506
bgneal@45 12507 if (!s.onclick) {
bgneal@45 12508 s.onclick = function(v) {
bgneal@45 12509 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12510 };
bgneal@45 12511 }
bgneal@45 12512
bgneal@45 12513 if (!s.onselect) {
bgneal@45 12514 s.onselect = function(v) {
bgneal@45 12515 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12516 };
bgneal@45 12517 }
bgneal@45 12518
bgneal@45 12519 s = extend({
bgneal@45 12520 title : s.title,
bgneal@45 12521 'class' : 'mce_' + id,
bgneal@45 12522 scope : s.scope,
bgneal@45 12523 control_manager : t
bgneal@45 12524 }, s);
bgneal@45 12525
bgneal@45 12526 id = t.prefix + id;
bgneal@45 12527 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
bgneal@45 12528 c = t.add(new cls(id, s));
bgneal@45 12529 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12530
bgneal@45 12531 return c;
bgneal@45 12532 },
bgneal@45 12533
bgneal@45 12534 createColorSplitButton : function(id, s, cc) {
bgneal@45 12535 var t = this, ed = t.editor, cmd, c, cls, bm;
bgneal@45 12536
bgneal@45 12537 if (t.get(id))
bgneal@45 12538 return null;
bgneal@45 12539
bgneal@45 12540 s.title = ed.translate(s.title);
bgneal@45 12541 s.scope = s.scope || ed;
bgneal@45 12542
bgneal@45 12543 if (!s.onclick) {
bgneal@45 12544 s.onclick = function(v) {
bgneal@45 12545 if (tinymce.isIE)
bgneal@45 12546 bm = ed.selection.getBookmark(1);
bgneal@183 12547
bgneal@45 12548 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12549 };
bgneal@45 12550 }
bgneal@45 12551
bgneal@45 12552 if (!s.onselect) {
bgneal@45 12553 s.onselect = function(v) {
bgneal@45 12554 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12555 };
bgneal@45 12556 }
bgneal@45 12557
bgneal@45 12558 s = extend({
bgneal@45 12559 title : s.title,
bgneal@45 12560 'class' : 'mce_' + id,
bgneal@45 12561 'menu_class' : ed.getParam('skin') + 'Skin',
bgneal@45 12562 scope : s.scope,
bgneal@45 12563 more_colors_title : ed.getLang('more_colors')
bgneal@45 12564 }, s);
bgneal@45 12565
bgneal@45 12566 id = t.prefix + id;
bgneal@45 12567 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
bgneal@45 12568 c = new cls(id, s);
bgneal@45 12569 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12570
bgneal@45 12571 // Remove the menu element when the editor is removed
bgneal@45 12572 ed.onRemove.add(function() {
bgneal@45 12573 c.destroy();
bgneal@45 12574 });
bgneal@45 12575
bgneal@45 12576 // Fix for bug #1897785, #1898007
bgneal@45 12577 if (tinymce.isIE) {
bgneal@183 12578 c.onShowMenu.add(function() {
bgneal@183 12579 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@183 12580 ed.focus();
bgneal@183 12581 bm = ed.selection.getBookmark(1);
bgneal@183 12582 });
bgneal@183 12583
bgneal@45 12584 c.onHideMenu.add(function() {
bgneal@45 12585 if (bm) {
bgneal@45 12586 ed.selection.moveToBookmark(bm);
bgneal@45 12587 bm = 0;
bgneal@45 12588 }
bgneal@45 12589 });
bgneal@45 12590 }
bgneal@45 12591
bgneal@45 12592 return t.add(c);
bgneal@45 12593 },
bgneal@45 12594
bgneal@45 12595 createToolbar : function(id, s, cc) {
bgneal@45 12596 var c, t = this, cls;
bgneal@45 12597
bgneal@45 12598 id = t.prefix + id;
bgneal@45 12599 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
bgneal@45 12600 c = new cls(id, s);
bgneal@45 12601
bgneal@45 12602 if (t.get(id))
bgneal@45 12603 return null;
bgneal@45 12604
bgneal@45 12605 return t.add(c);
bgneal@45 12606 },
bgneal@45 12607
bgneal@45 12608 createSeparator : function(cc) {
bgneal@45 12609 var cls = cc || this._cls.separator || tinymce.ui.Separator;
bgneal@45 12610
bgneal@45 12611 return new cls();
bgneal@45 12612 },
bgneal@45 12613
bgneal@45 12614 setControlType : function(n, c) {
bgneal@45 12615 return this._cls[n.toLowerCase()] = c;
bgneal@45 12616 },
bgneal@183 12617
bgneal@45 12618 destroy : function() {
bgneal@45 12619 each(this.controls, function(c) {
bgneal@45 12620 c.destroy();
bgneal@45 12621 });
bgneal@45 12622
bgneal@45 12623 this.controls = null;
bgneal@45 12624 }
bgneal@183 12625 });
bgneal@45 12626 })(tinymce);
bgneal@183 12627
bgneal@45 12628 (function(tinymce) {
bgneal@45 12629 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
bgneal@45 12630
bgneal@45 12631 tinymce.create('tinymce.WindowManager', {
bgneal@45 12632 WindowManager : function(ed) {
bgneal@45 12633 var t = this;
bgneal@45 12634
bgneal@45 12635 t.editor = ed;
bgneal@45 12636 t.onOpen = new Dispatcher(t);
bgneal@45 12637 t.onClose = new Dispatcher(t);
bgneal@45 12638 t.params = {};
bgneal@45 12639 t.features = {};
bgneal@45 12640 },
bgneal@45 12641
bgneal@45 12642 open : function(s, p) {
bgneal@45 12643 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
bgneal@45 12644
bgneal@45 12645 // Default some options
bgneal@45 12646 s = s || {};
bgneal@45 12647 p = p || {};
bgneal@45 12648 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
bgneal@45 12649 sh = isOpera ? vp.h : screen.height;
bgneal@45 12650 s.name = s.name || 'mc_' + new Date().getTime();
bgneal@45 12651 s.width = parseInt(s.width || 320);
bgneal@45 12652 s.height = parseInt(s.height || 240);
bgneal@45 12653 s.resizable = true;
bgneal@45 12654 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
bgneal@45 12655 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
bgneal@45 12656 p.inline = false;
bgneal@45 12657 p.mce_width = s.width;
bgneal@45 12658 p.mce_height = s.height;
bgneal@45 12659 p.mce_auto_focus = s.auto_focus;
bgneal@45 12660
bgneal@45 12661 if (mo) {
bgneal@45 12662 if (isIE) {
bgneal@45 12663 s.center = true;
bgneal@45 12664 s.help = false;
bgneal@45 12665 s.dialogWidth = s.width + 'px';
bgneal@45 12666 s.dialogHeight = s.height + 'px';
bgneal@45 12667 s.scroll = s.scrollbars || false;
bgneal@45 12668 }
bgneal@45 12669 }
bgneal@45 12670
bgneal@45 12671 // Build features string
bgneal@45 12672 each(s, function(v, k) {
bgneal@45 12673 if (tinymce.is(v, 'boolean'))
bgneal@45 12674 v = v ? 'yes' : 'no';
bgneal@45 12675
bgneal@45 12676 if (!/^(name|url)$/.test(k)) {
bgneal@45 12677 if (isIE && mo)
bgneal@45 12678 f += (f ? ';' : '') + k + ':' + v;
bgneal@45 12679 else
bgneal@45 12680 f += (f ? ',' : '') + k + '=' + v;
bgneal@45 12681 }
bgneal@45 12682 });
bgneal@45 12683
bgneal@45 12684 t.features = s;
bgneal@45 12685 t.params = p;
bgneal@45 12686 t.onOpen.dispatch(t, s, p);
bgneal@45 12687
bgneal@45 12688 u = s.url || s.file;
bgneal@45 12689 u = tinymce._addVer(u);
bgneal@45 12690
bgneal@45 12691 try {
bgneal@45 12692 if (isIE && mo) {
bgneal@45 12693 w = 1;
bgneal@45 12694 window.showModalDialog(u, window, f);
bgneal@45 12695 } else
bgneal@45 12696 w = window.open(u, s.name, f);
bgneal@45 12697 } catch (ex) {
bgneal@45 12698 // Ignore
bgneal@45 12699 }
bgneal@45 12700
bgneal@45 12701 if (!w)
bgneal@45 12702 alert(t.editor.getLang('popup_blocked'));
bgneal@45 12703 },
bgneal@45 12704
bgneal@45 12705 close : function(w) {
bgneal@45 12706 w.close();
bgneal@45 12707 this.onClose.dispatch(this);
bgneal@45 12708 },
bgneal@45 12709
bgneal@45 12710 createInstance : function(cl, a, b, c, d, e) {
bgneal@45 12711 var f = tinymce.resolve(cl);
bgneal@45 12712
bgneal@45 12713 return new f(a, b, c, d, e);
bgneal@45 12714 },
bgneal@45 12715
bgneal@45 12716 confirm : function(t, cb, s, w) {
bgneal@45 12717 w = w || window;
bgneal@45 12718
bgneal@45 12719 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
bgneal@45 12720 },
bgneal@45 12721
bgneal@45 12722 alert : function(tx, cb, s, w) {
bgneal@45 12723 var t = this;
bgneal@45 12724
bgneal@45 12725 w = w || window;
bgneal@45 12726 w.alert(t._decode(t.editor.getLang(tx, tx)));
bgneal@45 12727
bgneal@45 12728 if (cb)
bgneal@45 12729 cb.call(s || t);
bgneal@45 12730 },
bgneal@45 12731
bgneal@183 12732 resizeBy : function(dw, dh, win) {
bgneal@183 12733 win.resizeBy(dw, dh);
bgneal@183 12734 },
bgneal@183 12735
bgneal@45 12736 // Internal functions
bgneal@45 12737
bgneal@45 12738 _decode : function(s) {
bgneal@45 12739 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
bgneal@45 12740 }
bgneal@183 12741 });
bgneal@183 12742 }(tinymce));
bgneal@183 12743 (function(tinymce) {
bgneal@183 12744 function CommandManager() {
bgneal@45 12745 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
bgneal@45 12746
bgneal@45 12747 function add(collection, cmd, func, scope) {
bgneal@45 12748 if (typeof(cmd) == 'string')
bgneal@45 12749 cmd = [cmd];
bgneal@45 12750
bgneal@45 12751 tinymce.each(cmd, function(cmd) {
bgneal@45 12752 collection[cmd.toLowerCase()] = {func : func, scope : scope};
bgneal@45 12753 });
bgneal@45 12754 };
bgneal@45 12755
bgneal@45 12756 tinymce.extend(this, {
bgneal@45 12757 add : function(cmd, func, scope) {
bgneal@45 12758 add(execCommands, cmd, func, scope);
bgneal@45 12759 },
bgneal@45 12760
bgneal@45 12761 addQueryStateHandler : function(cmd, func, scope) {
bgneal@45 12762 add(queryStateCommands, cmd, func, scope);
bgneal@45 12763 },
bgneal@45 12764
bgneal@45 12765 addQueryValueHandler : function(cmd, func, scope) {
bgneal@45 12766 add(queryValueCommands, cmd, func, scope);
bgneal@45 12767 },
bgneal@45 12768
bgneal@45 12769 execCommand : function(scope, cmd, ui, value, args) {
bgneal@45 12770 if (cmd = execCommands[cmd.toLowerCase()]) {
bgneal@45 12771 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
bgneal@45 12772 return true;
bgneal@45 12773 }
bgneal@45 12774 },
bgneal@45 12775
bgneal@45 12776 queryCommandValue : function() {
bgneal@45 12777 if (cmd = queryValueCommands[cmd.toLowerCase()])
bgneal@45 12778 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@45 12779 },
bgneal@45 12780
bgneal@45 12781 queryCommandState : function() {
bgneal@45 12782 if (cmd = queryStateCommands[cmd.toLowerCase()])
bgneal@45 12783 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@45 12784 }
bgneal@45 12785 });
bgneal@45 12786 };
bgneal@45 12787
bgneal@183 12788 tinymce.GlobalCommands = new CommandManager();
bgneal@45 12789 })(tinymce);
bgneal@45 12790 (function(tinymce) {
bgneal@183 12791 tinymce.Formatter = function(ed) {
bgneal@183 12792 var formats = {},
bgneal@183 12793 each = tinymce.each,
bgneal@183 12794 dom = ed.dom,
bgneal@183 12795 selection = ed.selection,
bgneal@183 12796 TreeWalker = tinymce.dom.TreeWalker,
bgneal@183 12797 rangeUtils = new tinymce.dom.RangeUtils(dom),
bgneal@183 12798 isValid = ed.schema.isValid,
bgneal@183 12799 isBlock = dom.isBlock,
bgneal@183 12800 forcedRootBlock = ed.settings.forced_root_block,
bgneal@183 12801 nodeIndex = dom.nodeIndex,
bgneal@183 12802 INVISIBLE_CHAR = '\uFEFF',
bgneal@183 12803 MCE_ATTR_RE = /^(src|href|style)$/,
bgneal@183 12804 FALSE = false,
bgneal@183 12805 TRUE = true,
bgneal@183 12806 undefined,
bgneal@183 12807 pendingFormats = {apply : [], remove : []};
bgneal@183 12808
bgneal@183 12809 function isArray(obj) {
bgneal@183 12810 return obj instanceof Array;
bgneal@183 12811 };
bgneal@183 12812
bgneal@183 12813 function getParents(node, selector) {
bgneal@183 12814 return dom.getParents(node, selector, dom.getRoot());
bgneal@183 12815 };
bgneal@183 12816
bgneal@183 12817 function isCaretNode(node) {
bgneal@183 12818 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
bgneal@183 12819 };
bgneal@183 12820
bgneal@183 12821 // Public functions
bgneal@183 12822
bgneal@183 12823 function get(name) {
bgneal@183 12824 return name ? formats[name] : formats;
bgneal@183 12825 };
bgneal@183 12826
bgneal@183 12827 function register(name, format) {
bgneal@183 12828 if (name) {
bgneal@183 12829 if (typeof(name) !== 'string') {
bgneal@183 12830 each(name, function(format, name) {
bgneal@183 12831 register(name, format);
bgneal@183 12832 });
bgneal@45 12833 } else {
bgneal@183 12834 // Force format into array and add it to internal collection
bgneal@183 12835 format = format.length ? format : [format];
bgneal@183 12836
bgneal@183 12837 each(format, function(format) {
bgneal@183 12838 // Set deep to false by default on selector formats this to avoid removing
bgneal@183 12839 // alignment on images inside paragraphs when alignment is changed on paragraphs
bgneal@183 12840 if (format.deep === undefined)
bgneal@183 12841 format.deep = !format.selector;
bgneal@183 12842
bgneal@183 12843 // Default to true
bgneal@183 12844 if (format.split === undefined)
bgneal@183 12845 format.split = !format.selector || format.inline;
bgneal@183 12846
bgneal@183 12847 // Default to true
bgneal@183 12848 if (format.remove === undefined && format.selector && !format.inline)
bgneal@183 12849 format.remove = 'none';
bgneal@183 12850
bgneal@183 12851 // Mark format as a mixed format inline + block level
bgneal@183 12852 if (format.selector && format.inline) {
bgneal@183 12853 format.mixed = true;
bgneal@183 12854 format.block_expand = true;
bgneal@183 12855 }
bgneal@183 12856
bgneal@183 12857 // Split classes if needed
bgneal@183 12858 if (typeof(format.classes) === 'string')
bgneal@183 12859 format.classes = format.classes.split(/\s+/);
bgneal@183 12860 });
bgneal@183 12861
bgneal@183 12862 formats[name] = format;
bgneal@183 12863 }
bgneal@183 12864 }
bgneal@183 12865 };
bgneal@183 12866
bgneal@183 12867 function apply(name, vars, node) {
bgneal@183 12868 var formatList = get(name), format = formatList[0], bookmark, rng, i;
bgneal@183 12869
bgneal@183 12870 function moveStart(rng) {
bgneal@183 12871 var container = rng.startContainer,
bgneal@183 12872 offset = rng.startOffset,
bgneal@183 12873 walker, node;
bgneal@183 12874
bgneal@183 12875 // Move startContainer/startOffset in to a suitable node
bgneal@183 12876 if (container.nodeType == 1 || container.nodeValue === "") {
bgneal@217 12877 container = container.nodeType == 1 ? container.childNodes[offset] : container;
bgneal@247 12878
bgneal@247 12879 // Might fail if the offset is behind the last element in it's container
bgneal@247 12880 if (container) {
bgneal@247 12881 walker = new TreeWalker(container, container.parentNode);
bgneal@247 12882 for (node = walker.current(); node; node = walker.next()) {
bgneal@247 12883 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@247 12884 rng.setStart(node, 0);
bgneal@247 12885 break;
bgneal@247 12886 }
bgneal@183 12887 }
bgneal@183 12888 }
bgneal@183 12889 }
bgneal@183 12890
bgneal@183 12891 return rng;
bgneal@183 12892 };
bgneal@183 12893
bgneal@183 12894 function setElementFormat(elm, fmt) {
bgneal@183 12895 fmt = fmt || format;
bgneal@183 12896
bgneal@183 12897 if (elm) {
bgneal@183 12898 each(fmt.styles, function(value, name) {
bgneal@183 12899 dom.setStyle(elm, name, replaceVars(value, vars));
bgneal@183 12900 });
bgneal@183 12901
bgneal@183 12902 each(fmt.attributes, function(value, name) {
bgneal@183 12903 dom.setAttrib(elm, name, replaceVars(value, vars));
bgneal@183 12904 });
bgneal@183 12905
bgneal@183 12906 each(fmt.classes, function(value) {
bgneal@183 12907 value = replaceVars(value, vars);
bgneal@183 12908
bgneal@183 12909 if (!dom.hasClass(elm, value))
bgneal@183 12910 dom.addClass(elm, value);
bgneal@183 12911 });
bgneal@183 12912 }
bgneal@183 12913 };
bgneal@183 12914
bgneal@183 12915 function applyRngStyle(rng) {
bgneal@183 12916 var newWrappers = [], wrapName, wrapElm;
bgneal@183 12917
bgneal@183 12918 // Setup wrapper element
bgneal@183 12919 wrapName = format.inline || format.block;
bgneal@183 12920 wrapElm = dom.create(wrapName);
bgneal@183 12921 setElementFormat(wrapElm);
bgneal@183 12922
bgneal@183 12923 rangeUtils.walk(rng, function(nodes) {
bgneal@183 12924 var currentWrapElm;
bgneal@183 12925
bgneal@183 12926 function process(node) {
bgneal@183 12927 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
bgneal@183 12928
bgneal@183 12929 // Stop wrapping on br elements
bgneal@183 12930 if (isEq(nodeName, 'br')) {
bgneal@183 12931 currentWrapElm = 0;
bgneal@183 12932
bgneal@183 12933 // Remove any br elements when we wrap things
bgneal@183 12934 if (format.block)
bgneal@183 12935 dom.remove(node);
bgneal@183 12936
bgneal@183 12937 return;
bgneal@183 12938 }
bgneal@183 12939
bgneal@183 12940 // If node is wrapper type
bgneal@183 12941 if (format.wrapper && matchNode(node, name, vars)) {
bgneal@183 12942 currentWrapElm = 0;
bgneal@183 12943 return;
bgneal@183 12944 }
bgneal@183 12945
bgneal@183 12946 // Can we rename the block
bgneal@183 12947 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
bgneal@183 12948 node = dom.rename(node, wrapName);
bgneal@183 12949 setElementFormat(node);
bgneal@183 12950 newWrappers.push(node);
bgneal@183 12951 currentWrapElm = 0;
bgneal@183 12952 return;
bgneal@183 12953 }
bgneal@183 12954
bgneal@183 12955 // Handle selector patterns
bgneal@183 12956 if (format.selector) {
bgneal@183 12957 // Look for matching formats
bgneal@183 12958 each(formatList, function(format) {
bgneal@183 12959 if (dom.is(node, format.selector) && !isCaretNode(node)) {
bgneal@183 12960 setElementFormat(node, format);
bgneal@183 12961 found = true;
bgneal@183 12962 }
bgneal@183 12963 });
bgneal@183 12964
bgneal@217 12965 // Continue processing if a selector match wasn't found and a inline element is defined
bgneal@183 12966 if (!format.inline || found) {
bgneal@183 12967 currentWrapElm = 0;
bgneal@183 12968 return;
bgneal@183 12969 }
bgneal@183 12970 }
bgneal@183 12971
bgneal@183 12972 // Is it valid to wrap this item
bgneal@183 12973 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
bgneal@183 12974 // Start wrapping
bgneal@183 12975 if (!currentWrapElm) {
bgneal@183 12976 // Wrap the node
bgneal@183 12977 currentWrapElm = wrapElm.cloneNode(FALSE);
bgneal@183 12978 node.parentNode.insertBefore(currentWrapElm, node);
bgneal@183 12979 newWrappers.push(currentWrapElm);
bgneal@183 12980 }
bgneal@183 12981
bgneal@183 12982 currentWrapElm.appendChild(node);
bgneal@183 12983 } else {
bgneal@183 12984 // Start a new wrapper for possible children
bgneal@183 12985 currentWrapElm = 0;
bgneal@183 12986
bgneal@183 12987 each(tinymce.grep(node.childNodes), process);
bgneal@183 12988
bgneal@183 12989 // End the last wrapper
bgneal@183 12990 currentWrapElm = 0;
bgneal@183 12991 }
bgneal@183 12992 };
bgneal@183 12993
bgneal@183 12994 // Process siblings from range
bgneal@183 12995 each(nodes, process);
bgneal@183 12996 });
bgneal@183 12997
bgneal@183 12998 // Cleanup
bgneal@183 12999 each(newWrappers, function(node) {
bgneal@183 13000 var childCount;
bgneal@183 13001
bgneal@183 13002 function getChildCount(node) {
bgneal@183 13003 var count = 0;
bgneal@183 13004
bgneal@183 13005 each(node.childNodes, function(node) {
bgneal@183 13006 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
bgneal@183 13007 count++;
bgneal@183 13008 });
bgneal@183 13009
bgneal@183 13010 return count;
bgneal@183 13011 };
bgneal@183 13012
bgneal@183 13013 function mergeStyles(node) {
bgneal@183 13014 var child, clone;
bgneal@183 13015
bgneal@183 13016 each(node.childNodes, function(node) {
bgneal@183 13017 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
bgneal@183 13018 child = node;
bgneal@183 13019 return FALSE; // break loop
bgneal@183 13020 }
bgneal@183 13021 });
bgneal@183 13022
bgneal@183 13023 // If child was found and of the same type as the current node
bgneal@183 13024 if (child && matchName(child, format)) {
bgneal@183 13025 clone = child.cloneNode(FALSE);
bgneal@183 13026 setElementFormat(clone);
bgneal@183 13027
bgneal@183 13028 dom.replace(clone, node, TRUE);
bgneal@183 13029 dom.remove(child, 1);
bgneal@183 13030 }
bgneal@183 13031
bgneal@183 13032 return clone || node;
bgneal@183 13033 };
bgneal@183 13034
bgneal@183 13035 childCount = getChildCount(node);
bgneal@183 13036
bgneal@183 13037 // Remove empty nodes
bgneal@183 13038 if (childCount === 0) {
bgneal@183 13039 dom.remove(node, 1);
bgneal@183 13040 return;
bgneal@183 13041 }
bgneal@183 13042
bgneal@183 13043 if (format.inline || format.wrapper) {
bgneal@183 13044 // Merges the current node with it's children of similar type to reduce the number of elements
bgneal@183 13045 if (!format.exact && childCount === 1)
bgneal@183 13046 node = mergeStyles(node);
bgneal@183 13047
bgneal@183 13048 // Remove/merge children
bgneal@183 13049 each(formatList, function(format) {
bgneal@183 13050 // Merge all children of similar type will move styles from child to parent
bgneal@183 13051 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
bgneal@183 13052 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
bgneal@183 13053 each(dom.select(format.inline, node), function(child) {
bgneal@183 13054 removeFormat(format, vars, child, format.exact ? child : null);
bgneal@183 13055 });
bgneal@183 13056 });
bgneal@183 13057
bgneal@217 13058 // Remove child if direct parent is of same type
bgneal@217 13059 if (matchNode(node.parentNode, name, vars)) {
bgneal@217 13060 dom.remove(node, 1);
bgneal@217 13061 node = 0;
bgneal@217 13062 return TRUE;
bgneal@217 13063 }
bgneal@217 13064
bgneal@183 13065 // Look for parent with similar style format
bgneal@217 13066 if (format.merge_with_parents) {
bgneal@217 13067 dom.getParent(node.parentNode, function(parent) {
bgneal@217 13068 if (matchNode(parent, name, vars)) {
bgneal@217 13069 dom.remove(node, 1);
bgneal@217 13070 node = 0;
bgneal@217 13071 return TRUE;
bgneal@217 13072 }
bgneal@217 13073 });
bgneal@217 13074 }
bgneal@183 13075
bgneal@183 13076 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
bgneal@183 13077 if (node) {
bgneal@183 13078 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
bgneal@183 13079 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
bgneal@183 13080 }
bgneal@183 13081 }
bgneal@183 13082 });
bgneal@183 13083 };
bgneal@183 13084
bgneal@183 13085 if (format) {
bgneal@183 13086 if (node) {
bgneal@183 13087 rng = dom.createRng();
bgneal@183 13088
bgneal@183 13089 rng.setStartBefore(node);
bgneal@183 13090 rng.setEndAfter(node);
bgneal@183 13091
bgneal@247 13092 applyRngStyle(expandRng(rng, formatList));
bgneal@183 13093 } else {
bgneal@183 13094 if (!selection.isCollapsed() || !format.inline) {
bgneal@183 13095 // Apply formatting to selection
bgneal@183 13096 bookmark = selection.getBookmark();
bgneal@183 13097 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
bgneal@183 13098
bgneal@183 13099 selection.moveToBookmark(bookmark);
bgneal@183 13100 selection.setRng(moveStart(selection.getRng(TRUE)));
bgneal@183 13101 ed.nodeChanged();
bgneal@183 13102 } else
bgneal@183 13103 performCaretAction('apply', name, vars);
bgneal@183 13104 }
bgneal@183 13105 }
bgneal@183 13106 };
bgneal@183 13107
bgneal@183 13108 function remove(name, vars, node) {
bgneal@183 13109 var formatList = get(name), format = formatList[0], bookmark, i, rng;
bgneal@183 13110
bgneal@247 13111 function moveStart(rng) {
bgneal@247 13112 var container = rng.startContainer,
bgneal@247 13113 offset = rng.startOffset,
bgneal@247 13114 walker, node, nodes, tmpNode;
bgneal@247 13115
bgneal@247 13116 // Convert text node into index if possible
bgneal@247 13117 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
bgneal@247 13118 container = container.parentNode;
bgneal@247 13119 offset = nodeIndex(container) + 1;
bgneal@247 13120 }
bgneal@247 13121
bgneal@247 13122 // Move startContainer/startOffset in to a suitable node
bgneal@247 13123 if (container.nodeType == 1) {
bgneal@247 13124 nodes = container.childNodes;
bgneal@247 13125 container = nodes[Math.min(offset, nodes.length - 1)];
bgneal@247 13126 walker = new TreeWalker(container);
bgneal@247 13127
bgneal@247 13128 // If offset is at end of the parent node walk to the next one
bgneal@247 13129 if (offset > nodes.length - 1)
bgneal@247 13130 walker.next();
bgneal@247 13131
bgneal@247 13132 for (node = walker.current(); node; node = walker.next()) {
bgneal@247 13133 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@247 13134 // IE has a "neat" feature where it moves the start node into the closest element
bgneal@247 13135 // we can avoid this by inserting an element before it and then remove it after we set the selection
bgneal@247 13136 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
bgneal@247 13137 node.parentNode.insertBefore(tmpNode, node);
bgneal@247 13138
bgneal@247 13139 // Set selection and remove tmpNode
bgneal@247 13140 rng.setStart(node, 0);
bgneal@247 13141 selection.setRng(rng);
bgneal@247 13142 dom.remove(tmpNode);
bgneal@247 13143
bgneal@247 13144 return;
bgneal@247 13145 }
bgneal@247 13146 }
bgneal@247 13147 }
bgneal@247 13148 };
bgneal@247 13149
bgneal@183 13150 // Merges the styles for each node
bgneal@183 13151 function process(node) {
bgneal@183 13152 var children, i, l;
bgneal@183 13153
bgneal@183 13154 // Grab the children first since the nodelist might be changed
bgneal@183 13155 children = tinymce.grep(node.childNodes);
bgneal@183 13156
bgneal@183 13157 // Process current node
bgneal@183 13158 for (i = 0, l = formatList.length; i < l; i++) {
bgneal@183 13159 if (removeFormat(formatList[i], vars, node, node))
bgneal@183 13160 break;
bgneal@183 13161 }
bgneal@183 13162
bgneal@183 13163 // Process the children
bgneal@183 13164 if (format.deep) {
bgneal@183 13165 for (i = 0, l = children.length; i < l; i++)
bgneal@183 13166 process(children[i]);
bgneal@183 13167 }
bgneal@183 13168 };
bgneal@183 13169
bgneal@183 13170 function findFormatRoot(container) {
bgneal@183 13171 var formatRoot;
bgneal@183 13172
bgneal@183 13173 // Find format root
bgneal@183 13174 each(getParents(container.parentNode).reverse(), function(parent) {
bgneal@183 13175 var format;
bgneal@183 13176
bgneal@183 13177 // Find format root element
bgneal@183 13178 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
bgneal@183 13179 // Is the node matching the format we are looking for
bgneal@183 13180 format = matchNode(parent, name, vars);
bgneal@183 13181 if (format && format.split !== false)
bgneal@183 13182 formatRoot = parent;
bgneal@183 13183 }
bgneal@183 13184 });
bgneal@183 13185
bgneal@183 13186 return formatRoot;
bgneal@183 13187 };
bgneal@183 13188
bgneal@183 13189 function wrapAndSplit(format_root, container, target, split) {
bgneal@183 13190 var parent, clone, lastClone, firstClone, i, formatRootParent;
bgneal@183 13191
bgneal@183 13192 // Format root found then clone formats and split it
bgneal@183 13193 if (format_root) {
bgneal@183 13194 formatRootParent = format_root.parentNode;
bgneal@183 13195
bgneal@183 13196 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
bgneal@183 13197 clone = parent.cloneNode(FALSE);
bgneal@183 13198
bgneal@183 13199 for (i = 0; i < formatList.length; i++) {
bgneal@183 13200 if (removeFormat(formatList[i], vars, clone, clone)) {
bgneal@183 13201 clone = 0;
bgneal@183 13202 break;
bgneal@183 13203 }
bgneal@183 13204 }
bgneal@183 13205
bgneal@183 13206 // Build wrapper node
bgneal@183 13207 if (clone) {
bgneal@183 13208 if (lastClone)
bgneal@183 13209 clone.appendChild(lastClone);
bgneal@183 13210
bgneal@183 13211 if (!firstClone)
bgneal@183 13212 firstClone = clone;
bgneal@183 13213
bgneal@183 13214 lastClone = clone;
bgneal@183 13215 }
bgneal@183 13216 }
bgneal@183 13217
bgneal@183 13218 // Never split block elements if the format is mixed
bgneal@183 13219 if (split && (!format.mixed || !isBlock(format_root)))
bgneal@183 13220 container = dom.split(format_root, container);
bgneal@183 13221
bgneal@183 13222 // Wrap container in cloned formats
bgneal@183 13223 if (lastClone) {
bgneal@183 13224 target.parentNode.insertBefore(lastClone, target);
bgneal@183 13225 firstClone.appendChild(target);
bgneal@183 13226 }
bgneal@183 13227 }
bgneal@183 13228
bgneal@183 13229 return container;
bgneal@183 13230 };
bgneal@183 13231
bgneal@183 13232 function splitToFormatRoot(container) {
bgneal@183 13233 return wrapAndSplit(findFormatRoot(container), container, container, true);
bgneal@183 13234 };
bgneal@183 13235
bgneal@183 13236 function unwrap(start) {
bgneal@183 13237 var node = dom.get(start ? '_start' : '_end'),
bgneal@183 13238 out = node[start ? 'firstChild' : 'lastChild'];
bgneal@183 13239
bgneal@217 13240 // If the end is placed within the start the result will be removed
bgneal@217 13241 // So this checks if the out node is a bookmark node if it is it
bgneal@217 13242 // checks for another more suitable node
bgneal@217 13243 if (isBookmarkNode(out))
bgneal@217 13244 out = out[start ? 'firstChild' : 'lastChild'];
bgneal@217 13245
bgneal@217 13246 dom.remove(node, true);
bgneal@183 13247
bgneal@183 13248 return out;
bgneal@183 13249 };
bgneal@183 13250
bgneal@183 13251 function removeRngStyle(rng) {
bgneal@183 13252 var startContainer, endContainer;
bgneal@183 13253
bgneal@183 13254 rng = expandRng(rng, formatList, TRUE);
bgneal@183 13255
bgneal@183 13256 if (format.split) {
bgneal@183 13257 startContainer = getContainer(rng, TRUE);
bgneal@183 13258 endContainer = getContainer(rng);
bgneal@183 13259
bgneal@183 13260 if (startContainer != endContainer) {
bgneal@183 13261 // Wrap start/end nodes in span element since these might be cloned/moved
bgneal@183 13262 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
bgneal@183 13263 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
bgneal@183 13264
bgneal@183 13265 // Split start/end
bgneal@183 13266 splitToFormatRoot(startContainer);
bgneal@183 13267 splitToFormatRoot(endContainer);
bgneal@183 13268
bgneal@183 13269 // Unwrap start/end to get real elements again
bgneal@183 13270 startContainer = unwrap(TRUE);
bgneal@183 13271 endContainer = unwrap();
bgneal@183 13272 } else
bgneal@183 13273 startContainer = endContainer = splitToFormatRoot(startContainer);
bgneal@183 13274
bgneal@183 13275 // Update range positions since they might have changed after the split operations
bgneal@183 13276 rng.startContainer = startContainer.parentNode;
bgneal@183 13277 rng.startOffset = nodeIndex(startContainer);
bgneal@183 13278 rng.endContainer = endContainer.parentNode;
bgneal@183 13279 rng.endOffset = nodeIndex(endContainer) + 1;
bgneal@183 13280 }
bgneal@183 13281
bgneal@183 13282 // Remove items between start/end
bgneal@183 13283 rangeUtils.walk(rng, function(nodes) {
bgneal@183 13284 each(nodes, function(node) {
bgneal@183 13285 process(node);
bgneal@183 13286 });
bgneal@183 13287 });
bgneal@183 13288 };
bgneal@183 13289
bgneal@183 13290 // Handle node
bgneal@183 13291 if (node) {
bgneal@183 13292 rng = dom.createRng();
bgneal@183 13293 rng.setStartBefore(node);
bgneal@183 13294 rng.setEndAfter(node);
bgneal@183 13295 removeRngStyle(rng);
bgneal@183 13296 return;
bgneal@183 13297 }
bgneal@183 13298
bgneal@183 13299 if (!selection.isCollapsed() || !format.inline) {
bgneal@183 13300 bookmark = selection.getBookmark();
bgneal@183 13301 removeRngStyle(selection.getRng(TRUE));
bgneal@183 13302 selection.moveToBookmark(bookmark);
bgneal@247 13303
bgneal@247 13304 // 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@247 13305 if (match(name, vars, selection.getStart())) {
bgneal@247 13306 moveStart(selection.getRng(true));
bgneal@247 13307 }
bgneal@247 13308
bgneal@183 13309 ed.nodeChanged();
bgneal@45 13310 } else
bgneal@183 13311 performCaretAction('remove', name, vars);
bgneal@183 13312 };
bgneal@183 13313
bgneal@183 13314 function toggle(name, vars, node) {
bgneal@183 13315 if (match(name, vars, node))
bgneal@183 13316 remove(name, vars, node);
bgneal@183 13317 else
bgneal@183 13318 apply(name, vars, node);
bgneal@183 13319 };
bgneal@183 13320
bgneal@217 13321 function matchNode(node, name, vars, similar) {
bgneal@183 13322 var formatList = get(name), format, i, classes;
bgneal@183 13323
bgneal@183 13324 function matchItems(node, format, item_name) {
bgneal@183 13325 var key, value, items = format[item_name], i;
bgneal@183 13326
bgneal@183 13327 // Check all items
bgneal@183 13328 if (items) {
bgneal@183 13329 // Non indexed object
bgneal@183 13330 if (items.length === undefined) {
bgneal@183 13331 for (key in items) {
bgneal@183 13332 if (items.hasOwnProperty(key)) {
bgneal@183 13333 if (item_name === 'attributes')
bgneal@183 13334 value = dom.getAttrib(node, key);
bgneal@183 13335 else
bgneal@183 13336 value = getStyle(node, key);
bgneal@183 13337
bgneal@217 13338 if (similar && !value && !format.exact)
bgneal@217 13339 return;
bgneal@217 13340
bgneal@217 13341 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
bgneal@183 13342 return;
bgneal@183 13343 }
bgneal@183 13344 }
bgneal@183 13345 } else {
bgneal@183 13346 // Only one match needed for indexed arrays
bgneal@183 13347 for (i = 0; i < items.length; i++) {
bgneal@183 13348 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
bgneal@183 13349 return format;
bgneal@183 13350 }
bgneal@183 13351 }
bgneal@183 13352 }
bgneal@183 13353
bgneal@183 13354 return format;
bgneal@183 13355 };
bgneal@183 13356
bgneal@183 13357 if (formatList && node) {
bgneal@183 13358 // Check each format in list
bgneal@183 13359 for (i = 0; i < formatList.length; i++) {
bgneal@183 13360 format = formatList[i];
bgneal@183 13361
bgneal@183 13362 // Name name, attributes, styles and classes
bgneal@183 13363 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
bgneal@183 13364 // Match classes
bgneal@183 13365 if (classes = format.classes) {
bgneal@183 13366 for (i = 0; i < classes.length; i++) {
bgneal@183 13367 if (!dom.hasClass(node, classes[i]))
bgneal@183 13368 return;
bgneal@183 13369 }
bgneal@183 13370 }
bgneal@183 13371
bgneal@183 13372 return format;
bgneal@183 13373 }
bgneal@183 13374 }
bgneal@183 13375 }
bgneal@183 13376 };
bgneal@183 13377
bgneal@183 13378 function match(name, vars, node) {
bgneal@183 13379 var startNode, i;
bgneal@183 13380
bgneal@183 13381 function matchParents(node) {
bgneal@183 13382 // Find first node with similar format settings
bgneal@183 13383 node = dom.getParent(node, function(node) {
bgneal@217 13384 return !!matchNode(node, name, vars, true);
bgneal@183 13385 });
bgneal@183 13386
bgneal@183 13387 // Do an exact check on the similar format element
bgneal@183 13388 return matchNode(node, name, vars);
bgneal@183 13389 };
bgneal@183 13390
bgneal@183 13391 // Check specified node
bgneal@183 13392 if (node)
bgneal@183 13393 return matchParents(node);
bgneal@183 13394
bgneal@183 13395 // Check pending formats
bgneal@183 13396 if (selection.isCollapsed()) {
bgneal@183 13397 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@183 13398 if (pendingFormats.apply[i].name == name)
bgneal@183 13399 return true;
bgneal@183 13400 }
bgneal@183 13401
bgneal@183 13402 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@183 13403 if (pendingFormats.remove[i].name == name)
bgneal@183 13404 return false;
bgneal@183 13405 }
bgneal@183 13406
bgneal@183 13407 return matchParents(selection.getNode());
bgneal@183 13408 }
bgneal@183 13409
bgneal@183 13410 // Check selected node
bgneal@183 13411 node = selection.getNode();
bgneal@183 13412 if (matchParents(node))
bgneal@183 13413 return TRUE;
bgneal@183 13414
bgneal@183 13415 // Check start node if it's different
bgneal@183 13416 startNode = selection.getStart();
bgneal@183 13417 if (startNode != node) {
bgneal@183 13418 if (matchParents(startNode))
bgneal@183 13419 return TRUE;
bgneal@183 13420 }
bgneal@183 13421
bgneal@183 13422 return FALSE;
bgneal@183 13423 };
bgneal@183 13424
bgneal@183 13425 function matchAll(names, vars) {
bgneal@183 13426 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
bgneal@183 13427
bgneal@183 13428 // If the selection is collapsed then check pending formats
bgneal@183 13429 if (selection.isCollapsed()) {
bgneal@183 13430 for (ni = 0; ni < names.length; ni++) {
bgneal@183 13431 // If the name is to be removed, then stop it from being added
bgneal@183 13432 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@183 13433 name = names[ni];
bgneal@183 13434
bgneal@183 13435 if (pendingFormats.remove[i].name == name) {
bgneal@183 13436 checkedMap[name] = true;
bgneal@183 13437 break;
bgneal@183 13438 }
bgneal@183 13439 }
bgneal@183 13440 }
bgneal@183 13441
bgneal@183 13442 // If the format is to be applied
bgneal@183 13443 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@183 13444 for (ni = 0; ni < names.length; ni++) {
bgneal@183 13445 name = names[ni];
bgneal@183 13446
bgneal@183 13447 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
bgneal@183 13448 checkedMap[name] = true;
bgneal@183 13449 matchedFormatNames.push(name);
bgneal@183 13450 }
bgneal@183 13451 }
bgneal@183 13452 }
bgneal@183 13453 }
bgneal@183 13454
bgneal@183 13455 // Check start of selection for formats
bgneal@183 13456 startElement = selection.getStart();
bgneal@183 13457 dom.getParent(startElement, function(node) {
bgneal@183 13458 var i, name;
bgneal@183 13459
bgneal@183 13460 for (i = 0; i < names.length; i++) {
bgneal@183 13461 name = names[i];
bgneal@183 13462
bgneal@183 13463 if (!checkedMap[name] && matchNode(node, name, vars)) {
bgneal@183 13464 checkedMap[name] = true;
bgneal@183 13465 matchedFormatNames.push(name);
bgneal@183 13466 }
bgneal@183 13467 }
bgneal@183 13468 });
bgneal@183 13469
bgneal@183 13470 return matchedFormatNames;
bgneal@183 13471 };
bgneal@183 13472
bgneal@183 13473 function canApply(name) {
bgneal@183 13474 var formatList = get(name), startNode, parents, i, x, selector;
bgneal@183 13475
bgneal@183 13476 if (formatList) {
bgneal@183 13477 startNode = selection.getStart();
bgneal@183 13478 parents = getParents(startNode);
bgneal@183 13479
bgneal@183 13480 for (x = formatList.length - 1; x >= 0; x--) {
bgneal@183 13481 selector = formatList[x].selector;
bgneal@183 13482
bgneal@183 13483 // Format is not selector based, then always return TRUE
bgneal@183 13484 if (!selector)
bgneal@183 13485 return TRUE;
bgneal@183 13486
bgneal@183 13487 for (i = parents.length - 1; i >= 0; i--) {
bgneal@183 13488 if (dom.is(parents[i], selector))
bgneal@183 13489 return TRUE;
bgneal@183 13490 }
bgneal@183 13491 }
bgneal@183 13492 }
bgneal@183 13493
bgneal@183 13494 return FALSE;
bgneal@183 13495 };
bgneal@183 13496
bgneal@183 13497 // Expose to public
bgneal@183 13498 tinymce.extend(this, {
bgneal@183 13499 get : get,
bgneal@183 13500 register : register,
bgneal@183 13501 apply : apply,
bgneal@183 13502 remove : remove,
bgneal@183 13503 toggle : toggle,
bgneal@183 13504 match : match,
bgneal@183 13505 matchAll : matchAll,
bgneal@183 13506 matchNode : matchNode,
bgneal@183 13507 canApply : canApply
bgneal@183 13508 });
bgneal@183 13509
bgneal@183 13510 // Private functions
bgneal@183 13511
bgneal@183 13512 function matchName(node, format) {
bgneal@183 13513 // Check for inline match
bgneal@183 13514 if (isEq(node, format.inline))
bgneal@183 13515 return TRUE;
bgneal@183 13516
bgneal@183 13517 // Check for block match
bgneal@183 13518 if (isEq(node, format.block))
bgneal@183 13519 return TRUE;
bgneal@183 13520
bgneal@183 13521 // Check for selector match
bgneal@183 13522 if (format.selector)
bgneal@183 13523 return dom.is(node, format.selector);
bgneal@183 13524 };
bgneal@183 13525
bgneal@183 13526 function isEq(str1, str2) {
bgneal@183 13527 str1 = str1 || '';
bgneal@183 13528 str2 = str2 || '';
bgneal@183 13529
bgneal@183 13530 str1 = '' + (str1.nodeName || str1);
bgneal@183 13531 str2 = '' + (str2.nodeName || str2);
bgneal@183 13532
bgneal@183 13533 return str1.toLowerCase() == str2.toLowerCase();
bgneal@183 13534 };
bgneal@183 13535
bgneal@183 13536 function getStyle(node, name) {
bgneal@183 13537 var styleVal = dom.getStyle(node, name);
bgneal@183 13538
bgneal@183 13539 // Force the format to hex
bgneal@183 13540 if (name == 'color' || name == 'backgroundColor')
bgneal@183 13541 styleVal = dom.toHex(styleVal);
bgneal@183 13542
bgneal@183 13543 // Opera will return bold as 700
bgneal@183 13544 if (name == 'fontWeight' && styleVal == 700)
bgneal@183 13545 styleVal = 'bold';
bgneal@183 13546
bgneal@183 13547 return '' + styleVal;
bgneal@183 13548 };
bgneal@183 13549
bgneal@183 13550 function replaceVars(value, vars) {
bgneal@183 13551 if (typeof(value) != "string")
bgneal@183 13552 value = value(vars);
bgneal@183 13553 else if (vars) {
bgneal@183 13554 value = value.replace(/%(\w+)/g, function(str, name) {
bgneal@183 13555 return vars[name] || str;
bgneal@183 13556 });
bgneal@183 13557 }
bgneal@183 13558
bgneal@183 13559 return value;
bgneal@183 13560 };
bgneal@183 13561
bgneal@183 13562 function isWhiteSpaceNode(node) {
bgneal@247 13563 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
bgneal@183 13564 };
bgneal@183 13565
bgneal@183 13566 function wrap(node, name, attrs) {
bgneal@183 13567 var wrapper = dom.create(name, attrs);
bgneal@183 13568
bgneal@183 13569 node.parentNode.insertBefore(wrapper, node);
bgneal@183 13570 wrapper.appendChild(node);
bgneal@183 13571
bgneal@183 13572 return wrapper;
bgneal@183 13573 };
bgneal@183 13574
bgneal@183 13575 function expandRng(rng, format, remove) {
bgneal@183 13576 var startContainer = rng.startContainer,
bgneal@183 13577 startOffset = rng.startOffset,
bgneal@183 13578 endContainer = rng.endContainer,
bgneal@183 13579 endOffset = rng.endOffset, sibling, lastIdx;
bgneal@183 13580
bgneal@183 13581 // This function walks up the tree if there is no siblings before/after the node
bgneal@183 13582 function findParentContainer(container, child_name, sibling_name, root) {
bgneal@183 13583 var parent, child;
bgneal@183 13584
bgneal@183 13585 root = root || dom.getRoot();
bgneal@183 13586
bgneal@183 13587 for (;;) {
bgneal@183 13588 // Check if we can move up are we at root level or body level
bgneal@183 13589 parent = container.parentNode;
bgneal@183 13590
bgneal@183 13591 // Stop expanding on block elements or root depending on format
bgneal@183 13592 if (parent == root || (!format[0].block_expand && isBlock(parent)))
bgneal@183 13593 return container;
bgneal@183 13594
bgneal@183 13595 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
bgneal@183 13596 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@183 13597 return container;
bgneal@183 13598
bgneal@183 13599 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@183 13600 return container;
bgneal@183 13601 }
bgneal@183 13602
bgneal@183 13603 container = container.parentNode;
bgneal@183 13604 }
bgneal@183 13605
bgneal@183 13606 return container;
bgneal@183 13607 };
bgneal@183 13608
bgneal@183 13609 // If index based start position then resolve it
bgneal@183 13610 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
bgneal@183 13611 lastIdx = startContainer.childNodes.length - 1;
bgneal@183 13612 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
bgneal@183 13613
bgneal@183 13614 if (startContainer.nodeType == 3)
bgneal@183 13615 startOffset = 0;
bgneal@183 13616 }
bgneal@183 13617
bgneal@183 13618 // If index based end position then resolve it
bgneal@183 13619 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
bgneal@183 13620 lastIdx = endContainer.childNodes.length - 1;
bgneal@183 13621 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
bgneal@183 13622
bgneal@183 13623 if (endContainer.nodeType == 3)
bgneal@183 13624 endOffset = endContainer.nodeValue.length;
bgneal@183 13625 }
bgneal@183 13626
bgneal@183 13627 // Exclude bookmark nodes if possible
bgneal@183 13628 if (isBookmarkNode(startContainer.parentNode))
bgneal@183 13629 startContainer = startContainer.parentNode;
bgneal@183 13630
bgneal@183 13631 if (isBookmarkNode(startContainer))
bgneal@183 13632 startContainer = startContainer.nextSibling || startContainer;
bgneal@183 13633
bgneal@183 13634 if (isBookmarkNode(endContainer.parentNode))
bgneal@183 13635 endContainer = endContainer.parentNode;
bgneal@183 13636
bgneal@183 13637 if (isBookmarkNode(endContainer))
bgneal@183 13638 endContainer = endContainer.previousSibling || endContainer;
bgneal@183 13639
bgneal@183 13640 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
bgneal@183 13641 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
bgneal@183 13642 // This will reduce the number of wrapper elements that needs to be created
bgneal@183 13643 // Move start point up the tree
bgneal@183 13644 if (format[0].inline || format[0].block_expand) {
bgneal@183 13645 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@183 13646 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@183 13647 }
bgneal@183 13648
bgneal@183 13649 // Expand start/end container to matching selector
bgneal@183 13650 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
bgneal@183 13651 function findSelectorEndPoint(container, sibling_name) {
bgneal@183 13652 var parents, i, y;
bgneal@183 13653
bgneal@183 13654 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
bgneal@183 13655 container = container[sibling_name];
bgneal@183 13656
bgneal@183 13657 parents = getParents(container);
bgneal@183 13658 for (i = 0; i < parents.length; i++) {
bgneal@183 13659 for (y = 0; y < format.length; y++) {
bgneal@183 13660 if (dom.is(parents[i], format[y].selector))
bgneal@183 13661 return parents[i];
bgneal@183 13662 }
bgneal@183 13663 }
bgneal@183 13664
bgneal@183 13665 return container;
bgneal@183 13666 };
bgneal@183 13667
bgneal@183 13668 // Find new startContainer/endContainer if there is better one
bgneal@183 13669 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
bgneal@183 13670 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
bgneal@183 13671 }
bgneal@183 13672
bgneal@183 13673 // Expand start/end container to matching block element or text node
bgneal@183 13674 if (format[0].block || format[0].selector) {
bgneal@183 13675 function findBlockEndPoint(container, sibling_name, sibling_name2) {
bgneal@183 13676 var node;
bgneal@183 13677
bgneal@183 13678 // Expand to block of similar type
bgneal@183 13679 if (!format[0].wrapper)
bgneal@183 13680 node = dom.getParent(container, format[0].block);
bgneal@183 13681
bgneal@183 13682 // Expand to first wrappable block element or any block element
bgneal@183 13683 if (!node)
bgneal@183 13684 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
bgneal@183 13685
bgneal@183 13686 // Exclude inner lists from wrapping
bgneal@183 13687 if (node && format[0].wrapper)
bgneal@183 13688 node = getParents(node, 'ul,ol').reverse()[0] || node;
bgneal@183 13689
bgneal@183 13690 // Didn't find a block element look for first/last wrappable element
bgneal@183 13691 if (!node) {
bgneal@183 13692 node = container;
bgneal@183 13693
bgneal@183 13694 while (node[sibling_name] && !isBlock(node[sibling_name])) {
bgneal@183 13695 node = node[sibling_name];
bgneal@183 13696
bgneal@183 13697 // Break on BR but include it will be removed later on
bgneal@183 13698 // we can't remove it now since we need to check if it can be wrapped
bgneal@183 13699 if (isEq(node, 'br'))
bgneal@183 13700 break;
bgneal@183 13701 }
bgneal@183 13702 }
bgneal@183 13703
bgneal@183 13704 return node || container;
bgneal@183 13705 };
bgneal@183 13706
bgneal@183 13707 // Find new startContainer/endContainer if there is better one
bgneal@183 13708 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
bgneal@183 13709 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
bgneal@183 13710
bgneal@183 13711 // Non block element then try to expand up the leaf
bgneal@183 13712 if (format[0].block) {
bgneal@183 13713 if (!isBlock(startContainer))
bgneal@183 13714 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@183 13715
bgneal@183 13716 if (!isBlock(endContainer))
bgneal@183 13717 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@183 13718 }
bgneal@183 13719 }
bgneal@183 13720
bgneal@183 13721 // Setup index for startContainer
bgneal@183 13722 if (startContainer.nodeType == 1) {
bgneal@183 13723 startOffset = nodeIndex(startContainer);
bgneal@183 13724 startContainer = startContainer.parentNode;
bgneal@183 13725 }
bgneal@183 13726
bgneal@183 13727 // Setup index for endContainer
bgneal@183 13728 if (endContainer.nodeType == 1) {
bgneal@183 13729 endOffset = nodeIndex(endContainer) + 1;
bgneal@183 13730 endContainer = endContainer.parentNode;
bgneal@183 13731 }
bgneal@183 13732
bgneal@183 13733 // Return new range like object
bgneal@183 13734 return {
bgneal@183 13735 startContainer : startContainer,
bgneal@183 13736 startOffset : startOffset,
bgneal@183 13737 endContainer : endContainer,
bgneal@183 13738 endOffset : endOffset
bgneal@183 13739 };
bgneal@45 13740 }
bgneal@45 13741
bgneal@183 13742 function removeFormat(format, vars, node, compare_node) {
bgneal@183 13743 var i, attrs, stylesModified;
bgneal@183 13744
bgneal@183 13745 // Check if node matches format
bgneal@183 13746 if (!matchName(node, format))
bgneal@183 13747 return FALSE;
bgneal@183 13748
bgneal@183 13749 // Should we compare with format attribs and styles
bgneal@183 13750 if (format.remove != 'all') {
bgneal@183 13751 // Remove styles
bgneal@183 13752 each(format.styles, function(value, name) {
bgneal@183 13753 value = replaceVars(value, vars);
bgneal@183 13754
bgneal@183 13755 // Indexed array
bgneal@183 13756 if (typeof(name) === 'number') {
bgneal@183 13757 name = value;
bgneal@183 13758 compare_node = 0;
bgneal@183 13759 }
bgneal@183 13760
bgneal@183 13761 if (!compare_node || isEq(getStyle(compare_node, name), value))
bgneal@183 13762 dom.setStyle(node, name, '');
bgneal@183 13763
bgneal@183 13764 stylesModified = 1;
bgneal@183 13765 });
bgneal@183 13766
bgneal@183 13767 // Remove style attribute if it's empty
bgneal@183 13768 if (stylesModified && dom.getAttrib(node, 'style') == '') {
bgneal@183 13769 node.removeAttribute('style');
bgneal@183 13770 node.removeAttribute('_mce_style');
bgneal@183 13771 }
bgneal@183 13772
bgneal@183 13773 // Remove attributes
bgneal@183 13774 each(format.attributes, function(value, name) {
bgneal@183 13775 var valueOut;
bgneal@183 13776
bgneal@183 13777 value = replaceVars(value, vars);
bgneal@183 13778
bgneal@183 13779 // Indexed array
bgneal@183 13780 if (typeof(name) === 'number') {
bgneal@183 13781 name = value;
bgneal@183 13782 compare_node = 0;
bgneal@183 13783 }
bgneal@183 13784
bgneal@183 13785 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
bgneal@183 13786 // Keep internal classes
bgneal@183 13787 if (name == 'class') {
bgneal@183 13788 value = dom.getAttrib(node, name);
bgneal@183 13789 if (value) {
bgneal@183 13790 // Build new class value where everything is removed except the internal prefixed classes
bgneal@183 13791 valueOut = '';
bgneal@183 13792 each(value.split(/\s+/), function(cls) {
bgneal@183 13793 if (/mce\w+/.test(cls))
bgneal@183 13794 valueOut += (valueOut ? ' ' : '') + cls;
bgneal@183 13795 });
bgneal@183 13796
bgneal@183 13797 // We got some internal classes left
bgneal@183 13798 if (valueOut) {
bgneal@183 13799 dom.setAttrib(node, name, valueOut);
bgneal@183 13800 return;
bgneal@183 13801 }
bgneal@183 13802 }
bgneal@183 13803 }
bgneal@183 13804
bgneal@183 13805 // IE6 has a bug where the attribute doesn't get removed correctly
bgneal@183 13806 if (name == "class")
bgneal@183 13807 node.removeAttribute('className');
bgneal@183 13808
bgneal@183 13809 // Remove mce prefixed attributes
bgneal@183 13810 if (MCE_ATTR_RE.test(name))
bgneal@183 13811 node.removeAttribute('_mce_' + name);
bgneal@183 13812
bgneal@183 13813 node.removeAttribute(name);
bgneal@183 13814 }
bgneal@183 13815 });
bgneal@183 13816
bgneal@183 13817 // Remove classes
bgneal@183 13818 each(format.classes, function(value) {
bgneal@183 13819 value = replaceVars(value, vars);
bgneal@183 13820
bgneal@183 13821 if (!compare_node || dom.hasClass(compare_node, value))
bgneal@183 13822 dom.removeClass(node, value);
bgneal@183 13823 });
bgneal@183 13824
bgneal@183 13825 // Check for non internal attributes
bgneal@183 13826 attrs = dom.getAttribs(node);
bgneal@183 13827 for (i = 0; i < attrs.length; i++) {
bgneal@183 13828 if (attrs[i].nodeName.indexOf('_') !== 0)
bgneal@183 13829 return FALSE;
bgneal@183 13830 }
bgneal@183 13831 }
bgneal@183 13832
bgneal@183 13833 // Remove the inline child if it's empty for example <b> or <span>
bgneal@183 13834 if (format.remove != 'none') {
bgneal@183 13835 removeNode(node, format);
bgneal@183 13836 return TRUE;
bgneal@183 13837 }
bgneal@183 13838 };
bgneal@183 13839
bgneal@183 13840 function removeNode(node, format) {
bgneal@183 13841 var parentNode = node.parentNode, rootBlockElm;
bgneal@183 13842
bgneal@183 13843 if (format.block) {
bgneal@183 13844 if (!forcedRootBlock) {
bgneal@183 13845 function find(node, next, inc) {
bgneal@183 13846 node = getNonWhiteSpaceSibling(node, next, inc);
bgneal@183 13847
bgneal@183 13848 return !node || (node.nodeName == 'BR' || isBlock(node));
bgneal@183 13849 };
bgneal@183 13850
bgneal@183 13851 // Append BR elements if needed before we remove the block
bgneal@183 13852 if (isBlock(node) && !isBlock(parentNode)) {
bgneal@183 13853 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
bgneal@183 13854 node.insertBefore(dom.create('br'), node.firstChild);
bgneal@183 13855
bgneal@183 13856 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
bgneal@183 13857 node.appendChild(dom.create('br'));
bgneal@183 13858 }
bgneal@183 13859 } else {
bgneal@183 13860 // Wrap the block in a forcedRootBlock if we are at the root of document
bgneal@183 13861 if (parentNode == dom.getRoot()) {
bgneal@183 13862 if (!format.list_block || !isEq(node, format.list_block)) {
bgneal@183 13863 each(tinymce.grep(node.childNodes), function(node) {
bgneal@183 13864 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
bgneal@183 13865 if (!rootBlockElm)
bgneal@183 13866 rootBlockElm = wrap(node, forcedRootBlock);
bgneal@183 13867 else
bgneal@183 13868 rootBlockElm.appendChild(node);
bgneal@183 13869 } else
bgneal@183 13870 rootBlockElm = 0;
bgneal@183 13871 });
bgneal@183 13872 }
bgneal@183 13873 }
bgneal@183 13874 }
bgneal@183 13875 }
bgneal@183 13876
bgneal@183 13877 // Never remove nodes that isn't the specified inline element if a selector is specified too
bgneal@183 13878 if (format.selector && format.inline && !isEq(format.inline, node))
bgneal@45 13879 return;
bgneal@183 13880
bgneal@183 13881 dom.remove(node, 1);
bgneal@183 13882 };
bgneal@183 13883
bgneal@183 13884 function getNonWhiteSpaceSibling(node, next, inc) {
bgneal@183 13885 if (node) {
bgneal@183 13886 next = next ? 'nextSibling' : 'previousSibling';
bgneal@183 13887
bgneal@183 13888 for (node = inc ? node : node[next]; node; node = node[next]) {
bgneal@183 13889 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
bgneal@183 13890 return node;
bgneal@183 13891 }
bgneal@183 13892 }
bgneal@183 13893 };
bgneal@183 13894
bgneal@183 13895 function isBookmarkNode(node) {
bgneal@183 13896 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
bgneal@183 13897 };
bgneal@183 13898
bgneal@183 13899 function mergeSiblings(prev, next) {
bgneal@183 13900 var marker, sibling, tmpSibling;
bgneal@183 13901
bgneal@183 13902 function compareElements(node1, node2) {
bgneal@183 13903 // Not the same name
bgneal@183 13904 if (node1.nodeName != node2.nodeName)
bgneal@183 13905 return FALSE;
bgneal@183 13906
bgneal@183 13907 function getAttribs(node) {
bgneal@183 13908 var attribs = {};
bgneal@183 13909
bgneal@183 13910 each(dom.getAttribs(node), function(attr) {
bgneal@183 13911 var name = attr.nodeName.toLowerCase();
bgneal@183 13912
bgneal@183 13913 // Don't compare internal attributes or style
bgneal@183 13914 if (name.indexOf('_') !== 0 && name !== 'style')
bgneal@183 13915 attribs[name] = dom.getAttrib(node, name);
bgneal@183 13916 });
bgneal@183 13917
bgneal@183 13918 return attribs;
bgneal@183 13919 };
bgneal@183 13920
bgneal@183 13921 function compareObjects(obj1, obj2) {
bgneal@183 13922 var value, name;
bgneal@183 13923
bgneal@183 13924 for (name in obj1) {
bgneal@183 13925 // Obj1 has item obj2 doesn't have
bgneal@183 13926 if (obj1.hasOwnProperty(name)) {
bgneal@183 13927 value = obj2[name];
bgneal@183 13928
bgneal@183 13929 // Obj2 doesn't have obj1 item
bgneal@183 13930 if (value === undefined)
bgneal@183 13931 return FALSE;
bgneal@183 13932
bgneal@183 13933 // Obj2 item has a different value
bgneal@183 13934 if (obj1[name] != value)
bgneal@183 13935 return FALSE;
bgneal@183 13936
bgneal@183 13937 // Delete similar value
bgneal@183 13938 delete obj2[name];
bgneal@183 13939 }
bgneal@183 13940 }
bgneal@183 13941
bgneal@183 13942 // Check if obj 2 has something obj 1 doesn't have
bgneal@183 13943 for (name in obj2) {
bgneal@183 13944 // Obj2 has item obj1 doesn't have
bgneal@183 13945 if (obj2.hasOwnProperty(name))
bgneal@183 13946 return FALSE;
bgneal@183 13947 }
bgneal@183 13948
bgneal@183 13949 return TRUE;
bgneal@183 13950 };
bgneal@183 13951
bgneal@183 13952 // Attribs are not the same
bgneal@183 13953 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
bgneal@183 13954 return FALSE;
bgneal@183 13955
bgneal@183 13956 // Styles are not the same
bgneal@183 13957 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
bgneal@183 13958 return FALSE;
bgneal@183 13959
bgneal@183 13960 return TRUE;
bgneal@183 13961 };
bgneal@183 13962
bgneal@183 13963 // Check if next/prev exists and that they are elements
bgneal@183 13964 if (prev && next) {
bgneal@183 13965 function findElementSibling(node, sibling_name) {
bgneal@183 13966 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
bgneal@183 13967 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@183 13968 return node;
bgneal@183 13969
bgneal@183 13970 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@183 13971 return sibling;
bgneal@183 13972 }
bgneal@183 13973
bgneal@183 13974 return node;
bgneal@183 13975 };
bgneal@183 13976
bgneal@183 13977 // If previous sibling is empty then jump over it
bgneal@183 13978 prev = findElementSibling(prev, 'previousSibling');
bgneal@183 13979 next = findElementSibling(next, 'nextSibling');
bgneal@183 13980
bgneal@183 13981 // Compare next and previous nodes
bgneal@183 13982 if (compareElements(prev, next)) {
bgneal@183 13983 // Append nodes between
bgneal@183 13984 for (sibling = prev.nextSibling; sibling && sibling != next;) {
bgneal@183 13985 tmpSibling = sibling;
bgneal@183 13986 sibling = sibling.nextSibling;
bgneal@183 13987 prev.appendChild(tmpSibling);
bgneal@183 13988 }
bgneal@183 13989
bgneal@183 13990 // Remove next node
bgneal@183 13991 dom.remove(next);
bgneal@183 13992
bgneal@183 13993 // Move children into prev node
bgneal@183 13994 each(tinymce.grep(next.childNodes), function(node) {
bgneal@183 13995 prev.appendChild(node);
bgneal@183 13996 });
bgneal@183 13997
bgneal@183 13998 return prev;
bgneal@183 13999 }
bgneal@183 14000 }
bgneal@183 14001
bgneal@183 14002 return next;
bgneal@183 14003 };
bgneal@183 14004
bgneal@183 14005 function isTextBlock(name) {
bgneal@217 14006 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
bgneal@183 14007 };
bgneal@183 14008
bgneal@183 14009 function getContainer(rng, start) {
bgneal@183 14010 var container, offset, lastIdx;
bgneal@183 14011
bgneal@183 14012 container = rng[start ? 'startContainer' : 'endContainer'];
bgneal@183 14013 offset = rng[start ? 'startOffset' : 'endOffset'];
bgneal@183 14014
bgneal@183 14015 if (container.nodeType == 1) {
bgneal@183 14016 lastIdx = container.childNodes.length - 1;
bgneal@183 14017
bgneal@183 14018 if (!start && offset)
bgneal@183 14019 offset--;
bgneal@183 14020
bgneal@183 14021 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
bgneal@183 14022 }
bgneal@183 14023
bgneal@183 14024 return container;
bgneal@183 14025 };
bgneal@183 14026
bgneal@183 14027 function performCaretAction(type, name, vars) {
bgneal@183 14028 var i, currentPendingFormats = pendingFormats[type],
bgneal@183 14029 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
bgneal@183 14030
bgneal@183 14031 function hasPending() {
bgneal@183 14032 return pendingFormats.apply.length || pendingFormats.remove.length;
bgneal@183 14033 };
bgneal@183 14034
bgneal@183 14035 function resetPending() {
bgneal@183 14036 pendingFormats.apply = [];
bgneal@183 14037 pendingFormats.remove = [];
bgneal@183 14038 };
bgneal@183 14039
bgneal@183 14040 function perform(caret_node) {
bgneal@183 14041 // Apply pending formats
bgneal@183 14042 each(pendingFormats.apply.reverse(), function(item) {
bgneal@183 14043 apply(item.name, item.vars, caret_node);
bgneal@183 14044 });
bgneal@183 14045
bgneal@183 14046 // Remove pending formats
bgneal@183 14047 each(pendingFormats.remove.reverse(), function(item) {
bgneal@183 14048 remove(item.name, item.vars, caret_node);
bgneal@183 14049 });
bgneal@183 14050
bgneal@183 14051 dom.remove(caret_node, 1);
bgneal@183 14052 resetPending();
bgneal@183 14053 };
bgneal@183 14054
bgneal@183 14055 // Check if it already exists then ignore it
bgneal@183 14056 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
bgneal@183 14057 if (currentPendingFormats[i].name == name)
bgneal@183 14058 return;
bgneal@183 14059 }
bgneal@183 14060
bgneal@183 14061 currentPendingFormats.push({name : name, vars : vars});
bgneal@183 14062
bgneal@183 14063 // Check if it's in the other type, then remove it
bgneal@183 14064 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
bgneal@183 14065 if (otherPendingFormats[i].name == name)
bgneal@183 14066 otherPendingFormats.splice(i, 1);
bgneal@183 14067 }
bgneal@183 14068
bgneal@183 14069 // Pending apply or remove formats
bgneal@183 14070 if (hasPending()) {
bgneal@183 14071 ed.getDoc().execCommand('FontName', false, 'mceinline');
bgneal@217 14072 pendingFormats.lastRng = selection.getRng();
bgneal@183 14073
bgneal@183 14074 // IE will convert the current word
bgneal@183 14075 each(dom.select('font,span'), function(node) {
bgneal@183 14076 var bookmark;
bgneal@183 14077
bgneal@183 14078 if (isCaretNode(node)) {
bgneal@183 14079 bookmark = selection.getBookmark();
bgneal@183 14080 perform(node);
bgneal@183 14081 selection.moveToBookmark(bookmark);
bgneal@183 14082 ed.nodeChanged();
bgneal@183 14083 }
bgneal@183 14084 });
bgneal@183 14085
bgneal@183 14086 // Only register listeners once if we need to
bgneal@183 14087 if (!pendingFormats.isListening && hasPending()) {
bgneal@183 14088 pendingFormats.isListening = true;
bgneal@183 14089
bgneal@183 14090 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
bgneal@183 14091 ed[event].addToTop(function(ed, e) {
bgneal@217 14092 // Do we have pending formats and is the selection moved has moved
bgneal@217 14093 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
bgneal@183 14094 each(dom.select('font,span'), function(node) {
bgneal@217 14095 var textNode, rng;
bgneal@183 14096
bgneal@183 14097 // Look for marker
bgneal@183 14098 if (isCaretNode(node)) {
bgneal@183 14099 textNode = node.firstChild;
bgneal@183 14100
bgneal@247 14101 if (textNode) {
bgneal@247 14102 perform(node);
bgneal@247 14103
bgneal@247 14104 rng = dom.createRng();
bgneal@247 14105 rng.setStart(textNode, textNode.nodeValue.length);
bgneal@247 14106 rng.setEnd(textNode, textNode.nodeValue.length);
bgneal@247 14107 selection.setRng(rng);
bgneal@247 14108 ed.nodeChanged();
bgneal@247 14109 } else
bgneal@247 14110 dom.remove(node);
bgneal@183 14111 }
bgneal@183 14112 });
bgneal@183 14113
bgneal@183 14114 // Always unbind and clear pending styles on keyup
bgneal@183 14115 if (e.type == 'keyup' || e.type == 'mouseup')
bgneal@183 14116 resetPending();
bgneal@183 14117 }
bgneal@183 14118 });
bgneal@183 14119 });
bgneal@183 14120 }
bgneal@183 14121 }
bgneal@183 14122 };
bgneal@183 14123 };
bgneal@183 14124 })(tinymce);
bgneal@183 14125
bgneal@183 14126 tinymce.onAddEditor.add(function(tinymce, ed) {
bgneal@183 14127 var filters, fontSizes, dom, settings = ed.settings;
bgneal@183 14128
bgneal@183 14129 if (settings.inline_styles) {
bgneal@183 14130 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@183 14131
bgneal@183 14132 function replaceWithSpan(node, styles) {
bgneal@247 14133 tinymce.each(styles, function(value, name) {
bgneal@247 14134 if (value)
bgneal@247 14135 dom.setStyle(node, name, value);
bgneal@247 14136 });
bgneal@247 14137
bgneal@247 14138 dom.rename(node, 'span');
bgneal@183 14139 };
bgneal@183 14140
bgneal@183 14141 filters = {
bgneal@183 14142 font : function(dom, node) {
bgneal@183 14143 replaceWithSpan(node, {
bgneal@183 14144 backgroundColor : node.style.backgroundColor,
bgneal@183 14145 color : node.color,
bgneal@183 14146 fontFamily : node.face,
bgneal@183 14147 fontSize : fontSizes[parseInt(node.size) - 1]
bgneal@183 14148 });
bgneal@183 14149 },
bgneal@183 14150
bgneal@183 14151 u : function(dom, node) {
bgneal@183 14152 replaceWithSpan(node, {
bgneal@183 14153 textDecoration : 'underline'
bgneal@183 14154 });
bgneal@183 14155 },
bgneal@183 14156
bgneal@183 14157 strike : function(dom, node) {
bgneal@183 14158 replaceWithSpan(node, {
bgneal@183 14159 textDecoration : 'line-through'
bgneal@183 14160 });
bgneal@183 14161 }
bgneal@183 14162 };
bgneal@183 14163
bgneal@183 14164 function convert(editor, params) {
bgneal@183 14165 dom = editor.dom;
bgneal@183 14166
bgneal@183 14167 if (settings.convert_fonts_to_spans) {
bgneal@183 14168 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
bgneal@183 14169 filters[node.nodeName.toLowerCase()](ed.dom, node);
bgneal@183 14170 });
bgneal@183 14171 }
bgneal@183 14172 };
bgneal@183 14173
bgneal@183 14174 ed.onPreProcess.add(convert);
bgneal@183 14175
bgneal@183 14176 ed.onInit.add(function() {
bgneal@183 14177 ed.selection.onSetContent.add(convert);
bgneal@45 14178 });
bgneal@183 14179 }
bgneal@183 14180 });
bgneal@183 14181