annotate static/js/tiny_mce/tiny_mce_src.js @ 348:d1b11096595b

Fix #168; when nailing a spammer, clear their profile text fields. Guard against topics and forums that don't exist when deleting posts in the signal handler. Make the forum stats template tag only display the latest active users.
author Brian Neal <bgneal@gmail.com>
date Wed, 02 Mar 2011 02:18:28 +0000
parents 88b2b9cb8c1f
children 6c182ceb7147
rev   line source
bgneal@312 1 (function(win) {
bgneal@312 2 var whiteSpaceRe = /^\s*|\s*$/g,
bgneal@312 3 undefined;
bgneal@312 4
bgneal@312 5 var tinymce = {
bgneal@312 6 majorVersion : '3',
bgneal@312 7
bgneal@312 8 minorVersion : '3.9',
bgneal@312 9
bgneal@312 10 releaseDate : '2010-09-08',
bgneal@312 11
bgneal@312 12 _init : function() {
bgneal@312 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
bgneal@312 14
bgneal@312 15 t.isOpera = win.opera && opera.buildNumber;
bgneal@312 16
bgneal@312 17 t.isWebKit = /WebKit/.test(ua);
bgneal@312 18
bgneal@312 19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
bgneal@312 20
bgneal@312 21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
bgneal@312 22
bgneal@312 23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
bgneal@312 24
bgneal@312 25 t.isMac = ua.indexOf('Mac') != -1;
bgneal@312 26
bgneal@312 27 t.isAir = /adobeair/i.test(ua);
bgneal@312 28
bgneal@312 29 t.isIDevice = /(iPad|iPhone)/.test(ua);
bgneal@312 30
bgneal@312 31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
bgneal@312 32 if (win.tinyMCEPreInit) {
bgneal@312 33 t.suffix = tinyMCEPreInit.suffix;
bgneal@312 34 t.baseURL = tinyMCEPreInit.base;
bgneal@312 35 t.query = tinyMCEPreInit.query;
bgneal@312 36 return;
bgneal@312 37 }
bgneal@312 38
bgneal@312 39 // Get suffix and base
bgneal@312 40 t.suffix = '';
bgneal@312 41
bgneal@312 42 // If base element found, add that infront of baseURL
bgneal@312 43 nl = d.getElementsByTagName('base');
bgneal@312 44 for (i=0; i<nl.length; i++) {
bgneal@312 45 if (v = nl[i].href) {
bgneal@312 46 // Host only value like http://site.com or http://site.com:8008
bgneal@312 47 if (/^https?:\/\/[^\/]+$/.test(v))
bgneal@312 48 v += '/';
bgneal@312 49
bgneal@312 50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
bgneal@312 51 }
bgneal@312 52 }
bgneal@312 53
bgneal@312 54 function getBase(n) {
bgneal@312 55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
bgneal@312 56 if (/_(src|dev)\.js/g.test(n.src))
bgneal@312 57 t.suffix = '_src';
bgneal@312 58
bgneal@312 59 if ((p = n.src.indexOf('?')) != -1)
bgneal@312 60 t.query = n.src.substring(p + 1);
bgneal@312 61
bgneal@312 62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
bgneal@312 63
bgneal@312 64 // If path to script is relative and a base href was found add that one infront
bgneal@312 65 // the src property will always be an absolute one on non IE browsers and IE 8
bgneal@312 66 // so this logic will basically only be executed on older IE versions
bgneal@312 67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
bgneal@312 68 t.baseURL = base + t.baseURL;
bgneal@312 69
bgneal@312 70 return t.baseURL;
bgneal@312 71 }
bgneal@312 72
bgneal@312 73 return null;
bgneal@312 74 };
bgneal@312 75
bgneal@312 76 // Check document
bgneal@312 77 nl = d.getElementsByTagName('script');
bgneal@312 78 for (i=0; i<nl.length; i++) {
bgneal@312 79 if (getBase(nl[i]))
bgneal@312 80 return;
bgneal@312 81 }
bgneal@312 82
bgneal@312 83 // Check head
bgneal@312 84 n = d.getElementsByTagName('head')[0];
bgneal@312 85 if (n) {
bgneal@312 86 nl = n.getElementsByTagName('script');
bgneal@312 87 for (i=0; i<nl.length; i++) {
bgneal@312 88 if (getBase(nl[i]))
bgneal@312 89 return;
bgneal@312 90 }
bgneal@312 91 }
bgneal@312 92
bgneal@312 93 return;
bgneal@312 94 },
bgneal@312 95
bgneal@312 96 is : function(o, t) {
bgneal@312 97 if (!t)
bgneal@312 98 return o !== undefined;
bgneal@312 99
bgneal@312 100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
bgneal@312 101 return true;
bgneal@312 102
bgneal@312 103 return typeof(o) == t;
bgneal@312 104 },
bgneal@312 105
bgneal@312 106 each : function(o, cb, s) {
bgneal@312 107 var n, l;
bgneal@312 108
bgneal@312 109 if (!o)
bgneal@312 110 return 0;
bgneal@312 111
bgneal@312 112 s = s || o;
bgneal@312 113
bgneal@312 114 if (o.length !== undefined) {
bgneal@312 115 // Indexed arrays, needed for Safari
bgneal@312 116 for (n=0, l = o.length; n < l; n++) {
bgneal@312 117 if (cb.call(s, o[n], n, o) === false)
bgneal@312 118 return 0;
bgneal@312 119 }
bgneal@312 120 } else {
bgneal@312 121 // Hashtables
bgneal@312 122 for (n in o) {
bgneal@312 123 if (o.hasOwnProperty(n)) {
bgneal@312 124 if (cb.call(s, o[n], n, o) === false)
bgneal@312 125 return 0;
bgneal@312 126 }
bgneal@312 127 }
bgneal@312 128 }
bgneal@312 129
bgneal@312 130 return 1;
bgneal@312 131 },
bgneal@312 132
bgneal@312 133
bgneal@312 134 map : function(a, f) {
bgneal@312 135 var o = [];
bgneal@312 136
bgneal@312 137 tinymce.each(a, function(v) {
bgneal@312 138 o.push(f(v));
bgneal@312 139 });
bgneal@312 140
bgneal@312 141 return o;
bgneal@312 142 },
bgneal@312 143
bgneal@312 144 grep : function(a, f) {
bgneal@312 145 var o = [];
bgneal@312 146
bgneal@312 147 tinymce.each(a, function(v) {
bgneal@312 148 if (!f || f(v))
bgneal@312 149 o.push(v);
bgneal@312 150 });
bgneal@312 151
bgneal@312 152 return o;
bgneal@312 153 },
bgneal@312 154
bgneal@312 155 inArray : function(a, v) {
bgneal@312 156 var i, l;
bgneal@312 157
bgneal@312 158 if (a) {
bgneal@312 159 for (i = 0, l = a.length; i < l; i++) {
bgneal@312 160 if (a[i] === v)
bgneal@312 161 return i;
bgneal@312 162 }
bgneal@312 163 }
bgneal@312 164
bgneal@312 165 return -1;
bgneal@312 166 },
bgneal@312 167
bgneal@312 168 extend : function(o, e) {
bgneal@312 169 var i, l, a = arguments;
bgneal@312 170
bgneal@312 171 for (i = 1, l = a.length; i < l; i++) {
bgneal@312 172 e = a[i];
bgneal@312 173
bgneal@312 174 tinymce.each(e, function(v, n) {
bgneal@312 175 if (v !== undefined)
bgneal@312 176 o[n] = v;
bgneal@312 177 });
bgneal@312 178 }
bgneal@312 179
bgneal@312 180 return o;
bgneal@312 181 },
bgneal@312 182
bgneal@312 183
bgneal@312 184 trim : function(s) {
bgneal@312 185 return (s ? '' + s : '').replace(whiteSpaceRe, '');
bgneal@312 186 },
bgneal@312 187
bgneal@312 188 create : function(s, p) {
bgneal@312 189 var t = this, sp, ns, cn, scn, c, de = 0;
bgneal@312 190
bgneal@312 191 // Parse : <prefix> <class>:<super class>
bgneal@312 192 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
bgneal@312 193 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
bgneal@312 194
bgneal@312 195 // Create namespace for new class
bgneal@312 196 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
bgneal@312 197
bgneal@312 198 // Class already exists
bgneal@312 199 if (ns[cn])
bgneal@312 200 return;
bgneal@312 201
bgneal@312 202 // Make pure static class
bgneal@312 203 if (s[2] == 'static') {
bgneal@312 204 ns[cn] = p;
bgneal@312 205
bgneal@312 206 if (this.onCreate)
bgneal@312 207 this.onCreate(s[2], s[3], ns[cn]);
bgneal@312 208
bgneal@312 209 return;
bgneal@312 210 }
bgneal@312 211
bgneal@312 212 // Create default constructor
bgneal@312 213 if (!p[cn]) {
bgneal@312 214 p[cn] = function() {};
bgneal@312 215 de = 1;
bgneal@312 216 }
bgneal@312 217
bgneal@312 218 // Add constructor and methods
bgneal@312 219 ns[cn] = p[cn];
bgneal@312 220 t.extend(ns[cn].prototype, p);
bgneal@312 221
bgneal@312 222 // Extend
bgneal@312 223 if (s[5]) {
bgneal@312 224 sp = t.resolve(s[5]).prototype;
bgneal@312 225 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
bgneal@312 226
bgneal@312 227 // Extend constructor
bgneal@312 228 c = ns[cn];
bgneal@312 229 if (de) {
bgneal@312 230 // Add passthrough constructor
bgneal@312 231 ns[cn] = function() {
bgneal@312 232 return sp[scn].apply(this, arguments);
bgneal@312 233 };
bgneal@312 234 } else {
bgneal@312 235 // Add inherit constructor
bgneal@312 236 ns[cn] = function() {
bgneal@312 237 this.parent = sp[scn];
bgneal@312 238 return c.apply(this, arguments);
bgneal@312 239 };
bgneal@312 240 }
bgneal@312 241 ns[cn].prototype[cn] = ns[cn];
bgneal@312 242
bgneal@312 243 // Add super methods
bgneal@312 244 t.each(sp, function(f, n) {
bgneal@312 245 ns[cn].prototype[n] = sp[n];
bgneal@312 246 });
bgneal@312 247
bgneal@312 248 // Add overridden methods
bgneal@312 249 t.each(p, function(f, n) {
bgneal@312 250 // Extend methods if needed
bgneal@312 251 if (sp[n]) {
bgneal@312 252 ns[cn].prototype[n] = function() {
bgneal@312 253 this.parent = sp[n];
bgneal@312 254 return f.apply(this, arguments);
bgneal@312 255 };
bgneal@312 256 } else {
bgneal@312 257 if (n != cn)
bgneal@312 258 ns[cn].prototype[n] = f;
bgneal@312 259 }
bgneal@312 260 });
bgneal@312 261 }
bgneal@312 262
bgneal@312 263 // Add static methods
bgneal@312 264 t.each(p['static'], function(f, n) {
bgneal@312 265 ns[cn][n] = f;
bgneal@312 266 });
bgneal@312 267
bgneal@312 268 if (this.onCreate)
bgneal@312 269 this.onCreate(s[2], s[3], ns[cn].prototype);
bgneal@312 270 },
bgneal@312 271
bgneal@312 272 walk : function(o, f, n, s) {
bgneal@312 273 s = s || this;
bgneal@312 274
bgneal@312 275 if (o) {
bgneal@312 276 if (n)
bgneal@312 277 o = o[n];
bgneal@312 278
bgneal@312 279 tinymce.each(o, function(o, i) {
bgneal@312 280 if (f.call(s, o, i, n) === false)
bgneal@312 281 return false;
bgneal@312 282
bgneal@312 283 tinymce.walk(o, f, n, s);
bgneal@312 284 });
bgneal@312 285 }
bgneal@312 286 },
bgneal@312 287
bgneal@312 288 createNS : function(n, o) {
bgneal@312 289 var i, v;
bgneal@312 290
bgneal@312 291 o = o || win;
bgneal@312 292
bgneal@312 293 n = n.split('.');
bgneal@312 294 for (i=0; i<n.length; i++) {
bgneal@312 295 v = n[i];
bgneal@312 296
bgneal@312 297 if (!o[v])
bgneal@312 298 o[v] = {};
bgneal@312 299
bgneal@312 300 o = o[v];
bgneal@312 301 }
bgneal@312 302
bgneal@312 303 return o;
bgneal@312 304 },
bgneal@312 305
bgneal@312 306 resolve : function(n, o) {
bgneal@312 307 var i, l;
bgneal@312 308
bgneal@312 309 o = o || win;
bgneal@312 310
bgneal@312 311 n = n.split('.');
bgneal@312 312 for (i = 0, l = n.length; i < l; i++) {
bgneal@312 313 o = o[n[i]];
bgneal@312 314
bgneal@312 315 if (!o)
bgneal@312 316 break;
bgneal@312 317 }
bgneal@312 318
bgneal@312 319 return o;
bgneal@312 320 },
bgneal@312 321
bgneal@312 322 addUnload : function(f, s) {
bgneal@312 323 var t = this;
bgneal@312 324
bgneal@312 325 f = {func : f, scope : s || this};
bgneal@312 326
bgneal@312 327 if (!t.unloads) {
bgneal@312 328 function unload() {
bgneal@312 329 var li = t.unloads, o, n;
bgneal@312 330
bgneal@312 331 if (li) {
bgneal@312 332 // Call unload handlers
bgneal@312 333 for (n in li) {
bgneal@312 334 o = li[n];
bgneal@312 335
bgneal@312 336 if (o && o.func)
bgneal@312 337 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
bgneal@312 338 }
bgneal@312 339
bgneal@312 340 // Detach unload function
bgneal@312 341 if (win.detachEvent) {
bgneal@312 342 win.detachEvent('onbeforeunload', fakeUnload);
bgneal@312 343 win.detachEvent('onunload', unload);
bgneal@312 344 } else if (win.removeEventListener)
bgneal@312 345 win.removeEventListener('unload', unload, false);
bgneal@312 346
bgneal@312 347 // Destroy references
bgneal@312 348 t.unloads = o = li = w = unload = 0;
bgneal@312 349
bgneal@312 350 // Run garbarge collector on IE
bgneal@312 351 if (win.CollectGarbage)
bgneal@312 352 CollectGarbage();
bgneal@312 353 }
bgneal@312 354 };
bgneal@312 355
bgneal@312 356 function fakeUnload() {
bgneal@312 357 var d = document;
bgneal@312 358
bgneal@312 359 // Is there things still loading, then do some magic
bgneal@312 360 if (d.readyState == 'interactive') {
bgneal@312 361 function stop() {
bgneal@312 362 // Prevent memory leak
bgneal@312 363 d.detachEvent('onstop', stop);
bgneal@312 364
bgneal@312 365 // Call unload handler
bgneal@312 366 if (unload)
bgneal@312 367 unload();
bgneal@312 368
bgneal@312 369 d = 0;
bgneal@312 370 };
bgneal@312 371
bgneal@312 372 // Fire unload when the currently loading page is stopped
bgneal@312 373 if (d)
bgneal@312 374 d.attachEvent('onstop', stop);
bgneal@312 375
bgneal@312 376 // Remove onstop listener after a while to prevent the unload function
bgneal@312 377 // to execute if the user presses cancel in an onbeforeunload
bgneal@312 378 // confirm dialog and then presses the browser stop button
bgneal@312 379 win.setTimeout(function() {
bgneal@312 380 if (d)
bgneal@312 381 d.detachEvent('onstop', stop);
bgneal@312 382 }, 0);
bgneal@312 383 }
bgneal@312 384 };
bgneal@312 385
bgneal@312 386 // Attach unload handler
bgneal@312 387 if (win.attachEvent) {
bgneal@312 388 win.attachEvent('onunload', unload);
bgneal@312 389 win.attachEvent('onbeforeunload', fakeUnload);
bgneal@312 390 } else if (win.addEventListener)
bgneal@312 391 win.addEventListener('unload', unload, false);
bgneal@312 392
bgneal@312 393 // Setup initial unload handler array
bgneal@312 394 t.unloads = [f];
bgneal@312 395 } else
bgneal@312 396 t.unloads.push(f);
bgneal@312 397
bgneal@312 398 return f;
bgneal@312 399 },
bgneal@312 400
bgneal@312 401 removeUnload : function(f) {
bgneal@312 402 var u = this.unloads, r = null;
bgneal@312 403
bgneal@312 404 tinymce.each(u, function(o, i) {
bgneal@312 405 if (o && o.func == f) {
bgneal@312 406 u.splice(i, 1);
bgneal@312 407 r = f;
bgneal@312 408 return false;
bgneal@312 409 }
bgneal@312 410 });
bgneal@312 411
bgneal@312 412 return r;
bgneal@312 413 },
bgneal@312 414
bgneal@312 415 explode : function(s, d) {
bgneal@312 416 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
bgneal@312 417 },
bgneal@312 418
bgneal@312 419 _addVer : function(u) {
bgneal@312 420 var v;
bgneal@312 421
bgneal@312 422 if (!this.query)
bgneal@312 423 return u;
bgneal@312 424
bgneal@312 425 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
bgneal@312 426
bgneal@312 427 if (u.indexOf('#') == -1)
bgneal@312 428 return u + v;
bgneal@312 429
bgneal@312 430 return u.replace('#', v + '#');
bgneal@312 431 }
bgneal@312 432
bgneal@312 433 };
bgneal@312 434
bgneal@312 435 // Initialize the API
bgneal@312 436 tinymce._init();
bgneal@312 437
bgneal@312 438 // Expose tinymce namespace to the global namespace (window)
bgneal@312 439 win.tinymce = win.tinyMCE = tinymce;
bgneal@312 440 })(window);
bgneal@312 441
bgneal@312 442
bgneal@312 443 tinymce.create('tinymce.util.Dispatcher', {
bgneal@312 444 scope : null,
bgneal@312 445 listeners : null,
bgneal@312 446
bgneal@312 447 Dispatcher : function(s) {
bgneal@312 448 this.scope = s || this;
bgneal@312 449 this.listeners = [];
bgneal@312 450 },
bgneal@312 451
bgneal@312 452 add : function(cb, s) {
bgneal@312 453 this.listeners.push({cb : cb, scope : s || this.scope});
bgneal@312 454
bgneal@312 455 return cb;
bgneal@312 456 },
bgneal@312 457
bgneal@312 458 addToTop : function(cb, s) {
bgneal@312 459 this.listeners.unshift({cb : cb, scope : s || this.scope});
bgneal@312 460
bgneal@312 461 return cb;
bgneal@312 462 },
bgneal@312 463
bgneal@312 464 remove : function(cb) {
bgneal@312 465 var l = this.listeners, o = null;
bgneal@312 466
bgneal@312 467 tinymce.each(l, function(c, i) {
bgneal@312 468 if (cb == c.cb) {
bgneal@312 469 o = cb;
bgneal@312 470 l.splice(i, 1);
bgneal@312 471 return false;
bgneal@312 472 }
bgneal@312 473 });
bgneal@312 474
bgneal@312 475 return o;
bgneal@312 476 },
bgneal@312 477
bgneal@312 478 dispatch : function() {
bgneal@312 479 var s, a = arguments, i, li = this.listeners, c;
bgneal@312 480
bgneal@312 481 // Needs to be a real loop since the listener count might change while looping
bgneal@312 482 // And this is also more efficient
bgneal@312 483 for (i = 0; i<li.length; i++) {
bgneal@312 484 c = li[i];
bgneal@312 485 s = c.cb.apply(c.scope, a);
bgneal@312 486
bgneal@312 487 if (s === false)
bgneal@312 488 break;
bgneal@312 489 }
bgneal@312 490
bgneal@312 491 return s;
bgneal@312 492 }
bgneal@312 493
bgneal@312 494 });
bgneal@312 495
bgneal@312 496 (function() {
bgneal@312 497 var each = tinymce.each;
bgneal@312 498
bgneal@312 499 tinymce.create('tinymce.util.URI', {
bgneal@312 500 URI : function(u, s) {
bgneal@312 501 var t = this, o, a, b;
bgneal@312 502
bgneal@312 503 // Trim whitespace
bgneal@312 504 u = tinymce.trim(u);
bgneal@312 505
bgneal@312 506 // Default settings
bgneal@312 507 s = t.settings = s || {};
bgneal@312 508
bgneal@312 509 // Strange app protocol or local anchor
bgneal@312 510 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
bgneal@312 511 t.source = u;
bgneal@312 512 return;
bgneal@312 513 }
bgneal@312 514
bgneal@312 515 // Absolute path with no host, fake host and protocol
bgneal@312 516 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
bgneal@312 517 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
bgneal@312 518
bgneal@312 519 // Relative path http:// or protocol relative //path
bgneal@312 520 if (!/^\w*:?\/\//.test(u))
bgneal@312 521 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
bgneal@312 522
bgneal@312 523 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
bgneal@312 524 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
bgneal@312 525 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
bgneal@312 526 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
bgneal@312 527 var s = u[i];
bgneal@312 528
bgneal@312 529 // Zope 3 workaround, they use @@something
bgneal@312 530 if (s)
bgneal@312 531 s = s.replace(/\(mce_at\)/g, '@@');
bgneal@312 532
bgneal@312 533 t[v] = s;
bgneal@312 534 });
bgneal@312 535
bgneal@312 536 if (b = s.base_uri) {
bgneal@312 537 if (!t.protocol)
bgneal@312 538 t.protocol = b.protocol;
bgneal@312 539
bgneal@312 540 if (!t.userInfo)
bgneal@312 541 t.userInfo = b.userInfo;
bgneal@312 542
bgneal@312 543 if (!t.port && t.host == 'mce_host')
bgneal@312 544 t.port = b.port;
bgneal@312 545
bgneal@312 546 if (!t.host || t.host == 'mce_host')
bgneal@312 547 t.host = b.host;
bgneal@312 548
bgneal@312 549 t.source = '';
bgneal@312 550 }
bgneal@312 551
bgneal@312 552 //t.path = t.path || '/';
bgneal@312 553 },
bgneal@312 554
bgneal@312 555 setPath : function(p) {
bgneal@312 556 var t = this;
bgneal@312 557
bgneal@312 558 p = /^(.*?)\/?(\w+)?$/.exec(p);
bgneal@312 559
bgneal@312 560 // Update path parts
bgneal@312 561 t.path = p[0];
bgneal@312 562 t.directory = p[1];
bgneal@312 563 t.file = p[2];
bgneal@312 564
bgneal@312 565 // Rebuild source
bgneal@312 566 t.source = '';
bgneal@312 567 t.getURI();
bgneal@312 568 },
bgneal@312 569
bgneal@312 570 toRelative : function(u) {
bgneal@312 571 var t = this, o;
bgneal@312 572
bgneal@312 573 if (u === "./")
bgneal@312 574 return u;
bgneal@312 575
bgneal@312 576 u = new tinymce.util.URI(u, {base_uri : t});
bgneal@312 577
bgneal@312 578 // Not on same domain/port or protocol
bgneal@312 579 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
bgneal@312 580 return u.getURI();
bgneal@312 581
bgneal@312 582 o = t.toRelPath(t.path, u.path);
bgneal@312 583
bgneal@312 584 // Add query
bgneal@312 585 if (u.query)
bgneal@312 586 o += '?' + u.query;
bgneal@312 587
bgneal@312 588 // Add anchor
bgneal@312 589 if (u.anchor)
bgneal@312 590 o += '#' + u.anchor;
bgneal@312 591
bgneal@312 592 return o;
bgneal@312 593 },
bgneal@312 594
bgneal@312 595 toAbsolute : function(u, nh) {
bgneal@312 596 var u = new tinymce.util.URI(u, {base_uri : this});
bgneal@312 597
bgneal@312 598 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
bgneal@312 599 },
bgneal@312 600
bgneal@312 601 toRelPath : function(base, path) {
bgneal@312 602 var items, bp = 0, out = '', i, l;
bgneal@312 603
bgneal@312 604 // Split the paths
bgneal@312 605 base = base.substring(0, base.lastIndexOf('/'));
bgneal@312 606 base = base.split('/');
bgneal@312 607 items = path.split('/');
bgneal@312 608
bgneal@312 609 if (base.length >= items.length) {
bgneal@312 610 for (i = 0, l = base.length; i < l; i++) {
bgneal@312 611 if (i >= items.length || base[i] != items[i]) {
bgneal@312 612 bp = i + 1;
bgneal@312 613 break;
bgneal@312 614 }
bgneal@312 615 }
bgneal@312 616 }
bgneal@312 617
bgneal@312 618 if (base.length < items.length) {
bgneal@312 619 for (i = 0, l = items.length; i < l; i++) {
bgneal@312 620 if (i >= base.length || base[i] != items[i]) {
bgneal@312 621 bp = i + 1;
bgneal@312 622 break;
bgneal@312 623 }
bgneal@312 624 }
bgneal@312 625 }
bgneal@312 626
bgneal@312 627 if (bp == 1)
bgneal@312 628 return path;
bgneal@312 629
bgneal@312 630 for (i = 0, l = base.length - (bp - 1); i < l; i++)
bgneal@312 631 out += "../";
bgneal@312 632
bgneal@312 633 for (i = bp - 1, l = items.length; i < l; i++) {
bgneal@312 634 if (i != bp - 1)
bgneal@312 635 out += "/" + items[i];
bgneal@312 636 else
bgneal@312 637 out += items[i];
bgneal@312 638 }
bgneal@312 639
bgneal@312 640 return out;
bgneal@312 641 },
bgneal@312 642
bgneal@312 643 toAbsPath : function(base, path) {
bgneal@312 644 var i, nb = 0, o = [], tr, outPath;
bgneal@312 645
bgneal@312 646 // Split paths
bgneal@312 647 tr = /\/$/.test(path) ? '/' : '';
bgneal@312 648 base = base.split('/');
bgneal@312 649 path = path.split('/');
bgneal@312 650
bgneal@312 651 // Remove empty chunks
bgneal@312 652 each(base, function(k) {
bgneal@312 653 if (k)
bgneal@312 654 o.push(k);
bgneal@312 655 });
bgneal@312 656
bgneal@312 657 base = o;
bgneal@312 658
bgneal@312 659 // Merge relURLParts chunks
bgneal@312 660 for (i = path.length - 1, o = []; i >= 0; i--) {
bgneal@312 661 // Ignore empty or .
bgneal@312 662 if (path[i].length == 0 || path[i] == ".")
bgneal@312 663 continue;
bgneal@312 664
bgneal@312 665 // Is parent
bgneal@312 666 if (path[i] == '..') {
bgneal@312 667 nb++;
bgneal@312 668 continue;
bgneal@312 669 }
bgneal@312 670
bgneal@312 671 // Move up
bgneal@312 672 if (nb > 0) {
bgneal@312 673 nb--;
bgneal@312 674 continue;
bgneal@312 675 }
bgneal@312 676
bgneal@312 677 o.push(path[i]);
bgneal@312 678 }
bgneal@312 679
bgneal@312 680 i = base.length - nb;
bgneal@312 681
bgneal@312 682 // If /a/b/c or /
bgneal@312 683 if (i <= 0)
bgneal@312 684 outPath = o.reverse().join('/');
bgneal@312 685 else
bgneal@312 686 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
bgneal@312 687
bgneal@312 688 // Add front / if it's needed
bgneal@312 689 if (outPath.indexOf('/') !== 0)
bgneal@312 690 outPath = '/' + outPath;
bgneal@312 691
bgneal@312 692 // Add traling / if it's needed
bgneal@312 693 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
bgneal@312 694 outPath += tr;
bgneal@312 695
bgneal@312 696 return outPath;
bgneal@312 697 },
bgneal@312 698
bgneal@312 699 getURI : function(nh) {
bgneal@312 700 var s, t = this;
bgneal@312 701
bgneal@312 702 // Rebuild source
bgneal@312 703 if (!t.source || nh) {
bgneal@312 704 s = '';
bgneal@312 705
bgneal@312 706 if (!nh) {
bgneal@312 707 if (t.protocol)
bgneal@312 708 s += t.protocol + '://';
bgneal@312 709
bgneal@312 710 if (t.userInfo)
bgneal@312 711 s += t.userInfo + '@';
bgneal@312 712
bgneal@312 713 if (t.host)
bgneal@312 714 s += t.host;
bgneal@312 715
bgneal@312 716 if (t.port)
bgneal@312 717 s += ':' + t.port;
bgneal@312 718 }
bgneal@312 719
bgneal@312 720 if (t.path)
bgneal@312 721 s += t.path;
bgneal@312 722
bgneal@312 723 if (t.query)
bgneal@312 724 s += '?' + t.query;
bgneal@312 725
bgneal@312 726 if (t.anchor)
bgneal@312 727 s += '#' + t.anchor;
bgneal@312 728
bgneal@312 729 t.source = s;
bgneal@312 730 }
bgneal@312 731
bgneal@312 732 return t.source;
bgneal@312 733 }
bgneal@312 734 });
bgneal@312 735 })();
bgneal@312 736
bgneal@312 737 (function() {
bgneal@312 738 var each = tinymce.each;
bgneal@312 739
bgneal@312 740 tinymce.create('static tinymce.util.Cookie', {
bgneal@312 741 getHash : function(n) {
bgneal@312 742 var v = this.get(n), h;
bgneal@312 743
bgneal@312 744 if (v) {
bgneal@312 745 each(v.split('&'), function(v) {
bgneal@312 746 v = v.split('=');
bgneal@312 747 h = h || {};
bgneal@312 748 h[unescape(v[0])] = unescape(v[1]);
bgneal@312 749 });
bgneal@312 750 }
bgneal@312 751
bgneal@312 752 return h;
bgneal@312 753 },
bgneal@312 754
bgneal@312 755 setHash : function(n, v, e, p, d, s) {
bgneal@312 756 var o = '';
bgneal@312 757
bgneal@312 758 each(v, function(v, k) {
bgneal@312 759 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
bgneal@312 760 });
bgneal@312 761
bgneal@312 762 this.set(n, o, e, p, d, s);
bgneal@312 763 },
bgneal@312 764
bgneal@312 765 get : function(n) {
bgneal@312 766 var c = document.cookie, e, p = n + "=", b;
bgneal@312 767
bgneal@312 768 // Strict mode
bgneal@312 769 if (!c)
bgneal@312 770 return;
bgneal@312 771
bgneal@312 772 b = c.indexOf("; " + p);
bgneal@312 773
bgneal@312 774 if (b == -1) {
bgneal@312 775 b = c.indexOf(p);
bgneal@312 776
bgneal@312 777 if (b != 0)
bgneal@312 778 return null;
bgneal@312 779 } else
bgneal@312 780 b += 2;
bgneal@312 781
bgneal@312 782 e = c.indexOf(";", b);
bgneal@312 783
bgneal@312 784 if (e == -1)
bgneal@312 785 e = c.length;
bgneal@312 786
bgneal@312 787 return unescape(c.substring(b + p.length, e));
bgneal@312 788 },
bgneal@312 789
bgneal@312 790 set : function(n, v, e, p, d, s) {
bgneal@312 791 document.cookie = n + "=" + escape(v) +
bgneal@312 792 ((e) ? "; expires=" + e.toGMTString() : "") +
bgneal@312 793 ((p) ? "; path=" + escape(p) : "") +
bgneal@312 794 ((d) ? "; domain=" + d : "") +
bgneal@312 795 ((s) ? "; secure" : "");
bgneal@312 796 },
bgneal@312 797
bgneal@312 798 remove : function(n, p) {
bgneal@312 799 var d = new Date();
bgneal@312 800
bgneal@312 801 d.setTime(d.getTime() - 1000);
bgneal@312 802
bgneal@312 803 this.set(n, '', d, p, d);
bgneal@312 804 }
bgneal@312 805 });
bgneal@312 806 })();
bgneal@312 807
bgneal@312 808 tinymce.create('static tinymce.util.JSON', {
bgneal@312 809 serialize : function(o) {
bgneal@312 810 var i, v, s = tinymce.util.JSON.serialize, t;
bgneal@312 811
bgneal@312 812 if (o == null)
bgneal@312 813 return 'null';
bgneal@312 814
bgneal@312 815 t = typeof o;
bgneal@312 816
bgneal@312 817 if (t == 'string') {
bgneal@312 818 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
bgneal@312 819
bgneal@312 820 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
bgneal@312 821 i = v.indexOf(b);
bgneal@312 822
bgneal@312 823 if (i + 1)
bgneal@312 824 return '\\' + v.charAt(i + 1);
bgneal@312 825
bgneal@312 826 a = b.charCodeAt().toString(16);
bgneal@312 827
bgneal@312 828 return '\\u' + '0000'.substring(a.length) + a;
bgneal@312 829 }) + '"';
bgneal@312 830 }
bgneal@312 831
bgneal@312 832 if (t == 'object') {
bgneal@312 833 if (o.hasOwnProperty && o instanceof Array) {
bgneal@312 834 for (i=0, v = '['; i<o.length; i++)
bgneal@312 835 v += (i > 0 ? ',' : '') + s(o[i]);
bgneal@312 836
bgneal@312 837 return v + ']';
bgneal@312 838 }
bgneal@312 839
bgneal@312 840 v = '{';
bgneal@312 841
bgneal@312 842 for (i in o)
bgneal@312 843 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
bgneal@312 844
bgneal@312 845 return v + '}';
bgneal@312 846 }
bgneal@312 847
bgneal@312 848 return '' + o;
bgneal@312 849 },
bgneal@312 850
bgneal@312 851 parse : function(s) {
bgneal@312 852 try {
bgneal@312 853 return eval('(' + s + ')');
bgneal@312 854 } catch (ex) {
bgneal@312 855 // Ignore
bgneal@312 856 }
bgneal@312 857 }
bgneal@312 858
bgneal@312 859 });
bgneal@312 860
bgneal@312 861 tinymce.create('static tinymce.util.XHR', {
bgneal@312 862 send : function(o) {
bgneal@312 863 var x, t, w = window, c = 0;
bgneal@312 864
bgneal@312 865 // Default settings
bgneal@312 866 o.scope = o.scope || this;
bgneal@312 867 o.success_scope = o.success_scope || o.scope;
bgneal@312 868 o.error_scope = o.error_scope || o.scope;
bgneal@312 869 o.async = o.async === false ? false : true;
bgneal@312 870 o.data = o.data || '';
bgneal@312 871
bgneal@312 872 function get(s) {
bgneal@312 873 x = 0;
bgneal@312 874
bgneal@312 875 try {
bgneal@312 876 x = new ActiveXObject(s);
bgneal@312 877 } catch (ex) {
bgneal@312 878 }
bgneal@312 879
bgneal@312 880 return x;
bgneal@312 881 };
bgneal@312 882
bgneal@312 883 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
bgneal@312 884
bgneal@312 885 if (x) {
bgneal@312 886 if (x.overrideMimeType)
bgneal@312 887 x.overrideMimeType(o.content_type);
bgneal@312 888
bgneal@312 889 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
bgneal@312 890
bgneal@312 891 if (o.content_type)
bgneal@312 892 x.setRequestHeader('Content-Type', o.content_type);
bgneal@312 893
bgneal@312 894 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bgneal@312 895
bgneal@312 896 x.send(o.data);
bgneal@312 897
bgneal@312 898 function ready() {
bgneal@312 899 if (!o.async || x.readyState == 4 || c++ > 10000) {
bgneal@312 900 if (o.success && c < 10000 && x.status == 200)
bgneal@312 901 o.success.call(o.success_scope, '' + x.responseText, x, o);
bgneal@312 902 else if (o.error)
bgneal@312 903 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
bgneal@312 904
bgneal@312 905 x = null;
bgneal@312 906 } else
bgneal@312 907 w.setTimeout(ready, 10);
bgneal@312 908 };
bgneal@312 909
bgneal@312 910 // Syncronous request
bgneal@312 911 if (!o.async)
bgneal@312 912 return ready();
bgneal@312 913
bgneal@312 914 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
bgneal@312 915 t = w.setTimeout(ready, 10);
bgneal@312 916 }
bgneal@312 917 }
bgneal@312 918 });
bgneal@312 919
bgneal@312 920 (function() {
bgneal@312 921 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
bgneal@312 922
bgneal@312 923 tinymce.create('tinymce.util.JSONRequest', {
bgneal@312 924 JSONRequest : function(s) {
bgneal@312 925 this.settings = extend({
bgneal@312 926 }, s);
bgneal@312 927 this.count = 0;
bgneal@312 928 },
bgneal@312 929
bgneal@312 930 send : function(o) {
bgneal@312 931 var ecb = o.error, scb = o.success;
bgneal@312 932
bgneal@312 933 o = extend(this.settings, o);
bgneal@312 934
bgneal@312 935 o.success = function(c, x) {
bgneal@312 936 c = JSON.parse(c);
bgneal@312 937
bgneal@312 938 if (typeof(c) == 'undefined') {
bgneal@312 939 c = {
bgneal@312 940 error : 'JSON Parse error.'
bgneal@312 941 };
bgneal@312 942 }
bgneal@312 943
bgneal@312 944 if (c.error)
bgneal@312 945 ecb.call(o.error_scope || o.scope, c.error, x);
bgneal@312 946 else
bgneal@312 947 scb.call(o.success_scope || o.scope, c.result);
bgneal@312 948 };
bgneal@312 949
bgneal@312 950 o.error = function(ty, x) {
bgneal@312 951 ecb.call(o.error_scope || o.scope, ty, x);
bgneal@312 952 };
bgneal@312 953
bgneal@312 954 o.data = JSON.serialize({
bgneal@312 955 id : o.id || 'c' + (this.count++),
bgneal@312 956 method : o.method,
bgneal@312 957 params : o.params
bgneal@312 958 });
bgneal@312 959
bgneal@312 960 // JSON content type for Ruby on rails. Bug: #1883287
bgneal@312 961 o.content_type = 'application/json';
bgneal@312 962
bgneal@312 963 XHR.send(o);
bgneal@312 964 },
bgneal@312 965
bgneal@312 966 'static' : {
bgneal@312 967 sendRPC : function(o) {
bgneal@312 968 return new tinymce.util.JSONRequest().send(o);
bgneal@312 969 }
bgneal@312 970 }
bgneal@312 971 });
bgneal@312 972 }());
bgneal@312 973 (function(tinymce) {
bgneal@312 974 // Shorten names
bgneal@312 975 var each = tinymce.each,
bgneal@312 976 is = tinymce.is,
bgneal@312 977 isWebKit = tinymce.isWebKit,
bgneal@312 978 isIE = tinymce.isIE,
bgneal@312 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@312 980 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
bgneal@312 981 mceAttribs = makeMap('src,href,style,coords,shape'),
bgneal@312 982 encodedChars = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'},
bgneal@312 983 encodeCharsRe = /[<>&\"]/g,
bgneal@312 984 simpleSelectorRe = /^([a-z0-9],?)+$/i,
bgneal@312 985 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
bgneal@312 986 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
bgneal@312 987
bgneal@312 988 function makeMap(str) {
bgneal@312 989 var map = {}, i;
bgneal@312 990
bgneal@312 991 str = str.split(',');
bgneal@312 992 for (i = str.length; i >= 0; i--)
bgneal@312 993 map[str[i]] = 1;
bgneal@312 994
bgneal@312 995 return map;
bgneal@312 996 };
bgneal@312 997
bgneal@312 998 tinymce.create('tinymce.dom.DOMUtils', {
bgneal@312 999 doc : null,
bgneal@312 1000 root : null,
bgneal@312 1001 files : null,
bgneal@312 1002 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
bgneal@312 1003 props : {
bgneal@312 1004 "for" : "htmlFor",
bgneal@312 1005 "class" : "className",
bgneal@312 1006 className : "className",
bgneal@312 1007 checked : "checked",
bgneal@312 1008 disabled : "disabled",
bgneal@312 1009 maxlength : "maxLength",
bgneal@312 1010 readonly : "readOnly",
bgneal@312 1011 selected : "selected",
bgneal@312 1012 value : "value",
bgneal@312 1013 id : "id",
bgneal@312 1014 name : "name",
bgneal@312 1015 type : "type"
bgneal@312 1016 },
bgneal@312 1017
bgneal@312 1018 DOMUtils : function(d, s) {
bgneal@312 1019 var t = this, globalStyle;
bgneal@312 1020
bgneal@312 1021 t.doc = d;
bgneal@312 1022 t.win = window;
bgneal@312 1023 t.files = {};
bgneal@312 1024 t.cssFlicker = false;
bgneal@312 1025 t.counter = 0;
bgneal@312 1026 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
bgneal@312 1027 t.stdMode = d.documentMode === 8;
bgneal@312 1028
bgneal@312 1029 t.settings = s = tinymce.extend({
bgneal@312 1030 keep_values : false,
bgneal@312 1031 hex_colors : 1,
bgneal@312 1032 process_html : 1
bgneal@312 1033 }, s);
bgneal@312 1034
bgneal@312 1035 // Fix IE6SP2 flicker and check it failed for pre SP2
bgneal@312 1036 if (tinymce.isIE6) {
bgneal@312 1037 try {
bgneal@312 1038 d.execCommand('BackgroundImageCache', false, true);
bgneal@312 1039 } catch (e) {
bgneal@312 1040 t.cssFlicker = true;
bgneal@312 1041 }
bgneal@312 1042 }
bgneal@312 1043
bgneal@312 1044 // Build styles list
bgneal@312 1045 if (s.valid_styles) {
bgneal@312 1046 t._styles = {};
bgneal@312 1047
bgneal@312 1048 // Convert styles into a rule list
bgneal@312 1049 each(s.valid_styles, function(value, key) {
bgneal@312 1050 t._styles[key] = tinymce.explode(value);
bgneal@312 1051 });
bgneal@312 1052 }
bgneal@312 1053
bgneal@312 1054 tinymce.addUnload(t.destroy, t);
bgneal@312 1055 },
bgneal@312 1056
bgneal@312 1057 getRoot : function() {
bgneal@312 1058 var t = this, s = t.settings;
bgneal@312 1059
bgneal@312 1060 return (s && t.get(s.root_element)) || t.doc.body;
bgneal@312 1061 },
bgneal@312 1062
bgneal@312 1063 getViewPort : function(w) {
bgneal@312 1064 var d, b;
bgneal@312 1065
bgneal@312 1066 w = !w ? this.win : w;
bgneal@312 1067 d = w.document;
bgneal@312 1068 b = this.boxModel ? d.documentElement : d.body;
bgneal@312 1069
bgneal@312 1070 // Returns viewport size excluding scrollbars
bgneal@312 1071 return {
bgneal@312 1072 x : w.pageXOffset || b.scrollLeft,
bgneal@312 1073 y : w.pageYOffset || b.scrollTop,
bgneal@312 1074 w : w.innerWidth || b.clientWidth,
bgneal@312 1075 h : w.innerHeight || b.clientHeight
bgneal@312 1076 };
bgneal@312 1077 },
bgneal@312 1078
bgneal@312 1079 getRect : function(e) {
bgneal@312 1080 var p, t = this, sr;
bgneal@312 1081
bgneal@312 1082 e = t.get(e);
bgneal@312 1083 p = t.getPos(e);
bgneal@312 1084 sr = t.getSize(e);
bgneal@312 1085
bgneal@312 1086 return {
bgneal@312 1087 x : p.x,
bgneal@312 1088 y : p.y,
bgneal@312 1089 w : sr.w,
bgneal@312 1090 h : sr.h
bgneal@312 1091 };
bgneal@312 1092 },
bgneal@312 1093
bgneal@312 1094 getSize : function(e) {
bgneal@312 1095 var t = this, w, h;
bgneal@312 1096
bgneal@312 1097 e = t.get(e);
bgneal@312 1098 w = t.getStyle(e, 'width');
bgneal@312 1099 h = t.getStyle(e, 'height');
bgneal@312 1100
bgneal@312 1101 // Non pixel value, then force offset/clientWidth
bgneal@312 1102 if (w.indexOf('px') === -1)
bgneal@312 1103 w = 0;
bgneal@312 1104
bgneal@312 1105 // Non pixel value, then force offset/clientWidth
bgneal@312 1106 if (h.indexOf('px') === -1)
bgneal@312 1107 h = 0;
bgneal@312 1108
bgneal@312 1109 return {
bgneal@312 1110 w : parseInt(w) || e.offsetWidth || e.clientWidth,
bgneal@312 1111 h : parseInt(h) || e.offsetHeight || e.clientHeight
bgneal@312 1112 };
bgneal@312 1113 },
bgneal@312 1114
bgneal@312 1115 getParent : function(n, f, r) {
bgneal@312 1116 return this.getParents(n, f, r, false);
bgneal@312 1117 },
bgneal@312 1118
bgneal@312 1119 getParents : function(n, f, r, c) {
bgneal@312 1120 var t = this, na, se = t.settings, o = [];
bgneal@312 1121
bgneal@312 1122 n = t.get(n);
bgneal@312 1123 c = c === undefined;
bgneal@312 1124
bgneal@312 1125 if (se.strict_root)
bgneal@312 1126 r = r || t.getRoot();
bgneal@312 1127
bgneal@312 1128 // Wrap node name as func
bgneal@312 1129 if (is(f, 'string')) {
bgneal@312 1130 na = f;
bgneal@312 1131
bgneal@312 1132 if (f === '*') {
bgneal@312 1133 f = function(n) {return n.nodeType == 1;};
bgneal@312 1134 } else {
bgneal@312 1135 f = function(n) {
bgneal@312 1136 return t.is(n, na);
bgneal@312 1137 };
bgneal@312 1138 }
bgneal@312 1139 }
bgneal@312 1140
bgneal@312 1141 while (n) {
bgneal@312 1142 if (n == r || !n.nodeType || n.nodeType === 9)
bgneal@312 1143 break;
bgneal@312 1144
bgneal@312 1145 if (!f || f(n)) {
bgneal@312 1146 if (c)
bgneal@312 1147 o.push(n);
bgneal@312 1148 else
bgneal@312 1149 return n;
bgneal@312 1150 }
bgneal@312 1151
bgneal@312 1152 n = n.parentNode;
bgneal@312 1153 }
bgneal@312 1154
bgneal@312 1155 return c ? o : null;
bgneal@312 1156 },
bgneal@312 1157
bgneal@312 1158 get : function(e) {
bgneal@312 1159 var n;
bgneal@312 1160
bgneal@312 1161 if (e && this.doc && typeof(e) == 'string') {
bgneal@312 1162 n = e;
bgneal@312 1163 e = this.doc.getElementById(e);
bgneal@312 1164
bgneal@312 1165 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
bgneal@312 1166 if (e && e.id !== n)
bgneal@312 1167 return this.doc.getElementsByName(n)[1];
bgneal@312 1168 }
bgneal@312 1169
bgneal@312 1170 return e;
bgneal@312 1171 },
bgneal@312 1172
bgneal@312 1173 getNext : function(node, selector) {
bgneal@312 1174 return this._findSib(node, selector, 'nextSibling');
bgneal@312 1175 },
bgneal@312 1176
bgneal@312 1177 getPrev : function(node, selector) {
bgneal@312 1178 return this._findSib(node, selector, 'previousSibling');
bgneal@312 1179 },
bgneal@312 1180
bgneal@312 1181
bgneal@312 1182 select : function(pa, s) {
bgneal@312 1183 var t = this;
bgneal@312 1184
bgneal@312 1185 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
bgneal@312 1186 },
bgneal@312 1187
bgneal@312 1188 is : function(n, selector) {
bgneal@312 1189 var i;
bgneal@312 1190
bgneal@312 1191 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
bgneal@312 1192 if (n.length === undefined) {
bgneal@312 1193 // Simple all selector
bgneal@312 1194 if (selector === '*')
bgneal@312 1195 return n.nodeType == 1;
bgneal@312 1196
bgneal@312 1197 // Simple selector just elements
bgneal@312 1198 if (simpleSelectorRe.test(selector)) {
bgneal@312 1199 selector = selector.toLowerCase().split(/,/);
bgneal@312 1200 n = n.nodeName.toLowerCase();
bgneal@312 1201
bgneal@312 1202 for (i = selector.length - 1; i >= 0; i--) {
bgneal@312 1203 if (selector[i] == n)
bgneal@312 1204 return true;
bgneal@312 1205 }
bgneal@312 1206
bgneal@312 1207 return false;
bgneal@312 1208 }
bgneal@312 1209 }
bgneal@312 1210
bgneal@312 1211 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
bgneal@312 1212 },
bgneal@312 1213
bgneal@312 1214
bgneal@312 1215 add : function(p, n, a, h, c) {
bgneal@312 1216 var t = this;
bgneal@312 1217
bgneal@312 1218 return this.run(p, function(p) {
bgneal@312 1219 var e, k;
bgneal@312 1220
bgneal@312 1221 e = is(n, 'string') ? t.doc.createElement(n) : n;
bgneal@312 1222 t.setAttribs(e, a);
bgneal@312 1223
bgneal@312 1224 if (h) {
bgneal@312 1225 if (h.nodeType)
bgneal@312 1226 e.appendChild(h);
bgneal@312 1227 else
bgneal@312 1228 t.setHTML(e, h);
bgneal@312 1229 }
bgneal@312 1230
bgneal@312 1231 return !c ? p.appendChild(e) : e;
bgneal@312 1232 });
bgneal@312 1233 },
bgneal@312 1234
bgneal@312 1235 create : function(n, a, h) {
bgneal@312 1236 return this.add(this.doc.createElement(n), n, a, h, 1);
bgneal@312 1237 },
bgneal@312 1238
bgneal@312 1239 createHTML : function(n, a, h) {
bgneal@312 1240 var o = '', t = this, k;
bgneal@312 1241
bgneal@312 1242 o += '<' + n;
bgneal@312 1243
bgneal@312 1244 for (k in a) {
bgneal@312 1245 if (a.hasOwnProperty(k))
bgneal@312 1246 o += ' ' + k + '="' + t.encode(a[k]) + '"';
bgneal@312 1247 }
bgneal@312 1248
bgneal@312 1249 if (tinymce.is(h))
bgneal@312 1250 return o + '>' + h + '</' + n + '>';
bgneal@312 1251
bgneal@312 1252 return o + ' />';
bgneal@312 1253 },
bgneal@312 1254
bgneal@312 1255 remove : function(node, keep_children) {
bgneal@312 1256 return this.run(node, function(node) {
bgneal@312 1257 var parent, child;
bgneal@312 1258
bgneal@312 1259 parent = node.parentNode;
bgneal@312 1260
bgneal@312 1261 if (!parent)
bgneal@312 1262 return null;
bgneal@312 1263
bgneal@312 1264 if (keep_children) {
bgneal@312 1265 while (child = node.firstChild) {
bgneal@312 1266 // IE 8 will crash if you don't remove completely empty text nodes
bgneal@312 1267 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
bgneal@312 1268 parent.insertBefore(child, node);
bgneal@312 1269 else
bgneal@312 1270 node.removeChild(child);
bgneal@312 1271 }
bgneal@312 1272 }
bgneal@312 1273
bgneal@312 1274 return parent.removeChild(node);
bgneal@312 1275 });
bgneal@312 1276 },
bgneal@312 1277
bgneal@312 1278 setStyle : function(n, na, v) {
bgneal@312 1279 var t = this;
bgneal@312 1280
bgneal@312 1281 return t.run(n, function(e) {
bgneal@312 1282 var s, i;
bgneal@312 1283
bgneal@312 1284 s = e.style;
bgneal@312 1285
bgneal@312 1286 // Camelcase it, if needed
bgneal@312 1287 na = na.replace(/-(\D)/g, function(a, b){
bgneal@312 1288 return b.toUpperCase();
bgneal@312 1289 });
bgneal@312 1290
bgneal@312 1291 // Default px suffix on these
bgneal@312 1292 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
bgneal@312 1293 v += 'px';
bgneal@312 1294
bgneal@312 1295 switch (na) {
bgneal@312 1296 case 'opacity':
bgneal@312 1297 // IE specific opacity
bgneal@312 1298 if (isIE) {
bgneal@312 1299 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
bgneal@312 1300
bgneal@312 1301 if (!n.currentStyle || !n.currentStyle.hasLayout)
bgneal@312 1302 s.display = 'inline-block';
bgneal@312 1303 }
bgneal@312 1304
bgneal@312 1305 // Fix for older browsers
bgneal@312 1306 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
bgneal@312 1307 break;
bgneal@312 1308
bgneal@312 1309 case 'float':
bgneal@312 1310 isIE ? s.styleFloat = v : s.cssFloat = v;
bgneal@312 1311 break;
bgneal@312 1312
bgneal@312 1313 default:
bgneal@312 1314 s[na] = v || '';
bgneal@312 1315 }
bgneal@312 1316
bgneal@312 1317 // Force update of the style data
bgneal@312 1318 if (t.settings.update_styles)
bgneal@312 1319 t.setAttrib(e, '_mce_style');
bgneal@312 1320 });
bgneal@312 1321 },
bgneal@312 1322
bgneal@312 1323 getStyle : function(n, na, c) {
bgneal@312 1324 n = this.get(n);
bgneal@312 1325
bgneal@312 1326 if (!n)
bgneal@312 1327 return false;
bgneal@312 1328
bgneal@312 1329 // Gecko
bgneal@312 1330 if (this.doc.defaultView && c) {
bgneal@312 1331 // Remove camelcase
bgneal@312 1332 na = na.replace(/[A-Z]/g, function(a){
bgneal@312 1333 return '-' + a;
bgneal@312 1334 });
bgneal@312 1335
bgneal@312 1336 try {
bgneal@312 1337 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
bgneal@312 1338 } catch (ex) {
bgneal@312 1339 // Old safari might fail
bgneal@312 1340 return null;
bgneal@312 1341 }
bgneal@312 1342 }
bgneal@312 1343
bgneal@312 1344 // Camelcase it, if needed
bgneal@312 1345 na = na.replace(/-(\D)/g, function(a, b){
bgneal@312 1346 return b.toUpperCase();
bgneal@312 1347 });
bgneal@312 1348
bgneal@312 1349 if (na == 'float')
bgneal@312 1350 na = isIE ? 'styleFloat' : 'cssFloat';
bgneal@312 1351
bgneal@312 1352 // IE & Opera
bgneal@312 1353 if (n.currentStyle && c)
bgneal@312 1354 return n.currentStyle[na];
bgneal@312 1355
bgneal@312 1356 return n.style[na];
bgneal@312 1357 },
bgneal@312 1358
bgneal@312 1359 setStyles : function(e, o) {
bgneal@312 1360 var t = this, s = t.settings, ol;
bgneal@312 1361
bgneal@312 1362 ol = s.update_styles;
bgneal@312 1363 s.update_styles = 0;
bgneal@312 1364
bgneal@312 1365 each(o, function(v, n) {
bgneal@312 1366 t.setStyle(e, n, v);
bgneal@312 1367 });
bgneal@312 1368
bgneal@312 1369 // Update style info
bgneal@312 1370 s.update_styles = ol;
bgneal@312 1371 if (s.update_styles)
bgneal@312 1372 t.setAttrib(e, s.cssText);
bgneal@312 1373 },
bgneal@312 1374
bgneal@312 1375 setAttrib : function(e, n, v) {
bgneal@312 1376 var t = this;
bgneal@312 1377
bgneal@312 1378 // Whats the point
bgneal@312 1379 if (!e || !n)
bgneal@312 1380 return;
bgneal@312 1381
bgneal@312 1382 // Strict XML mode
bgneal@312 1383 if (t.settings.strict)
bgneal@312 1384 n = n.toLowerCase();
bgneal@312 1385
bgneal@312 1386 return this.run(e, function(e) {
bgneal@312 1387 var s = t.settings;
bgneal@312 1388
bgneal@312 1389 switch (n) {
bgneal@312 1390 case "style":
bgneal@312 1391 if (!is(v, 'string')) {
bgneal@312 1392 each(v, function(v, n) {
bgneal@312 1393 t.setStyle(e, n, v);
bgneal@312 1394 });
bgneal@312 1395
bgneal@312 1396 return;
bgneal@312 1397 }
bgneal@312 1398
bgneal@312 1399 // No mce_style for elements with these since they might get resized by the user
bgneal@312 1400 if (s.keep_values) {
bgneal@312 1401 if (v && !t._isRes(v))
bgneal@312 1402 e.setAttribute('_mce_style', v, 2);
bgneal@312 1403 else
bgneal@312 1404 e.removeAttribute('_mce_style', 2);
bgneal@312 1405 }
bgneal@312 1406
bgneal@312 1407 e.style.cssText = v;
bgneal@312 1408 break;
bgneal@312 1409
bgneal@312 1410 case "class":
bgneal@312 1411 e.className = v || ''; // Fix IE null bug
bgneal@312 1412 break;
bgneal@312 1413
bgneal@312 1414 case "src":
bgneal@312 1415 case "href":
bgneal@312 1416 if (s.keep_values) {
bgneal@312 1417 if (s.url_converter)
bgneal@312 1418 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
bgneal@312 1419
bgneal@312 1420 t.setAttrib(e, '_mce_' + n, v, 2);
bgneal@312 1421 }
bgneal@312 1422
bgneal@312 1423 break;
bgneal@312 1424
bgneal@312 1425 case "shape":
bgneal@312 1426 e.setAttribute('_mce_style', v);
bgneal@312 1427 break;
bgneal@312 1428 }
bgneal@312 1429
bgneal@312 1430 if (is(v) && v !== null && v.length !== 0)
bgneal@312 1431 e.setAttribute(n, '' + v, 2);
bgneal@312 1432 else
bgneal@312 1433 e.removeAttribute(n, 2);
bgneal@312 1434 });
bgneal@312 1435 },
bgneal@312 1436
bgneal@312 1437 setAttribs : function(e, o) {
bgneal@312 1438 var t = this;
bgneal@312 1439
bgneal@312 1440 return this.run(e, function(e) {
bgneal@312 1441 each(o, function(v, n) {
bgneal@312 1442 t.setAttrib(e, n, v);
bgneal@312 1443 });
bgneal@312 1444 });
bgneal@312 1445 },
bgneal@312 1446
bgneal@312 1447 getAttrib : function(e, n, dv) {
bgneal@312 1448 var v, t = this;
bgneal@312 1449
bgneal@312 1450 e = t.get(e);
bgneal@312 1451
bgneal@312 1452 if (!e || e.nodeType !== 1)
bgneal@312 1453 return false;
bgneal@312 1454
bgneal@312 1455 if (!is(dv))
bgneal@312 1456 dv = '';
bgneal@312 1457
bgneal@312 1458 // Try the mce variant for these
bgneal@312 1459 if (/^(src|href|style|coords|shape)$/.test(n)) {
bgneal@312 1460 v = e.getAttribute("_mce_" + n);
bgneal@312 1461
bgneal@312 1462 if (v)
bgneal@312 1463 return v;
bgneal@312 1464 }
bgneal@312 1465
bgneal@312 1466 if (isIE && t.props[n]) {
bgneal@312 1467 v = e[t.props[n]];
bgneal@312 1468 v = v && v.nodeValue ? v.nodeValue : v;
bgneal@312 1469 }
bgneal@312 1470
bgneal@312 1471 if (!v)
bgneal@312 1472 v = e.getAttribute(n, 2);
bgneal@312 1473
bgneal@312 1474 // Check boolean attribs
bgneal@312 1475 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
bgneal@312 1476 if (e[t.props[n]] === true && v === '')
bgneal@312 1477 return n;
bgneal@312 1478
bgneal@312 1479 return v ? n : '';
bgneal@312 1480 }
bgneal@312 1481
bgneal@312 1482 // Inner input elements will override attributes on form elements
bgneal@312 1483 if (e.nodeName === "FORM" && e.getAttributeNode(n))
bgneal@312 1484 return e.getAttributeNode(n).nodeValue;
bgneal@312 1485
bgneal@312 1486 if (n === 'style') {
bgneal@312 1487 v = v || e.style.cssText;
bgneal@312 1488
bgneal@312 1489 if (v) {
bgneal@312 1490 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
bgneal@312 1491
bgneal@312 1492 if (t.settings.keep_values && !t._isRes(v))
bgneal@312 1493 e.setAttribute('_mce_style', v);
bgneal@312 1494 }
bgneal@312 1495 }
bgneal@312 1496
bgneal@312 1497 // Remove Apple and WebKit stuff
bgneal@312 1498 if (isWebKit && n === "class" && v)
bgneal@312 1499 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
bgneal@312 1500
bgneal@312 1501 // Handle IE issues
bgneal@312 1502 if (isIE) {
bgneal@312 1503 switch (n) {
bgneal@312 1504 case 'rowspan':
bgneal@312 1505 case 'colspan':
bgneal@312 1506 // IE returns 1 as default value
bgneal@312 1507 if (v === 1)
bgneal@312 1508 v = '';
bgneal@312 1509
bgneal@312 1510 break;
bgneal@312 1511
bgneal@312 1512 case 'size':
bgneal@312 1513 // IE returns +0 as default value for size
bgneal@312 1514 if (v === '+0' || v === 20 || v === 0)
bgneal@312 1515 v = '';
bgneal@312 1516
bgneal@312 1517 break;
bgneal@312 1518
bgneal@312 1519 case 'width':
bgneal@312 1520 case 'height':
bgneal@312 1521 case 'vspace':
bgneal@312 1522 case 'checked':
bgneal@312 1523 case 'disabled':
bgneal@312 1524 case 'readonly':
bgneal@312 1525 if (v === 0)
bgneal@312 1526 v = '';
bgneal@312 1527
bgneal@312 1528 break;
bgneal@312 1529
bgneal@312 1530 case 'hspace':
bgneal@312 1531 // IE returns -1 as default value
bgneal@312 1532 if (v === -1)
bgneal@312 1533 v = '';
bgneal@312 1534
bgneal@312 1535 break;
bgneal@312 1536
bgneal@312 1537 case 'maxlength':
bgneal@312 1538 case 'tabindex':
bgneal@312 1539 // IE returns default value
bgneal@312 1540 if (v === 32768 || v === 2147483647 || v === '32768')
bgneal@312 1541 v = '';
bgneal@312 1542
bgneal@312 1543 break;
bgneal@312 1544
bgneal@312 1545 case 'multiple':
bgneal@312 1546 case 'compact':
bgneal@312 1547 case 'noshade':
bgneal@312 1548 case 'nowrap':
bgneal@312 1549 if (v === 65535)
bgneal@312 1550 return n;
bgneal@312 1551
bgneal@312 1552 return dv;
bgneal@312 1553
bgneal@312 1554 case 'shape':
bgneal@312 1555 v = v.toLowerCase();
bgneal@312 1556 break;
bgneal@312 1557
bgneal@312 1558 default:
bgneal@312 1559 // IE has odd anonymous function for event attributes
bgneal@312 1560 if (n.indexOf('on') === 0 && v)
bgneal@312 1561 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
bgneal@312 1562 }
bgneal@312 1563 }
bgneal@312 1564
bgneal@312 1565 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
bgneal@312 1566 },
bgneal@312 1567
bgneal@312 1568 getPos : function(n, ro) {
bgneal@312 1569 var t = this, x = 0, y = 0, e, d = t.doc, r;
bgneal@312 1570
bgneal@312 1571 n = t.get(n);
bgneal@312 1572 ro = ro || d.body;
bgneal@312 1573
bgneal@312 1574 if (n) {
bgneal@312 1575 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
bgneal@312 1576 if (isIE && !t.stdMode) {
bgneal@312 1577 n = n.getBoundingClientRect();
bgneal@312 1578 e = t.boxModel ? d.documentElement : d.body;
bgneal@312 1579 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
bgneal@312 1580 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
bgneal@312 1581
bgneal@312 1582 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
bgneal@312 1583 }
bgneal@312 1584
bgneal@312 1585 r = n;
bgneal@312 1586 while (r && r != ro && r.nodeType) {
bgneal@312 1587 x += r.offsetLeft || 0;
bgneal@312 1588 y += r.offsetTop || 0;
bgneal@312 1589 r = r.offsetParent;
bgneal@312 1590 }
bgneal@312 1591
bgneal@312 1592 r = n.parentNode;
bgneal@312 1593 while (r && r != ro && r.nodeType) {
bgneal@312 1594 x -= r.scrollLeft || 0;
bgneal@312 1595 y -= r.scrollTop || 0;
bgneal@312 1596 r = r.parentNode;
bgneal@312 1597 }
bgneal@312 1598 }
bgneal@312 1599
bgneal@312 1600 return {x : x, y : y};
bgneal@312 1601 },
bgneal@312 1602
bgneal@312 1603 parseStyle : function(st) {
bgneal@312 1604 var t = this, s = t.settings, o = {};
bgneal@312 1605
bgneal@312 1606 if (!st)
bgneal@312 1607 return o;
bgneal@312 1608
bgneal@312 1609 function compress(p, s, ot) {
bgneal@312 1610 var t, r, b, l;
bgneal@312 1611
bgneal@312 1612 // Get values and check it it needs compressing
bgneal@312 1613 t = o[p + '-top' + s];
bgneal@312 1614 if (!t)
bgneal@312 1615 return;
bgneal@312 1616
bgneal@312 1617 r = o[p + '-right' + s];
bgneal@312 1618 if (t != r)
bgneal@312 1619 return;
bgneal@312 1620
bgneal@312 1621 b = o[p + '-bottom' + s];
bgneal@312 1622 if (r != b)
bgneal@312 1623 return;
bgneal@312 1624
bgneal@312 1625 l = o[p + '-left' + s];
bgneal@312 1626 if (b != l)
bgneal@312 1627 return;
bgneal@312 1628
bgneal@312 1629 // Compress
bgneal@312 1630 o[ot] = l;
bgneal@312 1631 delete o[p + '-top' + s];
bgneal@312 1632 delete o[p + '-right' + s];
bgneal@312 1633 delete o[p + '-bottom' + s];
bgneal@312 1634 delete o[p + '-left' + s];
bgneal@312 1635 };
bgneal@312 1636
bgneal@312 1637 function compress2(ta, a, b, c) {
bgneal@312 1638 var t;
bgneal@312 1639
bgneal@312 1640 t = o[a];
bgneal@312 1641 if (!t)
bgneal@312 1642 return;
bgneal@312 1643
bgneal@312 1644 t = o[b];
bgneal@312 1645 if (!t)
bgneal@312 1646 return;
bgneal@312 1647
bgneal@312 1648 t = o[c];
bgneal@312 1649 if (!t)
bgneal@312 1650 return;
bgneal@312 1651
bgneal@312 1652 // Compress
bgneal@312 1653 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
bgneal@312 1654 delete o[a];
bgneal@312 1655 delete o[b];
bgneal@312 1656 delete o[c];
bgneal@312 1657 };
bgneal@312 1658
bgneal@312 1659 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
bgneal@312 1660
bgneal@312 1661 each(st.split(';'), function(v) {
bgneal@312 1662 var sv, ur = [];
bgneal@312 1663
bgneal@312 1664 if (v) {
bgneal@312 1665 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
bgneal@312 1666 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
bgneal@312 1667 v = v.split(':');
bgneal@312 1668 sv = tinymce.trim(v[1]);
bgneal@312 1669 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
bgneal@312 1670
bgneal@312 1671 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
bgneal@312 1672 return t.toHex(v);
bgneal@312 1673 });
bgneal@312 1674
bgneal@312 1675 if (s.url_converter) {
bgneal@312 1676 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
bgneal@312 1677 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
bgneal@312 1678 });
bgneal@312 1679 }
bgneal@312 1680
bgneal@312 1681 o[tinymce.trim(v[0]).toLowerCase()] = sv;
bgneal@312 1682 }
bgneal@312 1683 });
bgneal@312 1684
bgneal@312 1685 compress("border", "", "border");
bgneal@312 1686 compress("border", "-width", "border-width");
bgneal@312 1687 compress("border", "-color", "border-color");
bgneal@312 1688 compress("border", "-style", "border-style");
bgneal@312 1689 compress("padding", "", "padding");
bgneal@312 1690 compress("margin", "", "margin");
bgneal@312 1691 compress2('border', 'border-width', 'border-style', 'border-color');
bgneal@312 1692
bgneal@312 1693 if (isIE) {
bgneal@312 1694 // Remove pointless border
bgneal@312 1695 if (o.border == 'medium none')
bgneal@312 1696 o.border = '';
bgneal@312 1697 }
bgneal@312 1698
bgneal@312 1699 return o;
bgneal@312 1700 },
bgneal@312 1701
bgneal@312 1702 serializeStyle : function(o, name) {
bgneal@312 1703 var t = this, s = '';
bgneal@312 1704
bgneal@312 1705 function add(v, k) {
bgneal@312 1706 if (k && v) {
bgneal@312 1707 // Remove browser specific styles like -moz- or -webkit-
bgneal@312 1708 if (k.indexOf('-') === 0)
bgneal@312 1709 return;
bgneal@312 1710
bgneal@312 1711 switch (k) {
bgneal@312 1712 case 'font-weight':
bgneal@312 1713 // Opera will output bold as 700
bgneal@312 1714 if (v == 700)
bgneal@312 1715 v = 'bold';
bgneal@312 1716
bgneal@312 1717 break;
bgneal@312 1718
bgneal@312 1719 case 'color':
bgneal@312 1720 case 'background-color':
bgneal@312 1721 v = v.toLowerCase();
bgneal@312 1722 break;
bgneal@312 1723 }
bgneal@312 1724
bgneal@312 1725 s += (s ? ' ' : '') + k + ': ' + v + ';';
bgneal@312 1726 }
bgneal@312 1727 };
bgneal@312 1728
bgneal@312 1729 // Validate style output
bgneal@312 1730 if (name && t._styles) {
bgneal@312 1731 each(t._styles['*'], function(name) {
bgneal@312 1732 add(o[name], name);
bgneal@312 1733 });
bgneal@312 1734
bgneal@312 1735 each(t._styles[name.toLowerCase()], function(name) {
bgneal@312 1736 add(o[name], name);
bgneal@312 1737 });
bgneal@312 1738 } else
bgneal@312 1739 each(o, add);
bgneal@312 1740
bgneal@312 1741 return s;
bgneal@312 1742 },
bgneal@312 1743
bgneal@312 1744 loadCSS : function(u) {
bgneal@312 1745 var t = this, d = t.doc, head;
bgneal@312 1746
bgneal@312 1747 if (!u)
bgneal@312 1748 u = '';
bgneal@312 1749
bgneal@312 1750 head = t.select('head')[0];
bgneal@312 1751
bgneal@312 1752 each(u.split(','), function(u) {
bgneal@312 1753 var link;
bgneal@312 1754
bgneal@312 1755 if (t.files[u])
bgneal@312 1756 return;
bgneal@312 1757
bgneal@312 1758 t.files[u] = true;
bgneal@312 1759 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
bgneal@312 1760
bgneal@312 1761 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
bgneal@312 1762 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
bgneal@312 1763 // It's ugly but it seems to work fine.
bgneal@312 1764 if (isIE && d.documentMode) {
bgneal@312 1765 link.onload = function() {
bgneal@312 1766 d.recalc();
bgneal@312 1767 link.onload = null;
bgneal@312 1768 };
bgneal@312 1769 }
bgneal@312 1770
bgneal@312 1771 head.appendChild(link);
bgneal@312 1772 });
bgneal@312 1773 },
bgneal@312 1774
bgneal@312 1775 addClass : function(e, c) {
bgneal@312 1776 return this.run(e, function(e) {
bgneal@312 1777 var o;
bgneal@312 1778
bgneal@312 1779 if (!c)
bgneal@312 1780 return 0;
bgneal@312 1781
bgneal@312 1782 if (this.hasClass(e, c))
bgneal@312 1783 return e.className;
bgneal@312 1784
bgneal@312 1785 o = this.removeClass(e, c);
bgneal@312 1786
bgneal@312 1787 return e.className = (o != '' ? (o + ' ') : '') + c;
bgneal@312 1788 });
bgneal@312 1789 },
bgneal@312 1790
bgneal@312 1791 removeClass : function(e, c) {
bgneal@312 1792 var t = this, re;
bgneal@312 1793
bgneal@312 1794 return t.run(e, function(e) {
bgneal@312 1795 var v;
bgneal@312 1796
bgneal@312 1797 if (t.hasClass(e, c)) {
bgneal@312 1798 if (!re)
bgneal@312 1799 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
bgneal@312 1800
bgneal@312 1801 v = e.className.replace(re, ' ');
bgneal@312 1802 v = tinymce.trim(v != ' ' ? v : '');
bgneal@312 1803
bgneal@312 1804 e.className = v;
bgneal@312 1805
bgneal@312 1806 // Empty class attr
bgneal@312 1807 if (!v) {
bgneal@312 1808 e.removeAttribute('class');
bgneal@312 1809 e.removeAttribute('className');
bgneal@312 1810 }
bgneal@312 1811
bgneal@312 1812 return v;
bgneal@312 1813 }
bgneal@312 1814
bgneal@312 1815 return e.className;
bgneal@312 1816 });
bgneal@312 1817 },
bgneal@312 1818
bgneal@312 1819 hasClass : function(n, c) {
bgneal@312 1820 n = this.get(n);
bgneal@312 1821
bgneal@312 1822 if (!n || !c)
bgneal@312 1823 return false;
bgneal@312 1824
bgneal@312 1825 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
bgneal@312 1826 },
bgneal@312 1827
bgneal@312 1828 show : function(e) {
bgneal@312 1829 return this.setStyle(e, 'display', 'block');
bgneal@312 1830 },
bgneal@312 1831
bgneal@312 1832 hide : function(e) {
bgneal@312 1833 return this.setStyle(e, 'display', 'none');
bgneal@312 1834 },
bgneal@312 1835
bgneal@312 1836 isHidden : function(e) {
bgneal@312 1837 e = this.get(e);
bgneal@312 1838
bgneal@312 1839 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
bgneal@312 1840 },
bgneal@312 1841
bgneal@312 1842 uniqueId : function(p) {
bgneal@312 1843 return (!p ? 'mce_' : p) + (this.counter++);
bgneal@312 1844 },
bgneal@312 1845
bgneal@312 1846 setHTML : function(e, h) {
bgneal@312 1847 var t = this;
bgneal@312 1848
bgneal@312 1849 return this.run(e, function(e) {
bgneal@312 1850 var x, i, nl, n, p, x;
bgneal@312 1851
bgneal@312 1852 h = t.processHTML(h);
bgneal@312 1853
bgneal@312 1854 if (isIE) {
bgneal@312 1855 function set() {
bgneal@312 1856 // Remove all child nodes
bgneal@312 1857 while (e.firstChild)
bgneal@312 1858 e.firstChild.removeNode();
bgneal@312 1859
bgneal@312 1860 try {
bgneal@312 1861 // IE will remove comments from the beginning
bgneal@312 1862 // unless you padd the contents with something
bgneal@312 1863 e.innerHTML = '<br />' + h;
bgneal@312 1864 e.removeChild(e.firstChild);
bgneal@312 1865 } catch (ex) {
bgneal@312 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@312 1867 // This seems to fix this problem
bgneal@312 1868
bgneal@312 1869 // Create new div with HTML contents and a BR infront to keep comments
bgneal@312 1870 x = t.create('div');
bgneal@312 1871 x.innerHTML = '<br />' + h;
bgneal@312 1872
bgneal@312 1873 // Add all children from div to target
bgneal@312 1874 each (x.childNodes, function(n, i) {
bgneal@312 1875 // Skip br element
bgneal@312 1876 if (i)
bgneal@312 1877 e.appendChild(n);
bgneal@312 1878 });
bgneal@312 1879 }
bgneal@312 1880 };
bgneal@312 1881
bgneal@312 1882 // IE has a serious bug when it comes to paragraphs it can produce an invalid
bgneal@312 1883 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
bgneal@312 1884 // It seems to be that IE doesn't like a root block element placed inside another root block element
bgneal@312 1885 if (t.settings.fix_ie_paragraphs)
bgneal@312 1886 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true">&nbsp;</p>');
bgneal@312 1887
bgneal@312 1888 set();
bgneal@312 1889
bgneal@312 1890 if (t.settings.fix_ie_paragraphs) {
bgneal@312 1891 // Check for odd paragraphs this is a sign of a broken DOM
bgneal@312 1892 nl = e.getElementsByTagName("p");
bgneal@312 1893 for (i = nl.length - 1, x = 0; i >= 0; i--) {
bgneal@312 1894 n = nl[i];
bgneal@312 1895
bgneal@312 1896 if (!n.hasChildNodes()) {
bgneal@312 1897 if (!n._mce_keep) {
bgneal@312 1898 x = 1; // Is broken
bgneal@312 1899 break;
bgneal@312 1900 }
bgneal@312 1901
bgneal@312 1902 n.removeAttribute('_mce_keep');
bgneal@312 1903 }
bgneal@312 1904 }
bgneal@312 1905 }
bgneal@312 1906
bgneal@312 1907 // Time to fix the madness IE left us
bgneal@312 1908 if (x) {
bgneal@312 1909 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
bgneal@312 1910 // after we use innerHTML we can fix the DOM tree
bgneal@312 1911 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
bgneal@312 1912 h = h.replace(/<\/p>/gi, '</div>');
bgneal@312 1913
bgneal@312 1914 // Set the new HTML with DIVs
bgneal@312 1915 set();
bgneal@312 1916
bgneal@312 1917 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
bgneal@312 1918 // This is needed since IE has a annoying bug see above for details
bgneal@312 1919 // This is a slow process but it has to be done. :(
bgneal@312 1920 if (t.settings.fix_ie_paragraphs) {
bgneal@312 1921 nl = e.getElementsByTagName("DIV");
bgneal@312 1922 for (i = nl.length - 1; i >= 0; i--) {
bgneal@312 1923 n = nl[i];
bgneal@312 1924
bgneal@312 1925 // Is it a temp div
bgneal@312 1926 if (n._mce_tmp) {
bgneal@312 1927 // Create new paragraph
bgneal@312 1928 p = t.doc.createElement('p');
bgneal@312 1929
bgneal@312 1930 // Copy all attributes
bgneal@312 1931 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
bgneal@312 1932 var v;
bgneal@312 1933
bgneal@312 1934 if (b !== '_mce_tmp') {
bgneal@312 1935 v = n.getAttribute(b);
bgneal@312 1936
bgneal@312 1937 if (!v && b === 'class')
bgneal@312 1938 v = n.className;
bgneal@312 1939
bgneal@312 1940 p.setAttribute(b, v);
bgneal@312 1941 }
bgneal@312 1942 });
bgneal@312 1943
bgneal@312 1944 // Append all children to new paragraph
bgneal@312 1945 for (x = 0; x<n.childNodes.length; x++)
bgneal@312 1946 p.appendChild(n.childNodes[x].cloneNode(true));
bgneal@312 1947
bgneal@312 1948 // Replace div with new paragraph
bgneal@312 1949 n.swapNode(p);
bgneal@312 1950 }
bgneal@312 1951 }
bgneal@312 1952 }
bgneal@312 1953 }
bgneal@312 1954 } else
bgneal@312 1955 e.innerHTML = h;
bgneal@312 1956
bgneal@312 1957 return h;
bgneal@312 1958 });
bgneal@312 1959 },
bgneal@312 1960
bgneal@312 1961 processHTML : function(h) {
bgneal@312 1962 var t = this, s = t.settings, codeBlocks = [];
bgneal@312 1963
bgneal@312 1964 if (!s.process_html)
bgneal@312 1965 return h;
bgneal@312 1966
bgneal@312 1967 if (isIE) {
bgneal@312 1968 h = h.replace(/&apos;/g, '&#39;'); // IE can't handle apos
bgneal@312 1969 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
bgneal@312 1970 }
bgneal@312 1971
bgneal@312 1972 // Fix some issues
bgneal@312 1973 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
bgneal@312 1974
bgneal@312 1975 // Store away src and href in _mce_src and mce_href since browsers mess them up
bgneal@312 1976 if (s.keep_values) {
bgneal@312 1977 // Wrap scripts and styles in comments for serialization purposes
bgneal@312 1978 if (/<script|noscript|style/i.test(h)) {
bgneal@312 1979 function trim(s) {
bgneal@312 1980 // Remove prefix and suffix code for element
bgneal@312 1981 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
bgneal@312 1982 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
bgneal@312 1983 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
bgneal@312 1984 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
bgneal@312 1985
bgneal@312 1986 return s;
bgneal@312 1987 };
bgneal@312 1988
bgneal@312 1989 // Wrap the script contents in CDATA and keep them from executing
bgneal@312 1990 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
bgneal@312 1991 // Force type attribute
bgneal@312 1992 if (!attribs)
bgneal@312 1993 attribs = ' type="text/javascript"';
bgneal@312 1994
bgneal@312 1995 // Convert the src attribute of the scripts
bgneal@312 1996 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
bgneal@312 1997 if (s.url_converter)
bgneal@312 1998 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
bgneal@312 1999
bgneal@312 2000 return '_mce_src="' + url + '"';
bgneal@312 2001 });
bgneal@312 2002
bgneal@312 2003 // Wrap text contents
bgneal@312 2004 if (tinymce.trim(text)) {
bgneal@312 2005 codeBlocks.push(trim(text));
bgneal@312 2006 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
bgneal@312 2007 }
bgneal@312 2008
bgneal@312 2009 return '<mce:script' + attribs + '>' + text + '</mce:script>';
bgneal@312 2010 });
bgneal@312 2011
bgneal@312 2012 // Wrap style elements
bgneal@312 2013 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
bgneal@312 2014 // Wrap text contents
bgneal@312 2015 if (text) {
bgneal@312 2016 codeBlocks.push(trim(text));
bgneal@312 2017 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
bgneal@312 2018 }
bgneal@312 2019
bgneal@312 2020 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
bgneal@312 2021 });
bgneal@312 2022
bgneal@312 2023 // Wrap noscript elements
bgneal@312 2024 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@312 2025 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '&#45;&#45;') + '--></mce:noscript>';
bgneal@312 2026 });
bgneal@312 2027 }
bgneal@312 2028
bgneal@312 2029 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
bgneal@312 2030
bgneal@312 2031 // This function processes the attributes in the HTML string to force boolean
bgneal@312 2032 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
bgneal@312 2033 function processTags(html) {
bgneal@312 2034 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
bgneal@312 2035 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
bgneal@312 2036 var mceValue;
bgneal@312 2037
bgneal@312 2038 name = name.toLowerCase();
bgneal@312 2039 value = value || val2 || val3 || "";
bgneal@312 2040
bgneal@312 2041 // Treat boolean attributes
bgneal@312 2042 if (boolAttrs[name]) {
bgneal@312 2043 // false or 0 is treated as a missing attribute
bgneal@312 2044 if (value === 'false' || value === '0')
bgneal@312 2045 return;
bgneal@312 2046
bgneal@312 2047 return name + '="' + name + '"';
bgneal@312 2048 }
bgneal@312 2049
bgneal@312 2050 // Is attribute one that needs special treatment
bgneal@312 2051 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
bgneal@312 2052 mceValue = t.decode(value);
bgneal@312 2053
bgneal@312 2054 // Convert URLs to relative/absolute ones
bgneal@312 2055 if (s.url_converter && (name == "src" || name == "href"))
bgneal@312 2056 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
bgneal@312 2057
bgneal@312 2058 // Process styles lowercases them and compresses them
bgneal@312 2059 if (name == 'style')
bgneal@312 2060 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
bgneal@312 2061
bgneal@312 2062 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
bgneal@312 2063 }
bgneal@312 2064
bgneal@312 2065 return match;
bgneal@312 2066 }) + end + '>';
bgneal@312 2067 });
bgneal@312 2068 };
bgneal@312 2069
bgneal@312 2070 h = processTags(h);
bgneal@312 2071
bgneal@312 2072 // Restore script blocks
bgneal@312 2073 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
bgneal@312 2074 return codeBlocks[idx];
bgneal@312 2075 });
bgneal@312 2076 }
bgneal@312 2077
bgneal@312 2078 return h;
bgneal@312 2079 },
bgneal@312 2080
bgneal@312 2081 getOuterHTML : function(e) {
bgneal@312 2082 var d;
bgneal@312 2083
bgneal@312 2084 e = this.get(e);
bgneal@312 2085
bgneal@312 2086 if (!e)
bgneal@312 2087 return null;
bgneal@312 2088
bgneal@312 2089 if (e.outerHTML !== undefined)
bgneal@312 2090 return e.outerHTML;
bgneal@312 2091
bgneal@312 2092 d = (e.ownerDocument || this.doc).createElement("body");
bgneal@312 2093 d.appendChild(e.cloneNode(true));
bgneal@312 2094
bgneal@312 2095 return d.innerHTML;
bgneal@312 2096 },
bgneal@312 2097
bgneal@312 2098 setOuterHTML : function(e, h, d) {
bgneal@312 2099 var t = this;
bgneal@312 2100
bgneal@312 2101 function setHTML(e, h, d) {
bgneal@312 2102 var n, tp;
bgneal@312 2103
bgneal@312 2104 tp = d.createElement("body");
bgneal@312 2105 tp.innerHTML = h;
bgneal@312 2106
bgneal@312 2107 n = tp.lastChild;
bgneal@312 2108 while (n) {
bgneal@312 2109 t.insertAfter(n.cloneNode(true), e);
bgneal@312 2110 n = n.previousSibling;
bgneal@312 2111 }
bgneal@312 2112
bgneal@312 2113 t.remove(e);
bgneal@312 2114 };
bgneal@312 2115
bgneal@312 2116 return this.run(e, function(e) {
bgneal@312 2117 e = t.get(e);
bgneal@312 2118
bgneal@312 2119 // Only set HTML on elements
bgneal@312 2120 if (e.nodeType == 1) {
bgneal@312 2121 d = d || e.ownerDocument || t.doc;
bgneal@312 2122
bgneal@312 2123 if (isIE) {
bgneal@312 2124 try {
bgneal@312 2125 // Try outerHTML for IE it sometimes produces an unknown runtime error
bgneal@312 2126 if (isIE && e.nodeType == 1)
bgneal@312 2127 e.outerHTML = h;
bgneal@312 2128 else
bgneal@312 2129 setHTML(e, h, d);
bgneal@312 2130 } catch (ex) {
bgneal@312 2131 // Fix for unknown runtime error
bgneal@312 2132 setHTML(e, h, d);
bgneal@312 2133 }
bgneal@312 2134 } else
bgneal@312 2135 setHTML(e, h, d);
bgneal@312 2136 }
bgneal@312 2137 });
bgneal@312 2138 },
bgneal@312 2139
bgneal@312 2140 decode : function(s) {
bgneal@312 2141 var e, n, v;
bgneal@312 2142
bgneal@312 2143 // Look for entities to decode
bgneal@312 2144 if (/&[\w#]+;/.test(s)) {
bgneal@312 2145 // Decode the entities using a div element not super efficient but less code
bgneal@312 2146 e = this.doc.createElement("div");
bgneal@312 2147 e.innerHTML = s;
bgneal@312 2148 n = e.firstChild;
bgneal@312 2149 v = '';
bgneal@312 2150
bgneal@312 2151 if (n) {
bgneal@312 2152 do {
bgneal@312 2153 v += n.nodeValue;
bgneal@312 2154 } while (n = n.nextSibling);
bgneal@312 2155 }
bgneal@312 2156
bgneal@312 2157 return v || s;
bgneal@312 2158 }
bgneal@312 2159
bgneal@312 2160 return s;
bgneal@312 2161 },
bgneal@312 2162
bgneal@312 2163 encode : function(str) {
bgneal@312 2164 return ('' + str).replace(encodeCharsRe, function(chr) {
bgneal@312 2165 return encodedChars[chr];
bgneal@312 2166 });
bgneal@312 2167 },
bgneal@312 2168
bgneal@312 2169 insertAfter : function(node, reference_node) {
bgneal@312 2170 reference_node = this.get(reference_node);
bgneal@312 2171
bgneal@312 2172 return this.run(node, function(node) {
bgneal@312 2173 var parent, nextSibling;
bgneal@312 2174
bgneal@312 2175 parent = reference_node.parentNode;
bgneal@312 2176 nextSibling = reference_node.nextSibling;
bgneal@312 2177
bgneal@312 2178 if (nextSibling)
bgneal@312 2179 parent.insertBefore(node, nextSibling);
bgneal@312 2180 else
bgneal@312 2181 parent.appendChild(node);
bgneal@312 2182
bgneal@312 2183 return node;
bgneal@312 2184 });
bgneal@312 2185 },
bgneal@312 2186
bgneal@312 2187 isBlock : function(n) {
bgneal@312 2188 if (n.nodeType && n.nodeType !== 1)
bgneal@312 2189 return false;
bgneal@312 2190
bgneal@312 2191 n = n.nodeName || n;
bgneal@312 2192
bgneal@312 2193 return blockRe.test(n);
bgneal@312 2194 },
bgneal@312 2195
bgneal@312 2196 replace : function(n, o, k) {
bgneal@312 2197 var t = this;
bgneal@312 2198
bgneal@312 2199 if (is(o, 'array'))
bgneal@312 2200 n = n.cloneNode(true);
bgneal@312 2201
bgneal@312 2202 return t.run(o, function(o) {
bgneal@312 2203 if (k) {
bgneal@312 2204 each(tinymce.grep(o.childNodes), function(c) {
bgneal@312 2205 n.appendChild(c);
bgneal@312 2206 });
bgneal@312 2207 }
bgneal@312 2208
bgneal@312 2209 return o.parentNode.replaceChild(n, o);
bgneal@312 2210 });
bgneal@312 2211 },
bgneal@312 2212
bgneal@312 2213 rename : function(elm, name) {
bgneal@312 2214 var t = this, newElm;
bgneal@312 2215
bgneal@312 2216 if (elm.nodeName != name.toUpperCase()) {
bgneal@312 2217 // Rename block element
bgneal@312 2218 newElm = t.create(name);
bgneal@312 2219
bgneal@312 2220 // Copy attribs to new block
bgneal@312 2221 each(t.getAttribs(elm), function(attr_node) {
bgneal@312 2222 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
bgneal@312 2223 });
bgneal@312 2224
bgneal@312 2225 // Replace block
bgneal@312 2226 t.replace(newElm, elm, 1);
bgneal@312 2227 }
bgneal@312 2228
bgneal@312 2229 return newElm || elm;
bgneal@312 2230 },
bgneal@312 2231
bgneal@312 2232 findCommonAncestor : function(a, b) {
bgneal@312 2233 var ps = a, pe;
bgneal@312 2234
bgneal@312 2235 while (ps) {
bgneal@312 2236 pe = b;
bgneal@312 2237
bgneal@312 2238 while (pe && ps != pe)
bgneal@312 2239 pe = pe.parentNode;
bgneal@312 2240
bgneal@312 2241 if (ps == pe)
bgneal@312 2242 break;
bgneal@312 2243
bgneal@312 2244 ps = ps.parentNode;
bgneal@312 2245 }
bgneal@312 2246
bgneal@312 2247 if (!ps && a.ownerDocument)
bgneal@312 2248 return a.ownerDocument.documentElement;
bgneal@312 2249
bgneal@312 2250 return ps;
bgneal@312 2251 },
bgneal@312 2252
bgneal@312 2253 toHex : function(s) {
bgneal@312 2254 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
bgneal@312 2255
bgneal@312 2256 function hex(s) {
bgneal@312 2257 s = parseInt(s).toString(16);
bgneal@312 2258
bgneal@312 2259 return s.length > 1 ? s : '0' + s; // 0 -> 00
bgneal@312 2260 };
bgneal@312 2261
bgneal@312 2262 if (c) {
bgneal@312 2263 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
bgneal@312 2264
bgneal@312 2265 return s;
bgneal@312 2266 }
bgneal@312 2267
bgneal@312 2268 return s;
bgneal@312 2269 },
bgneal@312 2270
bgneal@312 2271 getClasses : function() {
bgneal@312 2272 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
bgneal@312 2273
bgneal@312 2274 if (t.classes)
bgneal@312 2275 return t.classes;
bgneal@312 2276
bgneal@312 2277 function addClasses(s) {
bgneal@312 2278 // IE style imports
bgneal@312 2279 each(s.imports, function(r) {
bgneal@312 2280 addClasses(r);
bgneal@312 2281 });
bgneal@312 2282
bgneal@312 2283 each(s.cssRules || s.rules, function(r) {
bgneal@312 2284 // Real type or fake it on IE
bgneal@312 2285 switch (r.type || 1) {
bgneal@312 2286 // Rule
bgneal@312 2287 case 1:
bgneal@312 2288 if (r.selectorText) {
bgneal@312 2289 each(r.selectorText.split(','), function(v) {
bgneal@312 2290 v = v.replace(/^\s*|\s*$|^\s\./g, "");
bgneal@312 2291
bgneal@312 2292 // Is internal or it doesn't contain a class
bgneal@312 2293 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
bgneal@312 2294 return;
bgneal@312 2295
bgneal@312 2296 // Remove everything but class name
bgneal@312 2297 ov = v;
bgneal@312 2298 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
bgneal@312 2299
bgneal@312 2300 // Filter classes
bgneal@312 2301 if (f && !(v = f(v, ov)))
bgneal@312 2302 return;
bgneal@312 2303
bgneal@312 2304 if (!lo[v]) {
bgneal@312 2305 cl.push({'class' : v});
bgneal@312 2306 lo[v] = 1;
bgneal@312 2307 }
bgneal@312 2308 });
bgneal@312 2309 }
bgneal@312 2310 break;
bgneal@312 2311
bgneal@312 2312 // Import
bgneal@312 2313 case 3:
bgneal@312 2314 addClasses(r.styleSheet);
bgneal@312 2315 break;
bgneal@312 2316 }
bgneal@312 2317 });
bgneal@312 2318 };
bgneal@312 2319
bgneal@312 2320 try {
bgneal@312 2321 each(t.doc.styleSheets, addClasses);
bgneal@312 2322 } catch (ex) {
bgneal@312 2323 // Ignore
bgneal@312 2324 }
bgneal@312 2325
bgneal@312 2326 if (cl.length > 0)
bgneal@312 2327 t.classes = cl;
bgneal@312 2328
bgneal@312 2329 return cl;
bgneal@312 2330 },
bgneal@312 2331
bgneal@312 2332 run : function(e, f, s) {
bgneal@312 2333 var t = this, o;
bgneal@312 2334
bgneal@312 2335 if (t.doc && typeof(e) === 'string')
bgneal@312 2336 e = t.get(e);
bgneal@312 2337
bgneal@312 2338 if (!e)
bgneal@312 2339 return false;
bgneal@312 2340
bgneal@312 2341 s = s || this;
bgneal@312 2342 if (!e.nodeType && (e.length || e.length === 0)) {
bgneal@312 2343 o = [];
bgneal@312 2344
bgneal@312 2345 each(e, function(e, i) {
bgneal@312 2346 if (e) {
bgneal@312 2347 if (typeof(e) == 'string')
bgneal@312 2348 e = t.doc.getElementById(e);
bgneal@312 2349
bgneal@312 2350 o.push(f.call(s, e, i));
bgneal@312 2351 }
bgneal@312 2352 });
bgneal@312 2353
bgneal@312 2354 return o;
bgneal@312 2355 }
bgneal@312 2356
bgneal@312 2357 return f.call(s, e);
bgneal@312 2358 },
bgneal@312 2359
bgneal@312 2360 getAttribs : function(n) {
bgneal@312 2361 var o;
bgneal@312 2362
bgneal@312 2363 n = this.get(n);
bgneal@312 2364
bgneal@312 2365 if (!n)
bgneal@312 2366 return [];
bgneal@312 2367
bgneal@312 2368 if (isIE) {
bgneal@312 2369 o = [];
bgneal@312 2370
bgneal@312 2371 // Object will throw exception in IE
bgneal@312 2372 if (n.nodeName == 'OBJECT')
bgneal@312 2373 return n.attributes;
bgneal@312 2374
bgneal@312 2375 // IE doesn't keep the selected attribute if you clone option elements
bgneal@312 2376 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
bgneal@312 2377 o.push({specified : 1, nodeName : 'selected'});
bgneal@312 2378
bgneal@312 2379 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
bgneal@312 2380 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
bgneal@312 2381 o.push({specified : 1, nodeName : a});
bgneal@312 2382 });
bgneal@312 2383
bgneal@312 2384 return o;
bgneal@312 2385 }
bgneal@312 2386
bgneal@312 2387 return n.attributes;
bgneal@312 2388 },
bgneal@312 2389
bgneal@312 2390 destroy : function(s) {
bgneal@312 2391 var t = this;
bgneal@312 2392
bgneal@312 2393 if (t.events)
bgneal@312 2394 t.events.destroy();
bgneal@312 2395
bgneal@312 2396 t.win = t.doc = t.root = t.events = null;
bgneal@312 2397
bgneal@312 2398 // Manual destroy then remove unload handler
bgneal@312 2399 if (!s)
bgneal@312 2400 tinymce.removeUnload(t.destroy);
bgneal@312 2401 },
bgneal@312 2402
bgneal@312 2403 createRng : function() {
bgneal@312 2404 var d = this.doc;
bgneal@312 2405
bgneal@312 2406 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
bgneal@312 2407 },
bgneal@312 2408
bgneal@312 2409 nodeIndex : function(node, normalized) {
bgneal@312 2410 var idx = 0, lastNodeType, lastNode, nodeType;
bgneal@312 2411
bgneal@312 2412 if (node) {
bgneal@312 2413 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
bgneal@312 2414 nodeType = node.nodeType;
bgneal@312 2415
bgneal@312 2416 // Normalize text nodes
bgneal@312 2417 if (normalized && nodeType == 3) {
bgneal@312 2418 if (nodeType == lastNodeType || !node.nodeValue.length)
bgneal@312 2419 continue;
bgneal@312 2420 }
bgneal@312 2421
bgneal@312 2422 idx++;
bgneal@312 2423 lastNodeType = nodeType;
bgneal@312 2424 }
bgneal@312 2425 }
bgneal@312 2426
bgneal@312 2427 return idx;
bgneal@312 2428 },
bgneal@312 2429
bgneal@312 2430 split : function(pe, e, re) {
bgneal@312 2431 var t = this, r = t.createRng(), bef, aft, pa;
bgneal@312 2432
bgneal@312 2433 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
bgneal@312 2434 // but we don't want that in our code since it serves no purpose for the end user
bgneal@312 2435 // For example if this is chopped:
bgneal@312 2436 // <p>text 1<span><b>CHOP</b></span>text 2</p>
bgneal@312 2437 // would produce:
bgneal@312 2438 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
bgneal@312 2439 // this function will then trim of empty edges and produce:
bgneal@312 2440 // <p>text 1</p><b>CHOP</b><p>text 2</p>
bgneal@312 2441 function trim(node) {
bgneal@312 2442 var i, children = node.childNodes;
bgneal@312 2443
bgneal@312 2444 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
bgneal@312 2445 return;
bgneal@312 2446
bgneal@312 2447 for (i = children.length - 1; i >= 0; i--)
bgneal@312 2448 trim(children[i]);
bgneal@312 2449
bgneal@312 2450 if (node.nodeType != 9) {
bgneal@312 2451 // Keep non whitespace text nodes
bgneal@312 2452 if (node.nodeType == 3 && node.nodeValue.length > 0)
bgneal@312 2453 return;
bgneal@312 2454
bgneal@312 2455 if (node.nodeType == 1) {
bgneal@312 2456 // If the only child is a bookmark then move it up
bgneal@312 2457 children = node.childNodes;
bgneal@312 2458 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
bgneal@312 2459 node.parentNode.insertBefore(children[0], node);
bgneal@312 2460
bgneal@312 2461 // Keep non empty elements or img, hr etc
bgneal@312 2462 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
bgneal@312 2463 return;
bgneal@312 2464 }
bgneal@312 2465
bgneal@312 2466 t.remove(node);
bgneal@312 2467 }
bgneal@312 2468
bgneal@312 2469 return node;
bgneal@312 2470 };
bgneal@312 2471
bgneal@312 2472 if (pe && e) {
bgneal@312 2473 // Get before chunk
bgneal@312 2474 r.setStart(pe.parentNode, t.nodeIndex(pe));
bgneal@312 2475 r.setEnd(e.parentNode, t.nodeIndex(e));
bgneal@312 2476 bef = r.extractContents();
bgneal@312 2477
bgneal@312 2478 // Get after chunk
bgneal@312 2479 r = t.createRng();
bgneal@312 2480 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
bgneal@312 2481 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
bgneal@312 2482 aft = r.extractContents();
bgneal@312 2483
bgneal@312 2484 // Insert before chunk
bgneal@312 2485 pa = pe.parentNode;
bgneal@312 2486 pa.insertBefore(trim(bef), pe);
bgneal@312 2487
bgneal@312 2488 // Insert middle chunk
bgneal@312 2489 if (re)
bgneal@312 2490 pa.replaceChild(re, e);
bgneal@312 2491 else
bgneal@312 2492 pa.insertBefore(e, pe);
bgneal@312 2493
bgneal@312 2494 // Insert after chunk
bgneal@312 2495 pa.insertBefore(trim(aft), pe);
bgneal@312 2496 t.remove(pe);
bgneal@312 2497
bgneal@312 2498 return re || e;
bgneal@312 2499 }
bgneal@312 2500 },
bgneal@312 2501
bgneal@312 2502 bind : function(target, name, func, scope) {
bgneal@312 2503 var t = this;
bgneal@312 2504
bgneal@312 2505 if (!t.events)
bgneal@312 2506 t.events = new tinymce.dom.EventUtils();
bgneal@312 2507
bgneal@312 2508 return t.events.add(target, name, func, scope || this);
bgneal@312 2509 },
bgneal@312 2510
bgneal@312 2511 unbind : function(target, name, func) {
bgneal@312 2512 var t = this;
bgneal@312 2513
bgneal@312 2514 if (!t.events)
bgneal@312 2515 t.events = new tinymce.dom.EventUtils();
bgneal@312 2516
bgneal@312 2517 return t.events.remove(target, name, func);
bgneal@312 2518 },
bgneal@312 2519
bgneal@312 2520
bgneal@312 2521 _findSib : function(node, selector, name) {
bgneal@312 2522 var t = this, f = selector;
bgneal@312 2523
bgneal@312 2524 if (node) {
bgneal@312 2525 // If expression make a function of it using is
bgneal@312 2526 if (is(f, 'string')) {
bgneal@312 2527 f = function(node) {
bgneal@312 2528 return t.is(node, selector);
bgneal@312 2529 };
bgneal@312 2530 }
bgneal@312 2531
bgneal@312 2532 // Loop all siblings
bgneal@312 2533 for (node = node[name]; node; node = node[name]) {
bgneal@312 2534 if (f(node))
bgneal@312 2535 return node;
bgneal@312 2536 }
bgneal@312 2537 }
bgneal@312 2538
bgneal@312 2539 return null;
bgneal@312 2540 },
bgneal@312 2541
bgneal@312 2542 _isRes : function(c) {
bgneal@312 2543 // Is live resizble element
bgneal@312 2544 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
bgneal@312 2545 }
bgneal@312 2546
bgneal@312 2547 /*
bgneal@312 2548 walk : function(n, f, s) {
bgneal@312 2549 var d = this.doc, w;
bgneal@312 2550
bgneal@312 2551 if (d.createTreeWalker) {
bgneal@312 2552 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
bgneal@312 2553
bgneal@312 2554 while ((n = w.nextNode()) != null)
bgneal@312 2555 f.call(s || this, n);
bgneal@312 2556 } else
bgneal@312 2557 tinymce.walk(n, f, 'childNodes', s);
bgneal@312 2558 }
bgneal@312 2559 */
bgneal@312 2560
bgneal@312 2561 /*
bgneal@312 2562 toRGB : function(s) {
bgneal@312 2563 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
bgneal@312 2564
bgneal@312 2565 if (c) {
bgneal@312 2566 // #FFF -> #FFFFFF
bgneal@312 2567 if (!is(c[3]))
bgneal@312 2568 c[3] = c[2] = c[1];
bgneal@312 2569
bgneal@312 2570 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
bgneal@312 2571 }
bgneal@312 2572
bgneal@312 2573 return s;
bgneal@312 2574 }
bgneal@312 2575 */
bgneal@312 2576 });
bgneal@312 2577
bgneal@312 2578 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
bgneal@312 2579 })(tinymce);
bgneal@312 2580
bgneal@312 2581 (function(ns) {
bgneal@312 2582 // Range constructor
bgneal@312 2583 function Range(dom) {
bgneal@312 2584 var t = this,
bgneal@312 2585 doc = dom.doc,
bgneal@312 2586 EXTRACT = 0,
bgneal@312 2587 CLONE = 1,
bgneal@312 2588 DELETE = 2,
bgneal@312 2589 TRUE = true,
bgneal@312 2590 FALSE = false,
bgneal@312 2591 START_OFFSET = 'startOffset',
bgneal@312 2592 START_CONTAINER = 'startContainer',
bgneal@312 2593 END_CONTAINER = 'endContainer',
bgneal@312 2594 END_OFFSET = 'endOffset',
bgneal@312 2595 extend = tinymce.extend,
bgneal@312 2596 nodeIndex = dom.nodeIndex;
bgneal@312 2597
bgneal@312 2598 extend(t, {
bgneal@312 2599 // Inital states
bgneal@312 2600 startContainer : doc,
bgneal@312 2601 startOffset : 0,
bgneal@312 2602 endContainer : doc,
bgneal@312 2603 endOffset : 0,
bgneal@312 2604 collapsed : TRUE,
bgneal@312 2605 commonAncestorContainer : doc,
bgneal@312 2606
bgneal@312 2607 // Range constants
bgneal@312 2608 START_TO_START : 0,
bgneal@312 2609 START_TO_END : 1,
bgneal@312 2610 END_TO_END : 2,
bgneal@312 2611 END_TO_START : 3,
bgneal@312 2612
bgneal@312 2613 // Public methods
bgneal@312 2614 setStart : setStart,
bgneal@312 2615 setEnd : setEnd,
bgneal@312 2616 setStartBefore : setStartBefore,
bgneal@312 2617 setStartAfter : setStartAfter,
bgneal@312 2618 setEndBefore : setEndBefore,
bgneal@312 2619 setEndAfter : setEndAfter,
bgneal@312 2620 collapse : collapse,
bgneal@312 2621 selectNode : selectNode,
bgneal@312 2622 selectNodeContents : selectNodeContents,
bgneal@312 2623 compareBoundaryPoints : compareBoundaryPoints,
bgneal@312 2624 deleteContents : deleteContents,
bgneal@312 2625 extractContents : extractContents,
bgneal@312 2626 cloneContents : cloneContents,
bgneal@312 2627 insertNode : insertNode,
bgneal@312 2628 surroundContents : surroundContents,
bgneal@312 2629 cloneRange : cloneRange
bgneal@312 2630 });
bgneal@312 2631
bgneal@312 2632 function setStart(n, o) {
bgneal@312 2633 _setEndPoint(TRUE, n, o);
bgneal@312 2634 };
bgneal@312 2635
bgneal@312 2636 function setEnd(n, o) {
bgneal@312 2637 _setEndPoint(FALSE, n, o);
bgneal@312 2638 };
bgneal@312 2639
bgneal@312 2640 function setStartBefore(n) {
bgneal@312 2641 setStart(n.parentNode, nodeIndex(n));
bgneal@312 2642 };
bgneal@312 2643
bgneal@312 2644 function setStartAfter(n) {
bgneal@312 2645 setStart(n.parentNode, nodeIndex(n) + 1);
bgneal@312 2646 };
bgneal@312 2647
bgneal@312 2648 function setEndBefore(n) {
bgneal@312 2649 setEnd(n.parentNode, nodeIndex(n));
bgneal@312 2650 };
bgneal@312 2651
bgneal@312 2652 function setEndAfter(n) {
bgneal@312 2653 setEnd(n.parentNode, nodeIndex(n) + 1);
bgneal@312 2654 };
bgneal@312 2655
bgneal@312 2656 function collapse(ts) {
bgneal@312 2657 if (ts) {
bgneal@312 2658 t[END_CONTAINER] = t[START_CONTAINER];
bgneal@312 2659 t[END_OFFSET] = t[START_OFFSET];
bgneal@312 2660 } else {
bgneal@312 2661 t[START_CONTAINER] = t[END_CONTAINER];
bgneal@312 2662 t[START_OFFSET] = t[END_OFFSET];
bgneal@312 2663 }
bgneal@312 2664
bgneal@312 2665 t.collapsed = TRUE;
bgneal@312 2666 };
bgneal@312 2667
bgneal@312 2668 function selectNode(n) {
bgneal@312 2669 setStartBefore(n);
bgneal@312 2670 setEndAfter(n);
bgneal@312 2671 };
bgneal@312 2672
bgneal@312 2673 function selectNodeContents(n) {
bgneal@312 2674 setStart(n, 0);
bgneal@312 2675 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
bgneal@312 2676 };
bgneal@312 2677
bgneal@312 2678 function compareBoundaryPoints(h, r) {
bgneal@312 2679 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
bgneal@312 2680
bgneal@312 2681 // Check START_TO_START
bgneal@312 2682 if (h === 0)
bgneal@312 2683 return _compareBoundaryPoints(sc, so, sc, so);
bgneal@312 2684
bgneal@312 2685 // Check START_TO_END
bgneal@312 2686 if (h === 1)
bgneal@312 2687 return _compareBoundaryPoints(sc, so, ec, eo);
bgneal@312 2688
bgneal@312 2689 // Check END_TO_END
bgneal@312 2690 if (h === 2)
bgneal@312 2691 return _compareBoundaryPoints(ec, eo, ec, eo);
bgneal@312 2692
bgneal@312 2693 // Check END_TO_START
bgneal@312 2694 if (h === 3)
bgneal@312 2695 return _compareBoundaryPoints(ec, eo, sc, so);
bgneal@312 2696 };
bgneal@312 2697
bgneal@312 2698 function deleteContents() {
bgneal@312 2699 _traverse(DELETE);
bgneal@312 2700 };
bgneal@312 2701
bgneal@312 2702 function extractContents() {
bgneal@312 2703 return _traverse(EXTRACT);
bgneal@312 2704 };
bgneal@312 2705
bgneal@312 2706 function cloneContents() {
bgneal@312 2707 return _traverse(CLONE);
bgneal@312 2708 };
bgneal@312 2709
bgneal@312 2710 function insertNode(n) {
bgneal@312 2711 var startContainer = this[START_CONTAINER],
bgneal@312 2712 startOffset = this[START_OFFSET], nn, o;
bgneal@312 2713
bgneal@312 2714 // Node is TEXT_NODE or CDATA
bgneal@312 2715 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
bgneal@312 2716 if (!startOffset) {
bgneal@312 2717 // At the start of text
bgneal@312 2718 startContainer.parentNode.insertBefore(n, startContainer);
bgneal@312 2719 } else if (startOffset >= startContainer.nodeValue.length) {
bgneal@312 2720 // At the end of text
bgneal@312 2721 dom.insertAfter(n, startContainer);
bgneal@312 2722 } else {
bgneal@312 2723 // Middle, need to split
bgneal@312 2724 nn = startContainer.splitText(startOffset);
bgneal@312 2725 startContainer.parentNode.insertBefore(n, nn);
bgneal@312 2726 }
bgneal@312 2727 } else {
bgneal@312 2728 // Insert element node
bgneal@312 2729 if (startContainer.childNodes.length > 0)
bgneal@312 2730 o = startContainer.childNodes[startOffset];
bgneal@312 2731
bgneal@312 2732 if (o)
bgneal@312 2733 startContainer.insertBefore(n, o);
bgneal@312 2734 else
bgneal@312 2735 startContainer.appendChild(n);
bgneal@312 2736 }
bgneal@312 2737 };
bgneal@312 2738
bgneal@312 2739 function surroundContents(n) {
bgneal@312 2740 var f = t.extractContents();
bgneal@312 2741
bgneal@312 2742 t.insertNode(n);
bgneal@312 2743 n.appendChild(f);
bgneal@312 2744 t.selectNode(n);
bgneal@312 2745 };
bgneal@312 2746
bgneal@312 2747 function cloneRange() {
bgneal@312 2748 return extend(new Range(dom), {
bgneal@312 2749 startContainer : t[START_CONTAINER],
bgneal@312 2750 startOffset : t[START_OFFSET],
bgneal@312 2751 endContainer : t[END_CONTAINER],
bgneal@312 2752 endOffset : t[END_OFFSET],
bgneal@312 2753 collapsed : t.collapsed,
bgneal@312 2754 commonAncestorContainer : t.commonAncestorContainer
bgneal@312 2755 });
bgneal@312 2756 };
bgneal@312 2757
bgneal@312 2758 // Private methods
bgneal@312 2759
bgneal@312 2760 function _getSelectedNode(container, offset) {
bgneal@312 2761 var child;
bgneal@312 2762
bgneal@312 2763 if (container.nodeType == 3 /* TEXT_NODE */)
bgneal@312 2764 return container;
bgneal@312 2765
bgneal@312 2766 if (offset < 0)
bgneal@312 2767 return container;
bgneal@312 2768
bgneal@312 2769 child = container.firstChild;
bgneal@312 2770 while (child && offset > 0) {
bgneal@312 2771 --offset;
bgneal@312 2772 child = child.nextSibling;
bgneal@312 2773 }
bgneal@312 2774
bgneal@312 2775 if (child)
bgneal@312 2776 return child;
bgneal@312 2777
bgneal@312 2778 return container;
bgneal@312 2779 };
bgneal@312 2780
bgneal@312 2781 function _isCollapsed() {
bgneal@312 2782 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
bgneal@312 2783 };
bgneal@312 2784
bgneal@312 2785 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
bgneal@312 2786 var c, offsetC, n, cmnRoot, childA, childB;
bgneal@312 2787
bgneal@312 2788 // In the first case the boundary-points have the same container. A is before B
bgneal@312 2789 // if its offset is less than the offset of B, A is equal to B if its offset is
bgneal@312 2790 // equal to the offset of B, and A is after B if its offset is greater than the
bgneal@312 2791 // offset of B.
bgneal@312 2792 if (containerA == containerB) {
bgneal@312 2793 if (offsetA == offsetB)
bgneal@312 2794 return 0; // equal
bgneal@312 2795
bgneal@312 2796 if (offsetA < offsetB)
bgneal@312 2797 return -1; // before
bgneal@312 2798
bgneal@312 2799 return 1; // after
bgneal@312 2800 }
bgneal@312 2801
bgneal@312 2802 // In the second case a child node C of the container of A is an ancestor
bgneal@312 2803 // container of B. In this case, A is before B if the offset of A is less than or
bgneal@312 2804 // equal to the index of the child node C and A is after B otherwise.
bgneal@312 2805 c = containerB;
bgneal@312 2806 while (c && c.parentNode != containerA)
bgneal@312 2807 c = c.parentNode;
bgneal@312 2808
bgneal@312 2809 if (c) {
bgneal@312 2810 offsetC = 0;
bgneal@312 2811 n = containerA.firstChild;
bgneal@312 2812
bgneal@312 2813 while (n != c && offsetC < offsetA) {
bgneal@312 2814 offsetC++;
bgneal@312 2815 n = n.nextSibling;
bgneal@312 2816 }
bgneal@312 2817
bgneal@312 2818 if (offsetA <= offsetC)
bgneal@312 2819 return -1; // before
bgneal@312 2820
bgneal@312 2821 return 1; // after
bgneal@312 2822 }
bgneal@312 2823
bgneal@312 2824 // In the third case a child node C of the container of B is an ancestor container
bgneal@312 2825 // of A. In this case, A is before B if the index of the child node C is less than
bgneal@312 2826 // the offset of B and A is after B otherwise.
bgneal@312 2827 c = containerA;
bgneal@312 2828 while (c && c.parentNode != containerB) {
bgneal@312 2829 c = c.parentNode;
bgneal@312 2830 }
bgneal@312 2831
bgneal@312 2832 if (c) {
bgneal@312 2833 offsetC = 0;
bgneal@312 2834 n = containerB.firstChild;
bgneal@312 2835
bgneal@312 2836 while (n != c && offsetC < offsetB) {
bgneal@312 2837 offsetC++;
bgneal@312 2838 n = n.nextSibling;
bgneal@312 2839 }
bgneal@312 2840
bgneal@312 2841 if (offsetC < offsetB)
bgneal@312 2842 return -1; // before
bgneal@312 2843
bgneal@312 2844 return 1; // after
bgneal@312 2845 }
bgneal@312 2846
bgneal@312 2847 // In the fourth case, none of three other cases hold: the containers of A and B
bgneal@312 2848 // are siblings or descendants of sibling nodes. In this case, A is before B if
bgneal@312 2849 // the container of A is before the container of B in a pre-order traversal of the
bgneal@312 2850 // Ranges' context tree and A is after B otherwise.
bgneal@312 2851 cmnRoot = dom.findCommonAncestor(containerA, containerB);
bgneal@312 2852 childA = containerA;
bgneal@312 2853
bgneal@312 2854 while (childA && childA.parentNode != cmnRoot)
bgneal@312 2855 childA = childA.parentNode;
bgneal@312 2856
bgneal@312 2857 if (!childA)
bgneal@312 2858 childA = cmnRoot;
bgneal@312 2859
bgneal@312 2860 childB = containerB;
bgneal@312 2861 while (childB && childB.parentNode != cmnRoot)
bgneal@312 2862 childB = childB.parentNode;
bgneal@312 2863
bgneal@312 2864 if (!childB)
bgneal@312 2865 childB = cmnRoot;
bgneal@312 2866
bgneal@312 2867 if (childA == childB)
bgneal@312 2868 return 0; // equal
bgneal@312 2869
bgneal@312 2870 n = cmnRoot.firstChild;
bgneal@312 2871 while (n) {
bgneal@312 2872 if (n == childA)
bgneal@312 2873 return -1; // before
bgneal@312 2874
bgneal@312 2875 if (n == childB)
bgneal@312 2876 return 1; // after
bgneal@312 2877
bgneal@312 2878 n = n.nextSibling;
bgneal@312 2879 }
bgneal@312 2880 };
bgneal@312 2881
bgneal@312 2882 function _setEndPoint(st, n, o) {
bgneal@312 2883 var ec, sc;
bgneal@312 2884
bgneal@312 2885 if (st) {
bgneal@312 2886 t[START_CONTAINER] = n;
bgneal@312 2887 t[START_OFFSET] = o;
bgneal@312 2888 } else {
bgneal@312 2889 t[END_CONTAINER] = n;
bgneal@312 2890 t[END_OFFSET] = o;
bgneal@312 2891 }
bgneal@312 2892
bgneal@312 2893 // If one boundary-point of a Range is set to have a root container
bgneal@312 2894 // other than the current one for the Range, the Range is collapsed to
bgneal@312 2895 // the new position. This enforces the restriction that both boundary-
bgneal@312 2896 // points of a Range must have the same root container.
bgneal@312 2897 ec = t[END_CONTAINER];
bgneal@312 2898 while (ec.parentNode)
bgneal@312 2899 ec = ec.parentNode;
bgneal@312 2900
bgneal@312 2901 sc = t[START_CONTAINER];
bgneal@312 2902 while (sc.parentNode)
bgneal@312 2903 sc = sc.parentNode;
bgneal@312 2904
bgneal@312 2905 if (sc == ec) {
bgneal@312 2906 // The start position of a Range is guaranteed to never be after the
bgneal@312 2907 // end position. To enforce this restriction, if the start is set to
bgneal@312 2908 // be at a position after the end, the Range is collapsed to that
bgneal@312 2909 // position.
bgneal@312 2910 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
bgneal@312 2911 t.collapse(st);
bgneal@312 2912 } else
bgneal@312 2913 t.collapse(st);
bgneal@312 2914
bgneal@312 2915 t.collapsed = _isCollapsed();
bgneal@312 2916 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
bgneal@312 2917 };
bgneal@312 2918
bgneal@312 2919 function _traverse(how) {
bgneal@312 2920 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
bgneal@312 2921
bgneal@312 2922 if (t[START_CONTAINER] == t[END_CONTAINER])
bgneal@312 2923 return _traverseSameContainer(how);
bgneal@312 2924
bgneal@312 2925 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@312 2926 if (p == t[START_CONTAINER])
bgneal@312 2927 return _traverseCommonStartContainer(c, how);
bgneal@312 2928
bgneal@312 2929 ++endContainerDepth;
bgneal@312 2930 }
bgneal@312 2931
bgneal@312 2932 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@312 2933 if (p == t[END_CONTAINER])
bgneal@312 2934 return _traverseCommonEndContainer(c, how);
bgneal@312 2935
bgneal@312 2936 ++startContainerDepth;
bgneal@312 2937 }
bgneal@312 2938
bgneal@312 2939 depthDiff = startContainerDepth - endContainerDepth;
bgneal@312 2940
bgneal@312 2941 startNode = t[START_CONTAINER];
bgneal@312 2942 while (depthDiff > 0) {
bgneal@312 2943 startNode = startNode.parentNode;
bgneal@312 2944 depthDiff--;
bgneal@312 2945 }
bgneal@312 2946
bgneal@312 2947 endNode = t[END_CONTAINER];
bgneal@312 2948 while (depthDiff < 0) {
bgneal@312 2949 endNode = endNode.parentNode;
bgneal@312 2950 depthDiff++;
bgneal@312 2951 }
bgneal@312 2952
bgneal@312 2953 // ascend the ancestor hierarchy until we have a common parent.
bgneal@312 2954 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
bgneal@312 2955 startNode = sp;
bgneal@312 2956 endNode = ep;
bgneal@312 2957 }
bgneal@312 2958
bgneal@312 2959 return _traverseCommonAncestors(startNode, endNode, how);
bgneal@312 2960 };
bgneal@312 2961
bgneal@312 2962 function _traverseSameContainer(how) {
bgneal@312 2963 var frag, s, sub, n, cnt, sibling, xferNode;
bgneal@312 2964
bgneal@312 2965 if (how != DELETE)
bgneal@312 2966 frag = doc.createDocumentFragment();
bgneal@312 2967
bgneal@312 2968 // If selection is empty, just return the fragment
bgneal@312 2969 if (t[START_OFFSET] == t[END_OFFSET])
bgneal@312 2970 return frag;
bgneal@312 2971
bgneal@312 2972 // Text node needs special case handling
bgneal@312 2973 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
bgneal@312 2974 // get the substring
bgneal@312 2975 s = t[START_CONTAINER].nodeValue;
bgneal@312 2976 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
bgneal@312 2977
bgneal@312 2978 // set the original text node to its new value
bgneal@312 2979 if (how != CLONE) {
bgneal@312 2980 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
bgneal@312 2981
bgneal@312 2982 // Nothing is partially selected, so collapse to start point
bgneal@312 2983 t.collapse(TRUE);
bgneal@312 2984 }
bgneal@312 2985
bgneal@312 2986 if (how == DELETE)
bgneal@312 2987 return;
bgneal@312 2988
bgneal@312 2989 frag.appendChild(doc.createTextNode(sub));
bgneal@312 2990 return frag;
bgneal@312 2991 }
bgneal@312 2992
bgneal@312 2993 // Copy nodes between the start/end offsets.
bgneal@312 2994 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
bgneal@312 2995 cnt = t[END_OFFSET] - t[START_OFFSET];
bgneal@312 2996
bgneal@312 2997 while (cnt > 0) {
bgneal@312 2998 sibling = n.nextSibling;
bgneal@312 2999 xferNode = _traverseFullySelected(n, how);
bgneal@312 3000
bgneal@312 3001 if (frag)
bgneal@312 3002 frag.appendChild( xferNode );
bgneal@312 3003
bgneal@312 3004 --cnt;
bgneal@312 3005 n = sibling;
bgneal@312 3006 }
bgneal@312 3007
bgneal@312 3008 // Nothing is partially selected, so collapse to start point
bgneal@312 3009 if (how != CLONE)
bgneal@312 3010 t.collapse(TRUE);
bgneal@312 3011
bgneal@312 3012 return frag;
bgneal@312 3013 };
bgneal@312 3014
bgneal@312 3015 function _traverseCommonStartContainer(endAncestor, how) {
bgneal@312 3016 var frag, n, endIdx, cnt, sibling, xferNode;
bgneal@312 3017
bgneal@312 3018 if (how != DELETE)
bgneal@312 3019 frag = doc.createDocumentFragment();
bgneal@312 3020
bgneal@312 3021 n = _traverseRightBoundary(endAncestor, how);
bgneal@312 3022
bgneal@312 3023 if (frag)
bgneal@312 3024 frag.appendChild(n);
bgneal@312 3025
bgneal@312 3026 endIdx = nodeIndex(endAncestor);
bgneal@312 3027 cnt = endIdx - t[START_OFFSET];
bgneal@312 3028
bgneal@312 3029 if (cnt <= 0) {
bgneal@312 3030 // Collapse to just before the endAncestor, which
bgneal@312 3031 // is partially selected.
bgneal@312 3032 if (how != CLONE) {
bgneal@312 3033 t.setEndBefore(endAncestor);
bgneal@312 3034 t.collapse(FALSE);
bgneal@312 3035 }
bgneal@312 3036
bgneal@312 3037 return frag;
bgneal@312 3038 }
bgneal@312 3039
bgneal@312 3040 n = endAncestor.previousSibling;
bgneal@312 3041 while (cnt > 0) {
bgneal@312 3042 sibling = n.previousSibling;
bgneal@312 3043 xferNode = _traverseFullySelected(n, how);
bgneal@312 3044
bgneal@312 3045 if (frag)
bgneal@312 3046 frag.insertBefore(xferNode, frag.firstChild);
bgneal@312 3047
bgneal@312 3048 --cnt;
bgneal@312 3049 n = sibling;
bgneal@312 3050 }
bgneal@312 3051
bgneal@312 3052 // Collapse to just before the endAncestor, which
bgneal@312 3053 // is partially selected.
bgneal@312 3054 if (how != CLONE) {
bgneal@312 3055 t.setEndBefore(endAncestor);
bgneal@312 3056 t.collapse(FALSE);
bgneal@312 3057 }
bgneal@312 3058
bgneal@312 3059 return frag;
bgneal@312 3060 };
bgneal@312 3061
bgneal@312 3062 function _traverseCommonEndContainer(startAncestor, how) {
bgneal@312 3063 var frag, startIdx, n, cnt, sibling, xferNode;
bgneal@312 3064
bgneal@312 3065 if (how != DELETE)
bgneal@312 3066 frag = doc.createDocumentFragment();
bgneal@312 3067
bgneal@312 3068 n = _traverseLeftBoundary(startAncestor, how);
bgneal@312 3069 if (frag)
bgneal@312 3070 frag.appendChild(n);
bgneal@312 3071
bgneal@312 3072 startIdx = nodeIndex(startAncestor);
bgneal@312 3073 ++startIdx; // Because we already traversed it....
bgneal@312 3074
bgneal@312 3075 cnt = t[END_OFFSET] - startIdx;
bgneal@312 3076 n = startAncestor.nextSibling;
bgneal@312 3077 while (cnt > 0) {
bgneal@312 3078 sibling = n.nextSibling;
bgneal@312 3079 xferNode = _traverseFullySelected(n, how);
bgneal@312 3080
bgneal@312 3081 if (frag)
bgneal@312 3082 frag.appendChild(xferNode);
bgneal@312 3083
bgneal@312 3084 --cnt;
bgneal@312 3085 n = sibling;
bgneal@312 3086 }
bgneal@312 3087
bgneal@312 3088 if (how != CLONE) {
bgneal@312 3089 t.setStartAfter(startAncestor);
bgneal@312 3090 t.collapse(TRUE);
bgneal@312 3091 }
bgneal@312 3092
bgneal@312 3093 return frag;
bgneal@312 3094 };
bgneal@312 3095
bgneal@312 3096 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
bgneal@312 3097 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
bgneal@312 3098
bgneal@312 3099 if (how != DELETE)
bgneal@312 3100 frag = doc.createDocumentFragment();
bgneal@312 3101
bgneal@312 3102 n = _traverseLeftBoundary(startAncestor, how);
bgneal@312 3103 if (frag)
bgneal@312 3104 frag.appendChild(n);
bgneal@312 3105
bgneal@312 3106 commonParent = startAncestor.parentNode;
bgneal@312 3107 startOffset = nodeIndex(startAncestor);
bgneal@312 3108 endOffset = nodeIndex(endAncestor);
bgneal@312 3109 ++startOffset;
bgneal@312 3110
bgneal@312 3111 cnt = endOffset - startOffset;
bgneal@312 3112 sibling = startAncestor.nextSibling;
bgneal@312 3113
bgneal@312 3114 while (cnt > 0) {
bgneal@312 3115 nextSibling = sibling.nextSibling;
bgneal@312 3116 n = _traverseFullySelected(sibling, how);
bgneal@312 3117
bgneal@312 3118 if (frag)
bgneal@312 3119 frag.appendChild(n);
bgneal@312 3120
bgneal@312 3121 sibling = nextSibling;
bgneal@312 3122 --cnt;
bgneal@312 3123 }
bgneal@312 3124
bgneal@312 3125 n = _traverseRightBoundary(endAncestor, how);
bgneal@312 3126
bgneal@312 3127 if (frag)
bgneal@312 3128 frag.appendChild(n);
bgneal@312 3129
bgneal@312 3130 if (how != CLONE) {
bgneal@312 3131 t.setStartAfter(startAncestor);
bgneal@312 3132 t.collapse(TRUE);
bgneal@312 3133 }
bgneal@312 3134
bgneal@312 3135 return frag;
bgneal@312 3136 };
bgneal@312 3137
bgneal@312 3138 function _traverseRightBoundary(root, how) {
bgneal@312 3139 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
bgneal@312 3140
bgneal@312 3141 if (next == root)
bgneal@312 3142 return _traverseNode(next, isFullySelected, FALSE, how);
bgneal@312 3143
bgneal@312 3144 parent = next.parentNode;
bgneal@312 3145 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@312 3146
bgneal@312 3147 while (parent) {
bgneal@312 3148 while (next) {
bgneal@312 3149 prevSibling = next.previousSibling;
bgneal@312 3150 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
bgneal@312 3151
bgneal@312 3152 if (how != DELETE)
bgneal@312 3153 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
bgneal@312 3154
bgneal@312 3155 isFullySelected = TRUE;
bgneal@312 3156 next = prevSibling;
bgneal@312 3157 }
bgneal@312 3158
bgneal@312 3159 if (parent == root)
bgneal@312 3160 return clonedParent;
bgneal@312 3161
bgneal@312 3162 next = parent.previousSibling;
bgneal@312 3163 parent = parent.parentNode;
bgneal@312 3164
bgneal@312 3165 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@312 3166
bgneal@312 3167 if (how != DELETE)
bgneal@312 3168 clonedGrandParent.appendChild(clonedParent);
bgneal@312 3169
bgneal@312 3170 clonedParent = clonedGrandParent;
bgneal@312 3171 }
bgneal@312 3172 };
bgneal@312 3173
bgneal@312 3174 function _traverseLeftBoundary(root, how) {
bgneal@312 3175 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
bgneal@312 3176
bgneal@312 3177 if (next == root)
bgneal@312 3178 return _traverseNode(next, isFullySelected, TRUE, how);
bgneal@312 3179
bgneal@312 3180 parent = next.parentNode;
bgneal@312 3181 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@312 3182
bgneal@312 3183 while (parent) {
bgneal@312 3184 while (next) {
bgneal@312 3185 nextSibling = next.nextSibling;
bgneal@312 3186 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
bgneal@312 3187
bgneal@312 3188 if (how != DELETE)
bgneal@312 3189 clonedParent.appendChild(clonedChild);
bgneal@312 3190
bgneal@312 3191 isFullySelected = TRUE;
bgneal@312 3192 next = nextSibling;
bgneal@312 3193 }
bgneal@312 3194
bgneal@312 3195 if (parent == root)
bgneal@312 3196 return clonedParent;
bgneal@312 3197
bgneal@312 3198 next = parent.nextSibling;
bgneal@312 3199 parent = parent.parentNode;
bgneal@312 3200
bgneal@312 3201 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@312 3202
bgneal@312 3203 if (how != DELETE)
bgneal@312 3204 clonedGrandParent.appendChild(clonedParent);
bgneal@312 3205
bgneal@312 3206 clonedParent = clonedGrandParent;
bgneal@312 3207 }
bgneal@312 3208 };
bgneal@312 3209
bgneal@312 3210 function _traverseNode(n, isFullySelected, isLeft, how) {
bgneal@312 3211 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
bgneal@312 3212
bgneal@312 3213 if (isFullySelected)
bgneal@312 3214 return _traverseFullySelected(n, how);
bgneal@312 3215
bgneal@312 3216 if (n.nodeType == 3 /* TEXT_NODE */) {
bgneal@312 3217 txtValue = n.nodeValue;
bgneal@312 3218
bgneal@312 3219 if (isLeft) {
bgneal@312 3220 offset = t[START_OFFSET];
bgneal@312 3221 newNodeValue = txtValue.substring(offset);
bgneal@312 3222 oldNodeValue = txtValue.substring(0, offset);
bgneal@312 3223 } else {
bgneal@312 3224 offset = t[END_OFFSET];
bgneal@312 3225 newNodeValue = txtValue.substring(0, offset);
bgneal@312 3226 oldNodeValue = txtValue.substring(offset);
bgneal@312 3227 }
bgneal@312 3228
bgneal@312 3229 if (how != CLONE)
bgneal@312 3230 n.nodeValue = oldNodeValue;
bgneal@312 3231
bgneal@312 3232 if (how == DELETE)
bgneal@312 3233 return;
bgneal@312 3234
bgneal@312 3235 newNode = n.cloneNode(FALSE);
bgneal@312 3236 newNode.nodeValue = newNodeValue;
bgneal@312 3237
bgneal@312 3238 return newNode;
bgneal@312 3239 }
bgneal@312 3240
bgneal@312 3241 if (how == DELETE)
bgneal@312 3242 return;
bgneal@312 3243
bgneal@312 3244 return n.cloneNode(FALSE);
bgneal@312 3245 };
bgneal@312 3246
bgneal@312 3247 function _traverseFullySelected(n, how) {
bgneal@312 3248 if (how != DELETE)
bgneal@312 3249 return how == CLONE ? n.cloneNode(TRUE) : n;
bgneal@312 3250
bgneal@312 3251 n.parentNode.removeChild(n);
bgneal@312 3252 };
bgneal@312 3253 };
bgneal@312 3254
bgneal@312 3255 ns.Range = Range;
bgneal@312 3256 })(tinymce.dom);
bgneal@312 3257
bgneal@312 3258 (function() {
bgneal@312 3259 function Selection(selection) {
bgneal@312 3260 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
bgneal@312 3261
bgneal@312 3262 // Returns a W3C DOM compatible range object by using the IE Range API
bgneal@312 3263 function getRange() {
bgneal@312 3264 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
bgneal@312 3265
bgneal@312 3266 // If selection is outside the current document just return an empty range
bgneal@312 3267 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
bgneal@312 3268 if (element.ownerDocument != dom.doc)
bgneal@312 3269 return domRange;
bgneal@312 3270
bgneal@312 3271 // Handle control selection or text selection of a image
bgneal@312 3272 if (ieRange.item || !element.hasChildNodes()) {
bgneal@312 3273 domRange.setStart(element.parentNode, dom.nodeIndex(element));
bgneal@312 3274 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
bgneal@312 3275
bgneal@312 3276 return domRange;
bgneal@312 3277 }
bgneal@312 3278
bgneal@312 3279 collapsed = selection.isCollapsed();
bgneal@312 3280
bgneal@312 3281 function findEndPoint(start) {
bgneal@312 3282 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
bgneal@312 3283
bgneal@312 3284 // Setup temp range and collapse it
bgneal@312 3285 checkRng = ieRange.duplicate();
bgneal@312 3286 checkRng.collapse(start);
bgneal@312 3287
bgneal@312 3288 // Create marker and insert it at the end of the endpoints parent
bgneal@312 3289 marker = dom.create('a');
bgneal@312 3290 parent = checkRng.parentElement();
bgneal@312 3291
bgneal@312 3292 // If parent doesn't have any children then set the container to that parent and the index to 0
bgneal@312 3293 if (!parent.hasChildNodes()) {
bgneal@312 3294 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
bgneal@312 3295 return;
bgneal@312 3296 }
bgneal@312 3297
bgneal@312 3298 parent.appendChild(marker);
bgneal@312 3299 checkRng.moveToElementText(marker);
bgneal@312 3300 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@312 3301 if (position > 0) {
bgneal@312 3302 // The position is after the end of the parent element.
bgneal@312 3303 // This is the case where IE puts the caret to the left edge of a table.
bgneal@312 3304 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
bgneal@312 3305 dom.remove(marker);
bgneal@312 3306 return;
bgneal@312 3307 }
bgneal@312 3308
bgneal@312 3309 // Setup node list and endIndex
bgneal@312 3310 nodes = tinymce.grep(parent.childNodes);
bgneal@312 3311 endIndex = nodes.length - 1;
bgneal@312 3312 // Perform a binary search for the position
bgneal@312 3313 while (startIndex <= endIndex) {
bgneal@312 3314 index = Math.floor((startIndex + endIndex) / 2);
bgneal@312 3315
bgneal@312 3316 // Insert marker and check it's position relative to the selection
bgneal@312 3317 parent.insertBefore(marker, nodes[index]);
bgneal@312 3318 checkRng.moveToElementText(marker);
bgneal@312 3319 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@312 3320 if (position > 0) {
bgneal@312 3321 // Marker is to the right
bgneal@312 3322 startIndex = index + 1;
bgneal@312 3323 } else if (position < 0) {
bgneal@312 3324 // Marker is to the left
bgneal@312 3325 endIndex = index - 1;
bgneal@312 3326 } else {
bgneal@312 3327 // Maker is where we are
bgneal@312 3328 found = true;
bgneal@312 3329 break;
bgneal@312 3330 }
bgneal@312 3331 }
bgneal@312 3332
bgneal@312 3333 // Setup container
bgneal@312 3334 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
bgneal@312 3335
bgneal@312 3336 // Handle element selection
bgneal@312 3337 if (container.nodeType == 1) {
bgneal@312 3338 dom.remove(marker);
bgneal@312 3339
bgneal@312 3340 // Find offset and container
bgneal@312 3341 offset = dom.nodeIndex(container);
bgneal@312 3342 container = container.parentNode;
bgneal@312 3343
bgneal@312 3344 // Move the offset if we are setting the end or the position is after an element
bgneal@312 3345 if (!start || index > 0)
bgneal@312 3346 offset++;
bgneal@312 3347 } else {
bgneal@312 3348 // Calculate offset within text node
bgneal@312 3349 if (position > 0 || index == 0) {
bgneal@312 3350 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@312 3351 offset = checkRng.text.length;
bgneal@312 3352 } else {
bgneal@312 3353 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@312 3354 offset = container.nodeValue.length - checkRng.text.length;
bgneal@312 3355 }
bgneal@312 3356
bgneal@312 3357 dom.remove(marker);
bgneal@312 3358 }
bgneal@312 3359
bgneal@312 3360 domRange[start ? 'setStart' : 'setEnd'](container, offset);
bgneal@312 3361 };
bgneal@312 3362
bgneal@312 3363 // Find start point
bgneal@312 3364 findEndPoint(true);
bgneal@312 3365
bgneal@312 3366 // Find end point if needed
bgneal@312 3367 if (!collapsed)
bgneal@312 3368 findEndPoint();
bgneal@312 3369
bgneal@312 3370 return domRange;
bgneal@312 3371 };
bgneal@312 3372
bgneal@312 3373 this.addRange = function(rng) {
bgneal@312 3374 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
bgneal@312 3375
bgneal@312 3376 function setEndPoint(start) {
bgneal@312 3377 var container, offset, marker, tmpRng, nodes;
bgneal@312 3378
bgneal@312 3379 marker = dom.create('a');
bgneal@312 3380 container = start ? startContainer : endContainer;
bgneal@312 3381 offset = start ? startOffset : endOffset;
bgneal@312 3382 tmpRng = ieRng.duplicate();
bgneal@312 3383
bgneal@312 3384 if (container == doc) {
bgneal@312 3385 container = body;
bgneal@312 3386 offset = 0;
bgneal@312 3387 }
bgneal@312 3388
bgneal@312 3389 if (container.nodeType == 3) {
bgneal@312 3390 container.parentNode.insertBefore(marker, container);
bgneal@312 3391 tmpRng.moveToElementText(marker);
bgneal@312 3392 tmpRng.moveStart('character', offset);
bgneal@312 3393 dom.remove(marker);
bgneal@312 3394 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@312 3395 } else {
bgneal@312 3396 nodes = container.childNodes;
bgneal@312 3397
bgneal@312 3398 if (nodes.length) {
bgneal@312 3399 if (offset >= nodes.length) {
bgneal@312 3400 dom.insertAfter(marker, nodes[nodes.length - 1]);
bgneal@312 3401 } else {
bgneal@312 3402 container.insertBefore(marker, nodes[offset]);
bgneal@312 3403 }
bgneal@312 3404
bgneal@312 3405 tmpRng.moveToElementText(marker);
bgneal@312 3406 } else {
bgneal@312 3407 // Empty node selection for example <div>|</div>
bgneal@312 3408 marker = doc.createTextNode(invisibleChar);
bgneal@312 3409 container.appendChild(marker);
bgneal@312 3410 tmpRng.moveToElementText(marker.parentNode);
bgneal@312 3411 tmpRng.collapse(TRUE);
bgneal@312 3412 }
bgneal@312 3413
bgneal@312 3414 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@312 3415 dom.remove(marker);
bgneal@312 3416 }
bgneal@312 3417 }
bgneal@312 3418
bgneal@312 3419 // Destroy cached range
bgneal@312 3420 this.destroy();
bgneal@312 3421
bgneal@312 3422 // Setup some shorter versions
bgneal@312 3423 startContainer = rng.startContainer;
bgneal@312 3424 startOffset = rng.startOffset;
bgneal@312 3425 endContainer = rng.endContainer;
bgneal@312 3426 endOffset = rng.endOffset;
bgneal@312 3427 ieRng = body.createTextRange();
bgneal@312 3428
bgneal@312 3429 // If single element selection then try making a control selection out of it
bgneal@312 3430 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
bgneal@312 3431 if (startOffset == endOffset - 1) {
bgneal@312 3432 try {
bgneal@312 3433 ctrlRng = body.createControlRange();
bgneal@312 3434 ctrlRng.addElement(startContainer.childNodes[startOffset]);
bgneal@312 3435 ctrlRng.select();
bgneal@312 3436 ctrlRng.scrollIntoView();
bgneal@312 3437 return;
bgneal@312 3438 } catch (ex) {
bgneal@312 3439 // Ignore
bgneal@312 3440 }
bgneal@312 3441 }
bgneal@312 3442 }
bgneal@312 3443
bgneal@312 3444 // Set start/end point of selection
bgneal@312 3445 setEndPoint(true);
bgneal@312 3446 setEndPoint();
bgneal@312 3447
bgneal@312 3448 // Select the new range and scroll it into view
bgneal@312 3449 ieRng.select();
bgneal@312 3450 ieRng.scrollIntoView();
bgneal@312 3451 };
bgneal@312 3452
bgneal@312 3453 this.getRangeAt = function() {
bgneal@312 3454 // Setup new range if the cache is empty
bgneal@312 3455 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
bgneal@312 3456 range = getRange();
bgneal@312 3457
bgneal@312 3458 // Store away text range for next call
bgneal@312 3459 lastIERng = selection.getRng();
bgneal@312 3460 }
bgneal@312 3461
bgneal@312 3462 // IE will say that the range is equal then produce an invalid argument exception
bgneal@312 3463 // if you perform specific operations in a keyup event. For example Ctrl+Del.
bgneal@312 3464 // This hack will invalidate the range cache if the exception occurs
bgneal@312 3465 try {
bgneal@312 3466 range.startContainer.nextSibling;
bgneal@312 3467 } catch (ex) {
bgneal@312 3468 range = getRange();
bgneal@312 3469 lastIERng = null;
bgneal@312 3470 }
bgneal@312 3471
bgneal@312 3472 // Return cached range
bgneal@312 3473 return range;
bgneal@312 3474 };
bgneal@312 3475
bgneal@312 3476 this.destroy = function() {
bgneal@312 3477 // Destroy cached range and last IE range to avoid memory leaks
bgneal@312 3478 lastIERng = range = null;
bgneal@312 3479 };
bgneal@312 3480
bgneal@312 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@312 3482 if (selection.dom.boxModel) {
bgneal@312 3483 (function() {
bgneal@312 3484 var doc = dom.doc, body = doc.body, started, startRng;
bgneal@312 3485
bgneal@312 3486 // Make HTML element unselectable since we are going to handle selection by hand
bgneal@312 3487 doc.documentElement.unselectable = TRUE;
bgneal@312 3488
bgneal@312 3489 // Return range from point or null if it failed
bgneal@312 3490 function rngFromPoint(x, y) {
bgneal@312 3491 var rng = body.createTextRange();
bgneal@312 3492
bgneal@312 3493 try {
bgneal@312 3494 rng.moveToPoint(x, y);
bgneal@312 3495 } catch (ex) {
bgneal@312 3496 // IE sometimes throws and exception, so lets just ignore it
bgneal@312 3497 rng = null;
bgneal@312 3498 }
bgneal@312 3499
bgneal@312 3500 return rng;
bgneal@312 3501 };
bgneal@312 3502
bgneal@312 3503 // Fires while the selection is changing
bgneal@312 3504 function selectionChange(e) {
bgneal@312 3505 var pointRng;
bgneal@312 3506
bgneal@312 3507 // Check if the button is down or not
bgneal@312 3508 if (e.button) {
bgneal@312 3509 // Create range from mouse position
bgneal@312 3510 pointRng = rngFromPoint(e.x, e.y);
bgneal@312 3511
bgneal@312 3512 if (pointRng) {
bgneal@312 3513 // Check if pointRange is before/after selection then change the endPoint
bgneal@312 3514 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
bgneal@312 3515 pointRng.setEndPoint('StartToStart', startRng);
bgneal@312 3516 else
bgneal@312 3517 pointRng.setEndPoint('EndToEnd', startRng);
bgneal@312 3518
bgneal@312 3519 pointRng.select();
bgneal@312 3520 }
bgneal@312 3521 } else
bgneal@312 3522 endSelection();
bgneal@312 3523 }
bgneal@312 3524
bgneal@312 3525 // Removes listeners
bgneal@312 3526 function endSelection() {
bgneal@312 3527 dom.unbind(doc, 'mouseup', endSelection);
bgneal@312 3528 dom.unbind(doc, 'mousemove', selectionChange);
bgneal@312 3529 started = 0;
bgneal@312 3530 };
bgneal@312 3531
bgneal@312 3532 // Detect when user selects outside BODY
bgneal@312 3533 dom.bind(doc, 'mousedown', function(e) {
bgneal@312 3534 if (e.target.nodeName === 'HTML') {
bgneal@312 3535 if (started)
bgneal@312 3536 endSelection();
bgneal@312 3537
bgneal@312 3538 started = 1;
bgneal@312 3539
bgneal@312 3540 // Setup start position
bgneal@312 3541 startRng = rngFromPoint(e.x, e.y);
bgneal@312 3542 if (startRng) {
bgneal@312 3543 // Listen for selection change events
bgneal@312 3544 dom.bind(doc, 'mouseup', endSelection);
bgneal@312 3545 dom.bind(doc, 'mousemove', selectionChange);
bgneal@312 3546
bgneal@312 3547 startRng.select();
bgneal@312 3548 }
bgneal@312 3549 }
bgneal@312 3550 });
bgneal@312 3551 })();
bgneal@312 3552 }
bgneal@312 3553 };
bgneal@312 3554
bgneal@312 3555 // Expose the selection object
bgneal@312 3556 tinymce.dom.TridentSelection = Selection;
bgneal@312 3557 })();
bgneal@312 3558
bgneal@312 3559
bgneal@312 3560 /*
bgneal@312 3561 * Sizzle CSS Selector Engine - v1.0
bgneal@312 3562 * Copyright 2009, The Dojo Foundation
bgneal@312 3563 * Released under the MIT, BSD, and GPL Licenses.
bgneal@312 3564 * More information: http://sizzlejs.com/
bgneal@312 3565 */
bgneal@312 3566 (function(){
bgneal@312 3567
bgneal@312 3568 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
bgneal@312 3569 done = 0,
bgneal@312 3570 toString = Object.prototype.toString,
bgneal@312 3571 hasDuplicate = false,
bgneal@312 3572 baseHasDuplicate = true;
bgneal@312 3573
bgneal@312 3574 // Here we check if the JavaScript engine is using some sort of
bgneal@312 3575 // optimization where it does not always call our comparision
bgneal@312 3576 // function. If that is the case, discard the hasDuplicate value.
bgneal@312 3577 // Thus far that includes Google Chrome.
bgneal@312 3578 [0, 0].sort(function(){
bgneal@312 3579 baseHasDuplicate = false;
bgneal@312 3580 return 0;
bgneal@312 3581 });
bgneal@312 3582
bgneal@312 3583 var Sizzle = function(selector, context, results, seed) {
bgneal@312 3584 results = results || [];
bgneal@312 3585 context = context || document;
bgneal@312 3586
bgneal@312 3587 var origContext = context;
bgneal@312 3588
bgneal@312 3589 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
bgneal@312 3590 return [];
bgneal@312 3591 }
bgneal@312 3592
bgneal@312 3593 if ( !selector || typeof selector !== "string" ) {
bgneal@312 3594 return results;
bgneal@312 3595 }
bgneal@312 3596
bgneal@312 3597 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
bgneal@312 3598 soFar = selector, ret, cur, pop, i;
bgneal@312 3599
bgneal@312 3600 // Reset the position of the chunker regexp (start from head)
bgneal@312 3601 do {
bgneal@312 3602 chunker.exec("");
bgneal@312 3603 m = chunker.exec(soFar);
bgneal@312 3604
bgneal@312 3605 if ( m ) {
bgneal@312 3606 soFar = m[3];
bgneal@312 3607
bgneal@312 3608 parts.push( m[1] );
bgneal@312 3609
bgneal@312 3610 if ( m[2] ) {
bgneal@312 3611 extra = m[3];
bgneal@312 3612 break;
bgneal@312 3613 }
bgneal@312 3614 }
bgneal@312 3615 } while ( m );
bgneal@312 3616
bgneal@312 3617 if ( parts.length > 1 && origPOS.exec( selector ) ) {
bgneal@312 3618 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
bgneal@312 3619 set = posProcess( parts[0] + parts[1], context );
bgneal@312 3620 } else {
bgneal@312 3621 set = Expr.relative[ parts[0] ] ?
bgneal@312 3622 [ context ] :
bgneal@312 3623 Sizzle( parts.shift(), context );
bgneal@312 3624
bgneal@312 3625 while ( parts.length ) {
bgneal@312 3626 selector = parts.shift();
bgneal@312 3627
bgneal@312 3628 if ( Expr.relative[ selector ] ) {
bgneal@312 3629 selector += parts.shift();
bgneal@312 3630 }
bgneal@312 3631
bgneal@312 3632 set = posProcess( selector, set );
bgneal@312 3633 }
bgneal@312 3634 }
bgneal@312 3635 } else {
bgneal@312 3636 // Take a shortcut and set the context if the root selector is an ID
bgneal@312 3637 // (but not if it'll be faster if the inner selector is an ID)
bgneal@312 3638 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
bgneal@312 3639 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
bgneal@312 3640 ret = Sizzle.find( parts.shift(), context, contextXML );
bgneal@312 3641 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
bgneal@312 3642 }
bgneal@312 3643
bgneal@312 3644 if ( context ) {
bgneal@312 3645 ret = seed ?
bgneal@312 3646 { expr: parts.pop(), set: makeArray(seed) } :
bgneal@312 3647 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
bgneal@312 3648 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
bgneal@312 3649
bgneal@312 3650 if ( parts.length > 0 ) {
bgneal@312 3651 checkSet = makeArray(set);
bgneal@312 3652 } else {
bgneal@312 3653 prune = false;
bgneal@312 3654 }
bgneal@312 3655
bgneal@312 3656 while ( parts.length ) {
bgneal@312 3657 cur = parts.pop();
bgneal@312 3658 pop = cur;
bgneal@312 3659
bgneal@312 3660 if ( !Expr.relative[ cur ] ) {
bgneal@312 3661 cur = "";
bgneal@312 3662 } else {
bgneal@312 3663 pop = parts.pop();
bgneal@312 3664 }
bgneal@312 3665
bgneal@312 3666 if ( pop == null ) {
bgneal@312 3667 pop = context;
bgneal@312 3668 }
bgneal@312 3669
bgneal@312 3670 Expr.relative[ cur ]( checkSet, pop, contextXML );
bgneal@312 3671 }
bgneal@312 3672 } else {
bgneal@312 3673 checkSet = parts = [];
bgneal@312 3674 }
bgneal@312 3675 }
bgneal@312 3676
bgneal@312 3677 if ( !checkSet ) {
bgneal@312 3678 checkSet = set;
bgneal@312 3679 }
bgneal@312 3680
bgneal@312 3681 if ( !checkSet ) {
bgneal@312 3682 Sizzle.error( cur || selector );
bgneal@312 3683 }
bgneal@312 3684
bgneal@312 3685 if ( toString.call(checkSet) === "[object Array]" ) {
bgneal@312 3686 if ( !prune ) {
bgneal@312 3687 results.push.apply( results, checkSet );
bgneal@312 3688 } else if ( context && context.nodeType === 1 ) {
bgneal@312 3689 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@312 3690 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
bgneal@312 3691 results.push( set[i] );
bgneal@312 3692 }
bgneal@312 3693 }
bgneal@312 3694 } else {
bgneal@312 3695 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@312 3696 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
bgneal@312 3697 results.push( set[i] );
bgneal@312 3698 }
bgneal@312 3699 }
bgneal@312 3700 }
bgneal@312 3701 } else {
bgneal@312 3702 makeArray( checkSet, results );
bgneal@312 3703 }
bgneal@312 3704
bgneal@312 3705 if ( extra ) {
bgneal@312 3706 Sizzle( extra, origContext, results, seed );
bgneal@312 3707 Sizzle.uniqueSort( results );
bgneal@312 3708 }
bgneal@312 3709
bgneal@312 3710 return results;
bgneal@312 3711 };
bgneal@312 3712
bgneal@312 3713 Sizzle.uniqueSort = function(results){
bgneal@312 3714 if ( sortOrder ) {
bgneal@312 3715 hasDuplicate = baseHasDuplicate;
bgneal@312 3716 results.sort(sortOrder);
bgneal@312 3717
bgneal@312 3718 if ( hasDuplicate ) {
bgneal@312 3719 for ( var i = 1; i < results.length; i++ ) {
bgneal@312 3720 if ( results[i] === results[i-1] ) {
bgneal@312 3721 results.splice(i--, 1);
bgneal@312 3722 }
bgneal@312 3723 }
bgneal@312 3724 }
bgneal@312 3725 }
bgneal@312 3726
bgneal@312 3727 return results;
bgneal@312 3728 };
bgneal@312 3729
bgneal@312 3730 Sizzle.matches = function(expr, set){
bgneal@312 3731 return Sizzle(expr, null, null, set);
bgneal@312 3732 };
bgneal@312 3733
bgneal@312 3734 Sizzle.find = function(expr, context, isXML){
bgneal@312 3735 var set;
bgneal@312 3736
bgneal@312 3737 if ( !expr ) {
bgneal@312 3738 return [];
bgneal@312 3739 }
bgneal@312 3740
bgneal@312 3741 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
bgneal@312 3742 var type = Expr.order[i], match;
bgneal@312 3743
bgneal@312 3744 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
bgneal@312 3745 var left = match[1];
bgneal@312 3746 match.splice(1,1);
bgneal@312 3747
bgneal@312 3748 if ( left.substr( left.length - 1 ) !== "\\" ) {
bgneal@312 3749 match[1] = (match[1] || "").replace(/\\/g, "");
bgneal@312 3750 set = Expr.find[ type ]( match, context, isXML );
bgneal@312 3751 if ( set != null ) {
bgneal@312 3752 expr = expr.replace( Expr.match[ type ], "" );
bgneal@312 3753 break;
bgneal@312 3754 }
bgneal@312 3755 }
bgneal@312 3756 }
bgneal@312 3757 }
bgneal@312 3758
bgneal@312 3759 if ( !set ) {
bgneal@312 3760 set = context.getElementsByTagName("*");
bgneal@312 3761 }
bgneal@312 3762
bgneal@312 3763 return {set: set, expr: expr};
bgneal@312 3764 };
bgneal@312 3765
bgneal@312 3766 Sizzle.filter = function(expr, set, inplace, not){
bgneal@312 3767 var old = expr, result = [], curLoop = set, match, anyFound,
bgneal@312 3768 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
bgneal@312 3769
bgneal@312 3770 while ( expr && set.length ) {
bgneal@312 3771 for ( var type in Expr.filter ) {
bgneal@312 3772 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
bgneal@312 3773 var filter = Expr.filter[ type ], found, item, left = match[1];
bgneal@312 3774 anyFound = false;
bgneal@312 3775
bgneal@312 3776 match.splice(1,1);
bgneal@312 3777
bgneal@312 3778 if ( left.substr( left.length - 1 ) === "\\" ) {
bgneal@312 3779 continue;
bgneal@312 3780 }
bgneal@312 3781
bgneal@312 3782 if ( curLoop === result ) {
bgneal@312 3783 result = [];
bgneal@312 3784 }
bgneal@312 3785
bgneal@312 3786 if ( Expr.preFilter[ type ] ) {
bgneal@312 3787 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
bgneal@312 3788
bgneal@312 3789 if ( !match ) {
bgneal@312 3790 anyFound = found = true;
bgneal@312 3791 } else if ( match === true ) {
bgneal@312 3792 continue;
bgneal@312 3793 }
bgneal@312 3794 }
bgneal@312 3795
bgneal@312 3796 if ( match ) {
bgneal@312 3797 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
bgneal@312 3798 if ( item ) {
bgneal@312 3799 found = filter( item, match, i, curLoop );
bgneal@312 3800 var pass = not ^ !!found;
bgneal@312 3801
bgneal@312 3802 if ( inplace && found != null ) {
bgneal@312 3803 if ( pass ) {
bgneal@312 3804 anyFound = true;
bgneal@312 3805 } else {
bgneal@312 3806 curLoop[i] = false;
bgneal@312 3807 }
bgneal@312 3808 } else if ( pass ) {
bgneal@312 3809 result.push( item );
bgneal@312 3810 anyFound = true;
bgneal@312 3811 }
bgneal@312 3812 }
bgneal@312 3813 }
bgneal@312 3814 }
bgneal@312 3815
bgneal@312 3816 if ( found !== undefined ) {
bgneal@312 3817 if ( !inplace ) {
bgneal@312 3818 curLoop = result;
bgneal@312 3819 }
bgneal@312 3820
bgneal@312 3821 expr = expr.replace( Expr.match[ type ], "" );
bgneal@312 3822
bgneal@312 3823 if ( !anyFound ) {
bgneal@312 3824 return [];
bgneal@312 3825 }
bgneal@312 3826
bgneal@312 3827 break;
bgneal@312 3828 }
bgneal@312 3829 }
bgneal@312 3830 }
bgneal@312 3831
bgneal@312 3832 // Improper expression
bgneal@312 3833 if ( expr === old ) {
bgneal@312 3834 if ( anyFound == null ) {
bgneal@312 3835 Sizzle.error( expr );
bgneal@312 3836 } else {
bgneal@312 3837 break;
bgneal@312 3838 }
bgneal@312 3839 }
bgneal@312 3840
bgneal@312 3841 old = expr;
bgneal@312 3842 }
bgneal@312 3843
bgneal@312 3844 return curLoop;
bgneal@312 3845 };
bgneal@312 3846
bgneal@312 3847 Sizzle.error = function( msg ) {
bgneal@312 3848 throw "Syntax error, unrecognized expression: " + msg;
bgneal@312 3849 };
bgneal@312 3850
bgneal@312 3851 var Expr = Sizzle.selectors = {
bgneal@312 3852 order: [ "ID", "NAME", "TAG" ],
bgneal@312 3853 match: {
bgneal@312 3854 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@312 3855 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@312 3856 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
bgneal@312 3857 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
bgneal@312 3858 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
bgneal@312 3859 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
bgneal@312 3860 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
bgneal@312 3861 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
bgneal@312 3862 },
bgneal@312 3863 leftMatch: {},
bgneal@312 3864 attrMap: {
bgneal@312 3865 "class": "className",
bgneal@312 3866 "for": "htmlFor"
bgneal@312 3867 },
bgneal@312 3868 attrHandle: {
bgneal@312 3869 href: function(elem){
bgneal@312 3870 return elem.getAttribute("href");
bgneal@312 3871 }
bgneal@312 3872 },
bgneal@312 3873 relative: {
bgneal@312 3874 "+": function(checkSet, part){
bgneal@312 3875 var isPartStr = typeof part === "string",
bgneal@312 3876 isTag = isPartStr && !/\W/.test(part),
bgneal@312 3877 isPartStrNotTag = isPartStr && !isTag;
bgneal@312 3878
bgneal@312 3879 if ( isTag ) {
bgneal@312 3880 part = part.toLowerCase();
bgneal@312 3881 }
bgneal@312 3882
bgneal@312 3883 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
bgneal@312 3884 if ( (elem = checkSet[i]) ) {
bgneal@312 3885 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
bgneal@312 3886
bgneal@312 3887 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
bgneal@312 3888 elem || false :
bgneal@312 3889 elem === part;
bgneal@312 3890 }
bgneal@312 3891 }
bgneal@312 3892
bgneal@312 3893 if ( isPartStrNotTag ) {
bgneal@312 3894 Sizzle.filter( part, checkSet, true );
bgneal@312 3895 }
bgneal@312 3896 },
bgneal@312 3897 ">": function(checkSet, part){
bgneal@312 3898 var isPartStr = typeof part === "string",
bgneal@312 3899 elem, i = 0, l = checkSet.length;
bgneal@312 3900
bgneal@312 3901 if ( isPartStr && !/\W/.test(part) ) {
bgneal@312 3902 part = part.toLowerCase();
bgneal@312 3903
bgneal@312 3904 for ( ; i < l; i++ ) {
bgneal@312 3905 elem = checkSet[i];
bgneal@312 3906 if ( elem ) {
bgneal@312 3907 var parent = elem.parentNode;
bgneal@312 3908 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
bgneal@312 3909 }
bgneal@312 3910 }
bgneal@312 3911 } else {
bgneal@312 3912 for ( ; i < l; i++ ) {
bgneal@312 3913 elem = checkSet[i];
bgneal@312 3914 if ( elem ) {
bgneal@312 3915 checkSet[i] = isPartStr ?
bgneal@312 3916 elem.parentNode :
bgneal@312 3917 elem.parentNode === part;
bgneal@312 3918 }
bgneal@312 3919 }
bgneal@312 3920
bgneal@312 3921 if ( isPartStr ) {
bgneal@312 3922 Sizzle.filter( part, checkSet, true );
bgneal@312 3923 }
bgneal@312 3924 }
bgneal@312 3925 },
bgneal@312 3926 "": function(checkSet, part, isXML){
bgneal@312 3927 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@312 3928
bgneal@312 3929 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@312 3930 part = part.toLowerCase();
bgneal@312 3931 nodeCheck = part;
bgneal@312 3932 checkFn = dirNodeCheck;
bgneal@312 3933 }
bgneal@312 3934
bgneal@312 3935 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
bgneal@312 3936 },
bgneal@312 3937 "~": function(checkSet, part, isXML){
bgneal@312 3938 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@312 3939
bgneal@312 3940 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@312 3941 part = part.toLowerCase();
bgneal@312 3942 nodeCheck = part;
bgneal@312 3943 checkFn = dirNodeCheck;
bgneal@312 3944 }
bgneal@312 3945
bgneal@312 3946 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
bgneal@312 3947 }
bgneal@312 3948 },
bgneal@312 3949 find: {
bgneal@312 3950 ID: function(match, context, isXML){
bgneal@312 3951 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@312 3952 var m = context.getElementById(match[1]);
bgneal@312 3953 return m ? [m] : [];
bgneal@312 3954 }
bgneal@312 3955 },
bgneal@312 3956 NAME: function(match, context){
bgneal@312 3957 if ( typeof context.getElementsByName !== "undefined" ) {
bgneal@312 3958 var ret = [], results = context.getElementsByName(match[1]);
bgneal@312 3959
bgneal@312 3960 for ( var i = 0, l = results.length; i < l; i++ ) {
bgneal@312 3961 if ( results[i].getAttribute("name") === match[1] ) {
bgneal@312 3962 ret.push( results[i] );
bgneal@312 3963 }
bgneal@312 3964 }
bgneal@312 3965
bgneal@312 3966 return ret.length === 0 ? null : ret;
bgneal@312 3967 }
bgneal@312 3968 },
bgneal@312 3969 TAG: function(match, context){
bgneal@312 3970 return context.getElementsByTagName(match[1]);
bgneal@312 3971 }
bgneal@312 3972 },
bgneal@312 3973 preFilter: {
bgneal@312 3974 CLASS: function(match, curLoop, inplace, result, not, isXML){
bgneal@312 3975 match = " " + match[1].replace(/\\/g, "") + " ";
bgneal@312 3976
bgneal@312 3977 if ( isXML ) {
bgneal@312 3978 return match;
bgneal@312 3979 }
bgneal@312 3980
bgneal@312 3981 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
bgneal@312 3982 if ( elem ) {
bgneal@312 3983 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
bgneal@312 3984 if ( !inplace ) {
bgneal@312 3985 result.push( elem );
bgneal@312 3986 }
bgneal@312 3987 } else if ( inplace ) {
bgneal@312 3988 curLoop[i] = false;
bgneal@312 3989 }
bgneal@312 3990 }
bgneal@312 3991 }
bgneal@312 3992
bgneal@312 3993 return false;
bgneal@312 3994 },
bgneal@312 3995 ID: function(match){
bgneal@312 3996 return match[1].replace(/\\/g, "");
bgneal@312 3997 },
bgneal@312 3998 TAG: function(match, curLoop){
bgneal@312 3999 return match[1].toLowerCase();
bgneal@312 4000 },
bgneal@312 4001 CHILD: function(match){
bgneal@312 4002 if ( match[1] === "nth" ) {
bgneal@312 4003 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
bgneal@312 4004 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
bgneal@312 4005 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
bgneal@312 4006 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
bgneal@312 4007
bgneal@312 4008 // calculate the numbers (first)n+(last) including if they are negative
bgneal@312 4009 match[2] = (test[1] + (test[2] || 1)) - 0;
bgneal@312 4010 match[3] = test[3] - 0;
bgneal@312 4011 }
bgneal@312 4012
bgneal@312 4013 // TODO: Move to normal caching system
bgneal@312 4014 match[0] = done++;
bgneal@312 4015
bgneal@312 4016 return match;
bgneal@312 4017 },
bgneal@312 4018 ATTR: function(match, curLoop, inplace, result, not, isXML){
bgneal@312 4019 var name = match[1].replace(/\\/g, "");
bgneal@312 4020
bgneal@312 4021 if ( !isXML && Expr.attrMap[name] ) {
bgneal@312 4022 match[1] = Expr.attrMap[name];
bgneal@312 4023 }
bgneal@312 4024
bgneal@312 4025 if ( match[2] === "~=" ) {
bgneal@312 4026 match[4] = " " + match[4] + " ";
bgneal@312 4027 }
bgneal@312 4028
bgneal@312 4029 return match;
bgneal@312 4030 },
bgneal@312 4031 PSEUDO: function(match, curLoop, inplace, result, not){
bgneal@312 4032 if ( match[1] === "not" ) {
bgneal@312 4033 // If we're dealing with a complex expression, or a simple one
bgneal@312 4034 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
bgneal@312 4035 match[3] = Sizzle(match[3], null, null, curLoop);
bgneal@312 4036 } else {
bgneal@312 4037 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
bgneal@312 4038 if ( !inplace ) {
bgneal@312 4039 result.push.apply( result, ret );
bgneal@312 4040 }
bgneal@312 4041 return false;
bgneal@312 4042 }
bgneal@312 4043 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
bgneal@312 4044 return true;
bgneal@312 4045 }
bgneal@312 4046
bgneal@312 4047 return match;
bgneal@312 4048 },
bgneal@312 4049 POS: function(match){
bgneal@312 4050 match.unshift( true );
bgneal@312 4051 return match;
bgneal@312 4052 }
bgneal@312 4053 },
bgneal@312 4054 filters: {
bgneal@312 4055 enabled: function(elem){
bgneal@312 4056 return elem.disabled === false && elem.type !== "hidden";
bgneal@312 4057 },
bgneal@312 4058 disabled: function(elem){
bgneal@312 4059 return elem.disabled === true;
bgneal@312 4060 },
bgneal@312 4061 checked: function(elem){
bgneal@312 4062 return elem.checked === true;
bgneal@312 4063 },
bgneal@312 4064 selected: function(elem){
bgneal@312 4065 // Accessing this property makes selected-by-default
bgneal@312 4066 // options in Safari work properly
bgneal@312 4067 elem.parentNode.selectedIndex;
bgneal@312 4068 return elem.selected === true;
bgneal@312 4069 },
bgneal@312 4070 parent: function(elem){
bgneal@312 4071 return !!elem.firstChild;
bgneal@312 4072 },
bgneal@312 4073 empty: function(elem){
bgneal@312 4074 return !elem.firstChild;
bgneal@312 4075 },
bgneal@312 4076 has: function(elem, i, match){
bgneal@312 4077 return !!Sizzle( match[3], elem ).length;
bgneal@312 4078 },
bgneal@312 4079 header: function(elem){
bgneal@312 4080 return (/h\d/i).test( elem.nodeName );
bgneal@312 4081 },
bgneal@312 4082 text: function(elem){
bgneal@312 4083 return "text" === elem.type;
bgneal@312 4084 },
bgneal@312 4085 radio: function(elem){
bgneal@312 4086 return "radio" === elem.type;
bgneal@312 4087 },
bgneal@312 4088 checkbox: function(elem){
bgneal@312 4089 return "checkbox" === elem.type;
bgneal@312 4090 },
bgneal@312 4091 file: function(elem){
bgneal@312 4092 return "file" === elem.type;
bgneal@312 4093 },
bgneal@312 4094 password: function(elem){
bgneal@312 4095 return "password" === elem.type;
bgneal@312 4096 },
bgneal@312 4097 submit: function(elem){
bgneal@312 4098 return "submit" === elem.type;
bgneal@312 4099 },
bgneal@312 4100 image: function(elem){
bgneal@312 4101 return "image" === elem.type;
bgneal@312 4102 },
bgneal@312 4103 reset: function(elem){
bgneal@312 4104 return "reset" === elem.type;
bgneal@312 4105 },
bgneal@312 4106 button: function(elem){
bgneal@312 4107 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
bgneal@312 4108 },
bgneal@312 4109 input: function(elem){
bgneal@312 4110 return (/input|select|textarea|button/i).test(elem.nodeName);
bgneal@312 4111 }
bgneal@312 4112 },
bgneal@312 4113 setFilters: {
bgneal@312 4114 first: function(elem, i){
bgneal@312 4115 return i === 0;
bgneal@312 4116 },
bgneal@312 4117 last: function(elem, i, match, array){
bgneal@312 4118 return i === array.length - 1;
bgneal@312 4119 },
bgneal@312 4120 even: function(elem, i){
bgneal@312 4121 return i % 2 === 0;
bgneal@312 4122 },
bgneal@312 4123 odd: function(elem, i){
bgneal@312 4124 return i % 2 === 1;
bgneal@312 4125 },
bgneal@312 4126 lt: function(elem, i, match){
bgneal@312 4127 return i < match[3] - 0;
bgneal@312 4128 },
bgneal@312 4129 gt: function(elem, i, match){
bgneal@312 4130 return i > match[3] - 0;
bgneal@312 4131 },
bgneal@312 4132 nth: function(elem, i, match){
bgneal@312 4133 return match[3] - 0 === i;
bgneal@312 4134 },
bgneal@312 4135 eq: function(elem, i, match){
bgneal@312 4136 return match[3] - 0 === i;
bgneal@312 4137 }
bgneal@312 4138 },
bgneal@312 4139 filter: {
bgneal@312 4140 PSEUDO: function(elem, match, i, array){
bgneal@312 4141 var name = match[1], filter = Expr.filters[ name ];
bgneal@312 4142
bgneal@312 4143 if ( filter ) {
bgneal@312 4144 return filter( elem, i, match, array );
bgneal@312 4145 } else if ( name === "contains" ) {
bgneal@312 4146 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
bgneal@312 4147 } else if ( name === "not" ) {
bgneal@312 4148 var not = match[3];
bgneal@312 4149
bgneal@312 4150 for ( var j = 0, l = not.length; j < l; j++ ) {
bgneal@312 4151 if ( not[j] === elem ) {
bgneal@312 4152 return false;
bgneal@312 4153 }
bgneal@312 4154 }
bgneal@312 4155
bgneal@312 4156 return true;
bgneal@312 4157 } else {
bgneal@312 4158 Sizzle.error( "Syntax error, unrecognized expression: " + name );
bgneal@312 4159 }
bgneal@312 4160 },
bgneal@312 4161 CHILD: function(elem, match){
bgneal@312 4162 var type = match[1], node = elem;
bgneal@312 4163 switch (type) {
bgneal@312 4164 case 'only':
bgneal@312 4165 case 'first':
bgneal@312 4166 while ( (node = node.previousSibling) ) {
bgneal@312 4167 if ( node.nodeType === 1 ) {
bgneal@312 4168 return false;
bgneal@312 4169 }
bgneal@312 4170 }
bgneal@312 4171 if ( type === "first" ) {
bgneal@312 4172 return true;
bgneal@312 4173 }
bgneal@312 4174 node = elem;
bgneal@312 4175 case 'last':
bgneal@312 4176 while ( (node = node.nextSibling) ) {
bgneal@312 4177 if ( node.nodeType === 1 ) {
bgneal@312 4178 return false;
bgneal@312 4179 }
bgneal@312 4180 }
bgneal@312 4181 return true;
bgneal@312 4182 case 'nth':
bgneal@312 4183 var first = match[2], last = match[3];
bgneal@312 4184
bgneal@312 4185 if ( first === 1 && last === 0 ) {
bgneal@312 4186 return true;
bgneal@312 4187 }
bgneal@312 4188
bgneal@312 4189 var doneName = match[0],
bgneal@312 4190 parent = elem.parentNode;
bgneal@312 4191
bgneal@312 4192 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
bgneal@312 4193 var count = 0;
bgneal@312 4194 for ( node = parent.firstChild; node; node = node.nextSibling ) {
bgneal@312 4195 if ( node.nodeType === 1 ) {
bgneal@312 4196 node.nodeIndex = ++count;
bgneal@312 4197 }
bgneal@312 4198 }
bgneal@312 4199 parent.sizcache = doneName;
bgneal@312 4200 }
bgneal@312 4201
bgneal@312 4202 var diff = elem.nodeIndex - last;
bgneal@312 4203 if ( first === 0 ) {
bgneal@312 4204 return diff === 0;
bgneal@312 4205 } else {
bgneal@312 4206 return ( diff % first === 0 && diff / first >= 0 );
bgneal@312 4207 }
bgneal@312 4208 }
bgneal@312 4209 },
bgneal@312 4210 ID: function(elem, match){
bgneal@312 4211 return elem.nodeType === 1 && elem.getAttribute("id") === match;
bgneal@312 4212 },
bgneal@312 4213 TAG: function(elem, match){
bgneal@312 4214 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
bgneal@312 4215 },
bgneal@312 4216 CLASS: function(elem, match){
bgneal@312 4217 return (" " + (elem.className || elem.getAttribute("class")) + " ")
bgneal@312 4218 .indexOf( match ) > -1;
bgneal@312 4219 },
bgneal@312 4220 ATTR: function(elem, match){
bgneal@312 4221 var name = match[1],
bgneal@312 4222 result = Expr.attrHandle[ name ] ?
bgneal@312 4223 Expr.attrHandle[ name ]( elem ) :
bgneal@312 4224 elem[ name ] != null ?
bgneal@312 4225 elem[ name ] :
bgneal@312 4226 elem.getAttribute( name ),
bgneal@312 4227 value = result + "",
bgneal@312 4228 type = match[2],
bgneal@312 4229 check = match[4];
bgneal@312 4230
bgneal@312 4231 return result == null ?
bgneal@312 4232 type === "!=" :
bgneal@312 4233 type === "=" ?
bgneal@312 4234 value === check :
bgneal@312 4235 type === "*=" ?
bgneal@312 4236 value.indexOf(check) >= 0 :
bgneal@312 4237 type === "~=" ?
bgneal@312 4238 (" " + value + " ").indexOf(check) >= 0 :
bgneal@312 4239 !check ?
bgneal@312 4240 value && result !== false :
bgneal@312 4241 type === "!=" ?
bgneal@312 4242 value !== check :
bgneal@312 4243 type === "^=" ?
bgneal@312 4244 value.indexOf(check) === 0 :
bgneal@312 4245 type === "$=" ?
bgneal@312 4246 value.substr(value.length - check.length) === check :
bgneal@312 4247 type === "|=" ?
bgneal@312 4248 value === check || value.substr(0, check.length + 1) === check + "-" :
bgneal@312 4249 false;
bgneal@312 4250 },
bgneal@312 4251 POS: function(elem, match, i, array){
bgneal@312 4252 var name = match[2], filter = Expr.setFilters[ name ];
bgneal@312 4253
bgneal@312 4254 if ( filter ) {
bgneal@312 4255 return filter( elem, i, match, array );
bgneal@312 4256 }
bgneal@312 4257 }
bgneal@312 4258 }
bgneal@312 4259 };
bgneal@312 4260
bgneal@312 4261 var origPOS = Expr.match.POS,
bgneal@312 4262 fescape = function(all, num){
bgneal@312 4263 return "\\" + (num - 0 + 1);
bgneal@312 4264 };
bgneal@312 4265
bgneal@312 4266 for ( var type in Expr.match ) {
bgneal@312 4267 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
bgneal@312 4268 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
bgneal@312 4269 }
bgneal@312 4270
bgneal@312 4271 var makeArray = function(array, results) {
bgneal@312 4272 array = Array.prototype.slice.call( array, 0 );
bgneal@312 4273
bgneal@312 4274 if ( results ) {
bgneal@312 4275 results.push.apply( results, array );
bgneal@312 4276 return results;
bgneal@312 4277 }
bgneal@312 4278
bgneal@312 4279 return array;
bgneal@312 4280 };
bgneal@312 4281
bgneal@312 4282 // Perform a simple check to determine if the browser is capable of
bgneal@312 4283 // converting a NodeList to an array using builtin methods.
bgneal@312 4284 // Also verifies that the returned array holds DOM nodes
bgneal@312 4285 // (which is not the case in the Blackberry browser)
bgneal@312 4286 try {
bgneal@312 4287 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
bgneal@312 4288
bgneal@312 4289 // Provide a fallback method if it does not work
bgneal@312 4290 } catch(e){
bgneal@312 4291 makeArray = function(array, results) {
bgneal@312 4292 var ret = results || [], i = 0;
bgneal@312 4293
bgneal@312 4294 if ( toString.call(array) === "[object Array]" ) {
bgneal@312 4295 Array.prototype.push.apply( ret, array );
bgneal@312 4296 } else {
bgneal@312 4297 if ( typeof array.length === "number" ) {
bgneal@312 4298 for ( var l = array.length; i < l; i++ ) {
bgneal@312 4299 ret.push( array[i] );
bgneal@312 4300 }
bgneal@312 4301 } else {
bgneal@312 4302 for ( ; array[i]; i++ ) {
bgneal@312 4303 ret.push( array[i] );
bgneal@312 4304 }
bgneal@312 4305 }
bgneal@312 4306 }
bgneal@312 4307
bgneal@312 4308 return ret;
bgneal@312 4309 };
bgneal@312 4310 }
bgneal@312 4311
bgneal@312 4312 var sortOrder;
bgneal@312 4313
bgneal@312 4314 if ( document.documentElement.compareDocumentPosition ) {
bgneal@312 4315 sortOrder = function( a, b ) {
bgneal@312 4316 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
bgneal@312 4317 if ( a == b ) {
bgneal@312 4318 hasDuplicate = true;
bgneal@312 4319 }
bgneal@312 4320 return a.compareDocumentPosition ? -1 : 1;
bgneal@312 4321 }
bgneal@312 4322
bgneal@312 4323 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
bgneal@312 4324 if ( ret === 0 ) {
bgneal@312 4325 hasDuplicate = true;
bgneal@312 4326 }
bgneal@312 4327 return ret;
bgneal@312 4328 };
bgneal@312 4329 } else if ( "sourceIndex" in document.documentElement ) {
bgneal@312 4330 sortOrder = function( a, b ) {
bgneal@312 4331 if ( !a.sourceIndex || !b.sourceIndex ) {
bgneal@312 4332 if ( a == b ) {
bgneal@312 4333 hasDuplicate = true;
bgneal@312 4334 }
bgneal@312 4335 return a.sourceIndex ? -1 : 1;
bgneal@312 4336 }
bgneal@312 4337
bgneal@312 4338 var ret = a.sourceIndex - b.sourceIndex;
bgneal@312 4339 if ( ret === 0 ) {
bgneal@312 4340 hasDuplicate = true;
bgneal@312 4341 }
bgneal@312 4342 return ret;
bgneal@312 4343 };
bgneal@312 4344 } else if ( document.createRange ) {
bgneal@312 4345 sortOrder = function( a, b ) {
bgneal@312 4346 if ( !a.ownerDocument || !b.ownerDocument ) {
bgneal@312 4347 if ( a == b ) {
bgneal@312 4348 hasDuplicate = true;
bgneal@312 4349 }
bgneal@312 4350 return a.ownerDocument ? -1 : 1;
bgneal@312 4351 }
bgneal@312 4352
bgneal@312 4353 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
bgneal@312 4354 aRange.setStart(a, 0);
bgneal@312 4355 aRange.setEnd(a, 0);
bgneal@312 4356 bRange.setStart(b, 0);
bgneal@312 4357 bRange.setEnd(b, 0);
bgneal@312 4358 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
bgneal@312 4359 if ( ret === 0 ) {
bgneal@312 4360 hasDuplicate = true;
bgneal@312 4361 }
bgneal@312 4362 return ret;
bgneal@312 4363 };
bgneal@312 4364 }
bgneal@312 4365
bgneal@312 4366 // Utility function for retreiving the text value of an array of DOM nodes
bgneal@312 4367 Sizzle.getText = function( elems ) {
bgneal@312 4368 var ret = "", elem;
bgneal@312 4369
bgneal@312 4370 for ( var i = 0; elems[i]; i++ ) {
bgneal@312 4371 elem = elems[i];
bgneal@312 4372
bgneal@312 4373 // Get the text from text nodes and CDATA nodes
bgneal@312 4374 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
bgneal@312 4375 ret += elem.nodeValue;
bgneal@312 4376
bgneal@312 4377 // Traverse everything else, except comment nodes
bgneal@312 4378 } else if ( elem.nodeType !== 8 ) {
bgneal@312 4379 ret += Sizzle.getText( elem.childNodes );
bgneal@312 4380 }
bgneal@312 4381 }
bgneal@312 4382
bgneal@312 4383 return ret;
bgneal@312 4384 };
bgneal@312 4385
bgneal@312 4386 // Check to see if the browser returns elements by name when
bgneal@312 4387 // querying by getElementById (and provide a workaround)
bgneal@312 4388 (function(){
bgneal@312 4389 // We're going to inject a fake input element with a specified name
bgneal@312 4390 var form = document.createElement("div"),
bgneal@312 4391 id = "script" + (new Date()).getTime();
bgneal@312 4392 form.innerHTML = "<a name='" + id + "'/>";
bgneal@312 4393
bgneal@312 4394 // Inject it into the root element, check its status, and remove it quickly
bgneal@312 4395 var root = document.documentElement;
bgneal@312 4396 root.insertBefore( form, root.firstChild );
bgneal@312 4397
bgneal@312 4398 // The workaround has to do additional checks after a getElementById
bgneal@312 4399 // Which slows things down for other browsers (hence the branching)
bgneal@312 4400 if ( document.getElementById( id ) ) {
bgneal@312 4401 Expr.find.ID = function(match, context, isXML){
bgneal@312 4402 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@312 4403 var m = context.getElementById(match[1]);
bgneal@312 4404 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
bgneal@312 4405 }
bgneal@312 4406 };
bgneal@312 4407
bgneal@312 4408 Expr.filter.ID = function(elem, match){
bgneal@312 4409 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
bgneal@312 4410 return elem.nodeType === 1 && node && node.nodeValue === match;
bgneal@312 4411 };
bgneal@312 4412 }
bgneal@312 4413
bgneal@312 4414 root.removeChild( form );
bgneal@312 4415 root = form = null; // release memory in IE
bgneal@312 4416 })();
bgneal@312 4417
bgneal@312 4418 (function(){
bgneal@312 4419 // Check to see if the browser returns only elements
bgneal@312 4420 // when doing getElementsByTagName("*")
bgneal@312 4421
bgneal@312 4422 // Create a fake element
bgneal@312 4423 var div = document.createElement("div");
bgneal@312 4424 div.appendChild( document.createComment("") );
bgneal@312 4425
bgneal@312 4426 // Make sure no comments are found
bgneal@312 4427 if ( div.getElementsByTagName("*").length > 0 ) {
bgneal@312 4428 Expr.find.TAG = function(match, context){
bgneal@312 4429 var results = context.getElementsByTagName(match[1]);
bgneal@312 4430
bgneal@312 4431 // Filter out possible comments
bgneal@312 4432 if ( match[1] === "*" ) {
bgneal@312 4433 var tmp = [];
bgneal@312 4434
bgneal@312 4435 for ( var i = 0; results[i]; i++ ) {
bgneal@312 4436 if ( results[i].nodeType === 1 ) {
bgneal@312 4437 tmp.push( results[i] );
bgneal@312 4438 }
bgneal@312 4439 }
bgneal@312 4440
bgneal@312 4441 results = tmp;
bgneal@312 4442 }
bgneal@312 4443
bgneal@312 4444 return results;
bgneal@312 4445 };
bgneal@312 4446 }
bgneal@312 4447
bgneal@312 4448 // Check to see if an attribute returns normalized href attributes
bgneal@312 4449 div.innerHTML = "<a href='#'></a>";
bgneal@312 4450 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
bgneal@312 4451 div.firstChild.getAttribute("href") !== "#" ) {
bgneal@312 4452 Expr.attrHandle.href = function(elem){
bgneal@312 4453 return elem.getAttribute("href", 2);
bgneal@312 4454 };
bgneal@312 4455 }
bgneal@312 4456
bgneal@312 4457 div = null; // release memory in IE
bgneal@312 4458 })();
bgneal@312 4459
bgneal@312 4460 if ( document.querySelectorAll ) {
bgneal@312 4461 (function(){
bgneal@312 4462 var oldSizzle = Sizzle, div = document.createElement("div");
bgneal@312 4463 div.innerHTML = "<p class='TEST'></p>";
bgneal@312 4464
bgneal@312 4465 // Safari can't handle uppercase or unicode characters when
bgneal@312 4466 // in quirks mode.
bgneal@312 4467 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
bgneal@312 4468 return;
bgneal@312 4469 }
bgneal@312 4470
bgneal@312 4471 Sizzle = function(query, context, extra, seed){
bgneal@312 4472 context = context || document;
bgneal@312 4473
bgneal@312 4474 // Only use querySelectorAll on non-XML documents
bgneal@312 4475 // (ID selectors don't work in non-HTML documents)
bgneal@312 4476 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
bgneal@312 4477 try {
bgneal@312 4478 return makeArray( context.querySelectorAll(query), extra );
bgneal@312 4479 } catch(e){}
bgneal@312 4480 }
bgneal@312 4481
bgneal@312 4482 return oldSizzle(query, context, extra, seed);
bgneal@312 4483 };
bgneal@312 4484
bgneal@312 4485 for ( var prop in oldSizzle ) {
bgneal@312 4486 Sizzle[ prop ] = oldSizzle[ prop ];
bgneal@312 4487 }
bgneal@312 4488
bgneal@312 4489 div = null; // release memory in IE
bgneal@312 4490 })();
bgneal@312 4491 }
bgneal@312 4492
bgneal@312 4493 (function(){
bgneal@312 4494 var div = document.createElement("div");
bgneal@312 4495
bgneal@312 4496 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
bgneal@312 4497
bgneal@312 4498 // Opera can't find a second classname (in 9.6)
bgneal@312 4499 // Also, make sure that getElementsByClassName actually exists
bgneal@312 4500 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
bgneal@312 4501 return;
bgneal@312 4502 }
bgneal@312 4503
bgneal@312 4504 // Safari caches class attributes, doesn't catch changes (in 3.2)
bgneal@312 4505 div.lastChild.className = "e";
bgneal@312 4506
bgneal@312 4507 if ( div.getElementsByClassName("e").length === 1 ) {
bgneal@312 4508 return;
bgneal@312 4509 }
bgneal@312 4510
bgneal@312 4511 Expr.order.splice(1, 0, "CLASS");
bgneal@312 4512 Expr.find.CLASS = function(match, context, isXML) {
bgneal@312 4513 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
bgneal@312 4514 return context.getElementsByClassName(match[1]);
bgneal@312 4515 }
bgneal@312 4516 };
bgneal@312 4517
bgneal@312 4518 div = null; // release memory in IE
bgneal@312 4519 })();
bgneal@312 4520
bgneal@312 4521 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@312 4522 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@312 4523 var elem = checkSet[i];
bgneal@312 4524 if ( elem ) {
bgneal@312 4525 elem = elem[dir];
bgneal@312 4526 var match = false;
bgneal@312 4527
bgneal@312 4528 while ( elem ) {
bgneal@312 4529 if ( elem.sizcache === doneName ) {
bgneal@312 4530 match = checkSet[elem.sizset];
bgneal@312 4531 break;
bgneal@312 4532 }
bgneal@312 4533
bgneal@312 4534 if ( elem.nodeType === 1 && !isXML ){
bgneal@312 4535 elem.sizcache = doneName;
bgneal@312 4536 elem.sizset = i;
bgneal@312 4537 }
bgneal@312 4538
bgneal@312 4539 if ( elem.nodeName.toLowerCase() === cur ) {
bgneal@312 4540 match = elem;
bgneal@312 4541 break;
bgneal@312 4542 }
bgneal@312 4543
bgneal@312 4544 elem = elem[dir];
bgneal@312 4545 }
bgneal@312 4546
bgneal@312 4547 checkSet[i] = match;
bgneal@312 4548 }
bgneal@312 4549 }
bgneal@312 4550 }
bgneal@312 4551
bgneal@312 4552 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@312 4553 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@312 4554 var elem = checkSet[i];
bgneal@312 4555 if ( elem ) {
bgneal@312 4556 elem = elem[dir];
bgneal@312 4557 var match = false;
bgneal@312 4558
bgneal@312 4559 while ( elem ) {
bgneal@312 4560 if ( elem.sizcache === doneName ) {
bgneal@312 4561 match = checkSet[elem.sizset];
bgneal@312 4562 break;
bgneal@312 4563 }
bgneal@312 4564
bgneal@312 4565 if ( elem.nodeType === 1 ) {
bgneal@312 4566 if ( !isXML ) {
bgneal@312 4567 elem.sizcache = doneName;
bgneal@312 4568 elem.sizset = i;
bgneal@312 4569 }
bgneal@312 4570 if ( typeof cur !== "string" ) {
bgneal@312 4571 if ( elem === cur ) {
bgneal@312 4572 match = true;
bgneal@312 4573 break;
bgneal@312 4574 }
bgneal@312 4575
bgneal@312 4576 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
bgneal@312 4577 match = elem;
bgneal@312 4578 break;
bgneal@312 4579 }
bgneal@312 4580 }
bgneal@312 4581
bgneal@312 4582 elem = elem[dir];
bgneal@312 4583 }
bgneal@312 4584
bgneal@312 4585 checkSet[i] = match;
bgneal@312 4586 }
bgneal@312 4587 }
bgneal@312 4588 }
bgneal@312 4589
bgneal@312 4590 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
bgneal@312 4591 return !!(a.compareDocumentPosition(b) & 16);
bgneal@312 4592 } : function(a, b){
bgneal@312 4593 return a !== b && (a.contains ? a.contains(b) : true);
bgneal@312 4594 };
bgneal@312 4595
bgneal@312 4596 Sizzle.isXML = function(elem){
bgneal@312 4597 // documentElement is verified for cases where it doesn't yet exist
bgneal@312 4598 // (such as loading iframes in IE - #4833)
bgneal@312 4599 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
bgneal@312 4600 return documentElement ? documentElement.nodeName !== "HTML" : false;
bgneal@312 4601 };
bgneal@312 4602
bgneal@312 4603 var posProcess = function(selector, context){
bgneal@312 4604 var tmpSet = [], later = "", match,
bgneal@312 4605 root = context.nodeType ? [context] : context;
bgneal@312 4606
bgneal@312 4607 // Position selectors must be done after the filter
bgneal@312 4608 // And so must :not(positional) so we move all PSEUDOs to the end
bgneal@312 4609 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
bgneal@312 4610 later += match[0];
bgneal@312 4611 selector = selector.replace( Expr.match.PSEUDO, "" );
bgneal@312 4612 }
bgneal@312 4613
bgneal@312 4614 selector = Expr.relative[selector] ? selector + "*" : selector;
bgneal@312 4615
bgneal@312 4616 for ( var i = 0, l = root.length; i < l; i++ ) {
bgneal@312 4617 Sizzle( selector, root[i], tmpSet );
bgneal@312 4618 }
bgneal@312 4619
bgneal@312 4620 return Sizzle.filter( later, tmpSet );
bgneal@312 4621 };
bgneal@312 4622
bgneal@312 4623 // EXPOSE
bgneal@312 4624
bgneal@312 4625 window.tinymce.dom.Sizzle = Sizzle;
bgneal@312 4626
bgneal@312 4627 })();
bgneal@312 4628
bgneal@312 4629
bgneal@312 4630 (function(tinymce) {
bgneal@312 4631 // Shorten names
bgneal@312 4632 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
bgneal@312 4633
bgneal@312 4634 tinymce.create('tinymce.dom.EventUtils', {
bgneal@312 4635 EventUtils : function() {
bgneal@312 4636 this.inits = [];
bgneal@312 4637 this.events = [];
bgneal@312 4638 },
bgneal@312 4639
bgneal@312 4640 add : function(o, n, f, s) {
bgneal@312 4641 var cb, t = this, el = t.events, r;
bgneal@312 4642
bgneal@312 4643 if (n instanceof Array) {
bgneal@312 4644 r = [];
bgneal@312 4645
bgneal@312 4646 each(n, function(n) {
bgneal@312 4647 r.push(t.add(o, n, f, s));
bgneal@312 4648 });
bgneal@312 4649
bgneal@312 4650 return r;
bgneal@312 4651 }
bgneal@312 4652
bgneal@312 4653 // Handle array
bgneal@312 4654 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@312 4655 r = [];
bgneal@312 4656
bgneal@312 4657 each(o, function(o) {
bgneal@312 4658 o = DOM.get(o);
bgneal@312 4659 r.push(t.add(o, n, f, s));
bgneal@312 4660 });
bgneal@312 4661
bgneal@312 4662 return r;
bgneal@312 4663 }
bgneal@312 4664
bgneal@312 4665 o = DOM.get(o);
bgneal@312 4666
bgneal@312 4667 if (!o)
bgneal@312 4668 return;
bgneal@312 4669
bgneal@312 4670 // Setup event callback
bgneal@312 4671 cb = function(e) {
bgneal@312 4672 // Is all events disabled
bgneal@312 4673 if (t.disabled)
bgneal@312 4674 return;
bgneal@312 4675
bgneal@312 4676 e = e || window.event;
bgneal@312 4677
bgneal@312 4678 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
bgneal@312 4679 if (e && isIE) {
bgneal@312 4680 if (!e.target)
bgneal@312 4681 e.target = e.srcElement;
bgneal@312 4682
bgneal@312 4683 // Patch in preventDefault, stopPropagation methods for W3C compatibility
bgneal@312 4684 tinymce.extend(e, t._stoppers);
bgneal@312 4685 }
bgneal@312 4686
bgneal@312 4687 if (!s)
bgneal@312 4688 return f(e);
bgneal@312 4689
bgneal@312 4690 return f.call(s, e);
bgneal@312 4691 };
bgneal@312 4692
bgneal@312 4693 if (n == 'unload') {
bgneal@312 4694 tinymce.unloads.unshift({func : cb});
bgneal@312 4695 return cb;
bgneal@312 4696 }
bgneal@312 4697
bgneal@312 4698 if (n == 'init') {
bgneal@312 4699 if (t.domLoaded)
bgneal@312 4700 cb();
bgneal@312 4701 else
bgneal@312 4702 t.inits.push(cb);
bgneal@312 4703
bgneal@312 4704 return cb;
bgneal@312 4705 }
bgneal@312 4706
bgneal@312 4707 // Store away listener reference
bgneal@312 4708 el.push({
bgneal@312 4709 obj : o,
bgneal@312 4710 name : n,
bgneal@312 4711 func : f,
bgneal@312 4712 cfunc : cb,
bgneal@312 4713 scope : s
bgneal@312 4714 });
bgneal@312 4715
bgneal@312 4716 t._add(o, n, cb);
bgneal@312 4717
bgneal@312 4718 return f;
bgneal@312 4719 },
bgneal@312 4720
bgneal@312 4721 remove : function(o, n, f) {
bgneal@312 4722 var t = this, a = t.events, s = false, r;
bgneal@312 4723
bgneal@312 4724 // Handle array
bgneal@312 4725 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@312 4726 r = [];
bgneal@312 4727
bgneal@312 4728 each(o, function(o) {
bgneal@312 4729 o = DOM.get(o);
bgneal@312 4730 r.push(t.remove(o, n, f));
bgneal@312 4731 });
bgneal@312 4732
bgneal@312 4733 return r;
bgneal@312 4734 }
bgneal@312 4735
bgneal@312 4736 o = DOM.get(o);
bgneal@312 4737
bgneal@312 4738 each(a, function(e, i) {
bgneal@312 4739 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
bgneal@312 4740 a.splice(i, 1);
bgneal@312 4741 t._remove(o, n, e.cfunc);
bgneal@312 4742 s = true;
bgneal@312 4743 return false;
bgneal@312 4744 }
bgneal@312 4745 });
bgneal@312 4746
bgneal@312 4747 return s;
bgneal@312 4748 },
bgneal@312 4749
bgneal@312 4750 clear : function(o) {
bgneal@312 4751 var t = this, a = t.events, i, e;
bgneal@312 4752
bgneal@312 4753 if (o) {
bgneal@312 4754 o = DOM.get(o);
bgneal@312 4755
bgneal@312 4756 for (i = a.length - 1; i >= 0; i--) {
bgneal@312 4757 e = a[i];
bgneal@312 4758
bgneal@312 4759 if (e.obj === o) {
bgneal@312 4760 t._remove(e.obj, e.name, e.cfunc);
bgneal@312 4761 e.obj = e.cfunc = null;
bgneal@312 4762 a.splice(i, 1);
bgneal@312 4763 }
bgneal@312 4764 }
bgneal@312 4765 }
bgneal@312 4766 },
bgneal@312 4767
bgneal@312 4768 cancel : function(e) {
bgneal@312 4769 if (!e)
bgneal@312 4770 return false;
bgneal@312 4771
bgneal@312 4772 this.stop(e);
bgneal@312 4773
bgneal@312 4774 return this.prevent(e);
bgneal@312 4775 },
bgneal@312 4776
bgneal@312 4777 stop : function(e) {
bgneal@312 4778 if (e.stopPropagation)
bgneal@312 4779 e.stopPropagation();
bgneal@312 4780 else
bgneal@312 4781 e.cancelBubble = true;
bgneal@312 4782
bgneal@312 4783 return false;
bgneal@312 4784 },
bgneal@312 4785
bgneal@312 4786 prevent : function(e) {
bgneal@312 4787 if (e.preventDefault)
bgneal@312 4788 e.preventDefault();
bgneal@312 4789 else
bgneal@312 4790 e.returnValue = false;
bgneal@312 4791
bgneal@312 4792 return false;
bgneal@312 4793 },
bgneal@312 4794
bgneal@312 4795 destroy : function() {
bgneal@312 4796 var t = this;
bgneal@312 4797
bgneal@312 4798 each(t.events, function(e, i) {
bgneal@312 4799 t._remove(e.obj, e.name, e.cfunc);
bgneal@312 4800 e.obj = e.cfunc = null;
bgneal@312 4801 });
bgneal@312 4802
bgneal@312 4803 t.events = [];
bgneal@312 4804 t = null;
bgneal@312 4805 },
bgneal@312 4806
bgneal@312 4807 _add : function(o, n, f) {
bgneal@312 4808 if (o.attachEvent)
bgneal@312 4809 o.attachEvent('on' + n, f);
bgneal@312 4810 else if (o.addEventListener)
bgneal@312 4811 o.addEventListener(n, f, false);
bgneal@312 4812 else
bgneal@312 4813 o['on' + n] = f;
bgneal@312 4814 },
bgneal@312 4815
bgneal@312 4816 _remove : function(o, n, f) {
bgneal@312 4817 if (o) {
bgneal@312 4818 try {
bgneal@312 4819 if (o.detachEvent)
bgneal@312 4820 o.detachEvent('on' + n, f);
bgneal@312 4821 else if (o.removeEventListener)
bgneal@312 4822 o.removeEventListener(n, f, false);
bgneal@312 4823 else
bgneal@312 4824 o['on' + n] = null;
bgneal@312 4825 } catch (ex) {
bgneal@312 4826 // Might fail with permission denined on IE so we just ignore that
bgneal@312 4827 }
bgneal@312 4828 }
bgneal@312 4829 },
bgneal@312 4830
bgneal@312 4831 _pageInit : function(win) {
bgneal@312 4832 var t = this;
bgneal@312 4833
bgneal@312 4834 // Keep it from running more than once
bgneal@312 4835 if (t.domLoaded)
bgneal@312 4836 return;
bgneal@312 4837
bgneal@312 4838 t.domLoaded = true;
bgneal@312 4839
bgneal@312 4840 each(t.inits, function(c) {
bgneal@312 4841 c();
bgneal@312 4842 });
bgneal@312 4843
bgneal@312 4844 t.inits = [];
bgneal@312 4845 },
bgneal@312 4846
bgneal@312 4847 _wait : function(win) {
bgneal@312 4848 var t = this, doc = win.document;
bgneal@312 4849
bgneal@312 4850 // No need since the document is already loaded
bgneal@312 4851 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
bgneal@312 4852 t.domLoaded = 1;
bgneal@312 4853 return;
bgneal@312 4854 }
bgneal@312 4855
bgneal@312 4856 // Use IE method
bgneal@312 4857 if (doc.attachEvent) {
bgneal@312 4858 doc.attachEvent("onreadystatechange", function() {
bgneal@312 4859 if (doc.readyState === "complete") {
bgneal@312 4860 doc.detachEvent("onreadystatechange", arguments.callee);
bgneal@312 4861 t._pageInit(win);
bgneal@312 4862 }
bgneal@312 4863 });
bgneal@312 4864
bgneal@312 4865 if (doc.documentElement.doScroll && win == win.top) {
bgneal@312 4866 (function() {
bgneal@312 4867 if (t.domLoaded)
bgneal@312 4868 return;
bgneal@312 4869
bgneal@312 4870 try {
bgneal@312 4871 // If IE is used, use the trick by Diego Perini
bgneal@312 4872 // http://javascript.nwbox.com/IEContentLoaded/
bgneal@312 4873 doc.documentElement.doScroll("left");
bgneal@312 4874 } catch (ex) {
bgneal@312 4875 setTimeout(arguments.callee, 0);
bgneal@312 4876 return;
bgneal@312 4877 }
bgneal@312 4878
bgneal@312 4879 t._pageInit(win);
bgneal@312 4880 })();
bgneal@312 4881 }
bgneal@312 4882 } else if (doc.addEventListener) {
bgneal@312 4883 t._add(win, 'DOMContentLoaded', function() {
bgneal@312 4884 t._pageInit(win);
bgneal@312 4885 });
bgneal@312 4886 }
bgneal@312 4887
bgneal@312 4888 t._add(win, 'load', function() {
bgneal@312 4889 t._pageInit(win);
bgneal@312 4890 });
bgneal@312 4891 },
bgneal@312 4892
bgneal@312 4893 _stoppers : {
bgneal@312 4894 preventDefault : function() {
bgneal@312 4895 this.returnValue = false;
bgneal@312 4896 },
bgneal@312 4897
bgneal@312 4898 stopPropagation : function() {
bgneal@312 4899 this.cancelBubble = true;
bgneal@312 4900 }
bgneal@312 4901 }
bgneal@312 4902 });
bgneal@312 4903
bgneal@312 4904 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
bgneal@312 4905
bgneal@312 4906 // Dispatch DOM content loaded event for IE and Safari
bgneal@312 4907 Event._wait(window);
bgneal@312 4908
bgneal@312 4909 tinymce.addUnload(function() {
bgneal@312 4910 Event.destroy();
bgneal@312 4911 });
bgneal@312 4912 })(tinymce);
bgneal@312 4913
bgneal@312 4914 (function(tinymce) {
bgneal@312 4915 tinymce.dom.Element = function(id, settings) {
bgneal@312 4916 var t = this, dom, el;
bgneal@312 4917
bgneal@312 4918 t.settings = settings = settings || {};
bgneal@312 4919 t.id = id;
bgneal@312 4920 t.dom = dom = settings.dom || tinymce.DOM;
bgneal@312 4921
bgneal@312 4922 // Only IE leaks DOM references, this is a lot faster
bgneal@312 4923 if (!tinymce.isIE)
bgneal@312 4924 el = dom.get(t.id);
bgneal@312 4925
bgneal@312 4926 tinymce.each(
bgneal@312 4927 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
bgneal@312 4928 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
bgneal@312 4929 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
bgneal@312 4930 'isHidden,setHTML,get').split(/,/)
bgneal@312 4931 , function(k) {
bgneal@312 4932 t[k] = function() {
bgneal@312 4933 var a = [id], i;
bgneal@312 4934
bgneal@312 4935 for (i = 0; i < arguments.length; i++)
bgneal@312 4936 a.push(arguments[i]);
bgneal@312 4937
bgneal@312 4938 a = dom[k].apply(dom, a);
bgneal@312 4939 t.update(k);
bgneal@312 4940
bgneal@312 4941 return a;
bgneal@312 4942 };
bgneal@312 4943 });
bgneal@312 4944
bgneal@312 4945 tinymce.extend(t, {
bgneal@312 4946 on : function(n, f, s) {
bgneal@312 4947 return tinymce.dom.Event.add(t.id, n, f, s);
bgneal@312 4948 },
bgneal@312 4949
bgneal@312 4950 getXY : function() {
bgneal@312 4951 return {
bgneal@312 4952 x : parseInt(t.getStyle('left')),
bgneal@312 4953 y : parseInt(t.getStyle('top'))
bgneal@312 4954 };
bgneal@312 4955 },
bgneal@312 4956
bgneal@312 4957 getSize : function() {
bgneal@312 4958 var n = dom.get(t.id);
bgneal@312 4959
bgneal@312 4960 return {
bgneal@312 4961 w : parseInt(t.getStyle('width') || n.clientWidth),
bgneal@312 4962 h : parseInt(t.getStyle('height') || n.clientHeight)
bgneal@312 4963 };
bgneal@312 4964 },
bgneal@312 4965
bgneal@312 4966 moveTo : function(x, y) {
bgneal@312 4967 t.setStyles({left : x, top : y});
bgneal@312 4968 },
bgneal@312 4969
bgneal@312 4970 moveBy : function(x, y) {
bgneal@312 4971 var p = t.getXY();
bgneal@312 4972
bgneal@312 4973 t.moveTo(p.x + x, p.y + y);
bgneal@312 4974 },
bgneal@312 4975
bgneal@312 4976 resizeTo : function(w, h) {
bgneal@312 4977 t.setStyles({width : w, height : h});
bgneal@312 4978 },
bgneal@312 4979
bgneal@312 4980 resizeBy : function(w, h) {
bgneal@312 4981 var s = t.getSize();
bgneal@312 4982
bgneal@312 4983 t.resizeTo(s.w + w, s.h + h);
bgneal@312 4984 },
bgneal@312 4985
bgneal@312 4986 update : function(k) {
bgneal@312 4987 var b;
bgneal@312 4988
bgneal@312 4989 if (tinymce.isIE6 && settings.blocker) {
bgneal@312 4990 k = k || '';
bgneal@312 4991
bgneal@312 4992 // Ignore getters
bgneal@312 4993 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
bgneal@312 4994 return;
bgneal@312 4995
bgneal@312 4996 // Remove blocker on remove
bgneal@312 4997 if (k == 'remove') {
bgneal@312 4998 dom.remove(t.blocker);
bgneal@312 4999 return;
bgneal@312 5000 }
bgneal@312 5001
bgneal@312 5002 if (!t.blocker) {
bgneal@312 5003 t.blocker = dom.uniqueId();
bgneal@312 5004 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
bgneal@312 5005 dom.setStyle(b, 'opacity', 0);
bgneal@312 5006 } else
bgneal@312 5007 b = dom.get(t.blocker);
bgneal@312 5008
bgneal@312 5009 dom.setStyles(b, {
bgneal@312 5010 left : t.getStyle('left', 1),
bgneal@312 5011 top : t.getStyle('top', 1),
bgneal@312 5012 width : t.getStyle('width', 1),
bgneal@312 5013 height : t.getStyle('height', 1),
bgneal@312 5014 display : t.getStyle('display', 1),
bgneal@312 5015 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
bgneal@312 5016 });
bgneal@312 5017 }
bgneal@312 5018 }
bgneal@312 5019 });
bgneal@312 5020 };
bgneal@312 5021 })(tinymce);
bgneal@312 5022
bgneal@312 5023 (function(tinymce) {
bgneal@312 5024 function trimNl(s) {
bgneal@312 5025 return s.replace(/[\n\r]+/g, '');
bgneal@312 5026 };
bgneal@312 5027
bgneal@312 5028 // Shorten names
bgneal@312 5029 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
bgneal@312 5030
bgneal@312 5031 tinymce.create('tinymce.dom.Selection', {
bgneal@312 5032 Selection : function(dom, win, serializer) {
bgneal@312 5033 var t = this;
bgneal@312 5034
bgneal@312 5035 t.dom = dom;
bgneal@312 5036 t.win = win;
bgneal@312 5037 t.serializer = serializer;
bgneal@312 5038
bgneal@312 5039 // Add events
bgneal@312 5040 each([
bgneal@312 5041 'onBeforeSetContent',
bgneal@312 5042 'onBeforeGetContent',
bgneal@312 5043 'onSetContent',
bgneal@312 5044 'onGetContent'
bgneal@312 5045 ], function(e) {
bgneal@312 5046 t[e] = new tinymce.util.Dispatcher(t);
bgneal@312 5047 });
bgneal@312 5048
bgneal@312 5049 // No W3C Range support
bgneal@312 5050 if (!t.win.getSelection)
bgneal@312 5051 t.tridentSel = new tinymce.dom.TridentSelection(t);
bgneal@312 5052
bgneal@312 5053 // Prevent leaks
bgneal@312 5054 tinymce.addUnload(t.destroy, t);
bgneal@312 5055 },
bgneal@312 5056
bgneal@312 5057 getContent : function(s) {
bgneal@312 5058 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
bgneal@312 5059
bgneal@312 5060 s = s || {};
bgneal@312 5061 wb = wa = '';
bgneal@312 5062 s.get = true;
bgneal@312 5063 s.format = s.format || 'html';
bgneal@312 5064 t.onBeforeGetContent.dispatch(t, s);
bgneal@312 5065
bgneal@312 5066 if (s.format == 'text')
bgneal@312 5067 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
bgneal@312 5068
bgneal@312 5069 if (r.cloneContents) {
bgneal@312 5070 n = r.cloneContents();
bgneal@312 5071
bgneal@312 5072 if (n)
bgneal@312 5073 e.appendChild(n);
bgneal@312 5074 } else if (is(r.item) || is(r.htmlText))
bgneal@312 5075 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
bgneal@312 5076 else
bgneal@312 5077 e.innerHTML = r.toString();
bgneal@312 5078
bgneal@312 5079 // Keep whitespace before and after
bgneal@312 5080 if (/^\s/.test(e.innerHTML))
bgneal@312 5081 wb = ' ';
bgneal@312 5082
bgneal@312 5083 if (/\s+$/.test(e.innerHTML))
bgneal@312 5084 wa = ' ';
bgneal@312 5085
bgneal@312 5086 s.getInner = true;
bgneal@312 5087
bgneal@312 5088 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
bgneal@312 5089 t.onGetContent.dispatch(t, s);
bgneal@312 5090
bgneal@312 5091 return s.content;
bgneal@312 5092 },
bgneal@312 5093
bgneal@312 5094 setContent : function(h, s) {
bgneal@312 5095 var t = this, r = t.getRng(), c, d = t.win.document;
bgneal@312 5096
bgneal@312 5097 s = s || {format : 'html'};
bgneal@312 5098 s.set = true;
bgneal@312 5099 h = s.content = t.dom.processHTML(h);
bgneal@312 5100
bgneal@312 5101 // Dispatch before set content event
bgneal@312 5102 t.onBeforeSetContent.dispatch(t, s);
bgneal@312 5103 h = s.content;
bgneal@312 5104
bgneal@312 5105 if (r.insertNode) {
bgneal@312 5106 // Make caret marker since insertNode places the caret in the beginning of text after insert
bgneal@312 5107 h += '<span id="__caret">_</span>';
bgneal@312 5108
bgneal@312 5109 // Delete and insert new node
bgneal@312 5110
bgneal@312 5111 if (r.startContainer == d && r.endContainer == d) {
bgneal@312 5112 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
bgneal@312 5113 d.body.innerHTML = h;
bgneal@312 5114 } else {
bgneal@312 5115 r.deleteContents();
bgneal@312 5116 if (d.body.childNodes.length == 0) {
bgneal@312 5117 d.body.innerHTML = h;
bgneal@312 5118 } else {
bgneal@312 5119 r.insertNode(r.createContextualFragment(h));
bgneal@312 5120 }
bgneal@312 5121 }
bgneal@312 5122
bgneal@312 5123 // Move to caret marker
bgneal@312 5124 c = t.dom.get('__caret');
bgneal@312 5125 // Make sure we wrap it compleatly, Opera fails with a simple select call
bgneal@312 5126 r = d.createRange();
bgneal@312 5127 r.setStartBefore(c);
bgneal@312 5128 r.setEndBefore(c);
bgneal@312 5129 t.setRng(r);
bgneal@312 5130
bgneal@312 5131 // Remove the caret position
bgneal@312 5132 t.dom.remove('__caret');
bgneal@312 5133 } else {
bgneal@312 5134 if (r.item) {
bgneal@312 5135 // Delete content and get caret text selection
bgneal@312 5136 d.execCommand('Delete', false, null);
bgneal@312 5137 r = t.getRng();
bgneal@312 5138 }
bgneal@312 5139
bgneal@312 5140 r.pasteHTML(h);
bgneal@312 5141 }
bgneal@312 5142
bgneal@312 5143 // Dispatch set content event
bgneal@312 5144 t.onSetContent.dispatch(t, s);
bgneal@312 5145 },
bgneal@312 5146
bgneal@312 5147 getStart : function() {
bgneal@312 5148 var rng = this.getRng(), startElement, parentElement, checkRng, node;
bgneal@312 5149
bgneal@312 5150 if (rng.duplicate || rng.item) {
bgneal@312 5151 // Control selection, return first item
bgneal@312 5152 if (rng.item)
bgneal@312 5153 return rng.item(0);
bgneal@312 5154
bgneal@312 5155 // Get start element
bgneal@312 5156 checkRng = rng.duplicate();
bgneal@312 5157 checkRng.collapse(1);
bgneal@312 5158 startElement = checkRng.parentElement();
bgneal@312 5159
bgneal@312 5160 // Check if range parent is inside the start element, then return the inner parent element
bgneal@312 5161 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
bgneal@312 5162 parentElement = node = rng.parentElement();
bgneal@312 5163 while (node = node.parentNode) {
bgneal@312 5164 if (node == startElement) {
bgneal@312 5165 startElement = parentElement;
bgneal@312 5166 break;
bgneal@312 5167 }
bgneal@312 5168 }
bgneal@312 5169
bgneal@312 5170 // If start element is body element try to move to the first child if it exists
bgneal@312 5171 if (startElement && startElement.nodeName == 'BODY')
bgneal@312 5172 return startElement.firstChild || startElement;
bgneal@312 5173
bgneal@312 5174 return startElement;
bgneal@312 5175 } else {
bgneal@312 5176 startElement = rng.startContainer;
bgneal@312 5177
bgneal@312 5178 if (startElement.nodeType == 1 && startElement.hasChildNodes())
bgneal@312 5179 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
bgneal@312 5180
bgneal@312 5181 if (startElement && startElement.nodeType == 3)
bgneal@312 5182 return startElement.parentNode;
bgneal@312 5183
bgneal@312 5184 return startElement;
bgneal@312 5185 }
bgneal@312 5186 },
bgneal@312 5187
bgneal@312 5188 getEnd : function() {
bgneal@312 5189 var t = this, r = t.getRng(), e, eo;
bgneal@312 5190
bgneal@312 5191 if (r.duplicate || r.item) {
bgneal@312 5192 if (r.item)
bgneal@312 5193 return r.item(0);
bgneal@312 5194
bgneal@312 5195 r = r.duplicate();
bgneal@312 5196 r.collapse(0);
bgneal@312 5197 e = r.parentElement();
bgneal@312 5198
bgneal@312 5199 if (e && e.nodeName == 'BODY')
bgneal@312 5200 return e.lastChild || e;
bgneal@312 5201
bgneal@312 5202 return e;
bgneal@312 5203 } else {
bgneal@312 5204 e = r.endContainer;
bgneal@312 5205 eo = r.endOffset;
bgneal@312 5206
bgneal@312 5207 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@312 5208 e = e.childNodes[eo > 0 ? eo - 1 : eo];
bgneal@312 5209
bgneal@312 5210 if (e && e.nodeType == 3)
bgneal@312 5211 return e.parentNode;
bgneal@312 5212
bgneal@312 5213 return e;
bgneal@312 5214 }
bgneal@312 5215 },
bgneal@312 5216
bgneal@312 5217 getBookmark : function(type, normalized) {
bgneal@312 5218 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
bgneal@312 5219
bgneal@312 5220 function findIndex(name, element) {
bgneal@312 5221 var index = 0;
bgneal@312 5222
bgneal@312 5223 each(dom.select(name), function(node, i) {
bgneal@312 5224 if (node == element)
bgneal@312 5225 index = i;
bgneal@312 5226 });
bgneal@312 5227
bgneal@312 5228 return index;
bgneal@312 5229 };
bgneal@312 5230
bgneal@312 5231 if (type == 2) {
bgneal@312 5232 function getLocation() {
bgneal@312 5233 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
bgneal@312 5234
bgneal@312 5235 function getPoint(rng, start) {
bgneal@312 5236 var container = rng[start ? 'startContainer' : 'endContainer'],
bgneal@312 5237 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
bgneal@312 5238
bgneal@312 5239 if (container.nodeType == 3) {
bgneal@312 5240 if (normalized) {
bgneal@312 5241 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
bgneal@312 5242 offset += node.nodeValue.length;
bgneal@312 5243 }
bgneal@312 5244
bgneal@312 5245 point.push(offset);
bgneal@312 5246 } else {
bgneal@312 5247 childNodes = container.childNodes;
bgneal@312 5248
bgneal@312 5249 if (offset >= childNodes.length && childNodes.length) {
bgneal@312 5250 after = 1;
bgneal@312 5251 offset = Math.max(0, childNodes.length - 1);
bgneal@312 5252 }
bgneal@312 5253
bgneal@312 5254 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
bgneal@312 5255 }
bgneal@312 5256
bgneal@312 5257 for (; container && container != root; container = container.parentNode)
bgneal@312 5258 point.push(t.dom.nodeIndex(container, normalized));
bgneal@312 5259
bgneal@312 5260 return point;
bgneal@312 5261 };
bgneal@312 5262
bgneal@312 5263 bookmark.start = getPoint(rng, true);
bgneal@312 5264
bgneal@312 5265 if (!t.isCollapsed())
bgneal@312 5266 bookmark.end = getPoint(rng);
bgneal@312 5267
bgneal@312 5268 return bookmark;
bgneal@312 5269 };
bgneal@312 5270
bgneal@312 5271 return getLocation();
bgneal@312 5272 }
bgneal@312 5273
bgneal@312 5274 // Handle simple range
bgneal@312 5275 if (type)
bgneal@312 5276 return {rng : t.getRng()};
bgneal@312 5277
bgneal@312 5278 rng = t.getRng();
bgneal@312 5279 id = dom.uniqueId();
bgneal@312 5280 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
bgneal@312 5281 styles = 'overflow:hidden;line-height:0px';
bgneal@312 5282
bgneal@312 5283 // Explorer method
bgneal@312 5284 if (rng.duplicate || rng.item) {
bgneal@312 5285 // Text selection
bgneal@312 5286 if (!rng.item) {
bgneal@312 5287 rng2 = rng.duplicate();
bgneal@312 5288
bgneal@312 5289 // Insert start marker
bgneal@312 5290 rng.collapse();
bgneal@312 5291 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
bgneal@312 5292
bgneal@312 5293 // Insert end marker
bgneal@312 5294 if (!collapsed) {
bgneal@312 5295 rng2.collapse(false);
bgneal@312 5296 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
bgneal@312 5297 }
bgneal@312 5298 } else {
bgneal@312 5299 // Control selection
bgneal@312 5300 element = rng.item(0);
bgneal@312 5301 name = element.nodeName;
bgneal@312 5302
bgneal@312 5303 return {name : name, index : findIndex(name, element)};
bgneal@312 5304 }
bgneal@312 5305 } else {
bgneal@312 5306 element = t.getNode();
bgneal@312 5307 name = element.nodeName;
bgneal@312 5308 if (name == 'IMG')
bgneal@312 5309 return {name : name, index : findIndex(name, element)};
bgneal@312 5310
bgneal@312 5311 // W3C method
bgneal@312 5312 rng2 = rng.cloneRange();
bgneal@312 5313
bgneal@312 5314 // Insert end marker
bgneal@312 5315 if (!collapsed) {
bgneal@312 5316 rng2.collapse(false);
bgneal@312 5317 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
bgneal@312 5318 }
bgneal@312 5319
bgneal@312 5320 rng.collapse(true);
bgneal@312 5321 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
bgneal@312 5322 }
bgneal@312 5323
bgneal@312 5324 t.moveToBookmark({id : id, keep : 1});
bgneal@312 5325
bgneal@312 5326 return {id : id};
bgneal@312 5327 },
bgneal@312 5328
bgneal@312 5329 moveToBookmark : function(bookmark) {
bgneal@312 5330 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
bgneal@312 5331
bgneal@312 5332 // Clear selection cache
bgneal@312 5333 if (t.tridentSel)
bgneal@312 5334 t.tridentSel.destroy();
bgneal@312 5335
bgneal@312 5336 if (bookmark) {
bgneal@312 5337 if (bookmark.start) {
bgneal@312 5338 rng = dom.createRng();
bgneal@312 5339 root = dom.getRoot();
bgneal@312 5340
bgneal@312 5341 function setEndPoint(start) {
bgneal@312 5342 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
bgneal@312 5343
bgneal@312 5344 if (point) {
bgneal@312 5345 // Find container node
bgneal@312 5346 for (node = root, i = point.length - 1; i >= 1; i--) {
bgneal@312 5347 children = node.childNodes;
bgneal@312 5348
bgneal@312 5349 if (children.length)
bgneal@312 5350 node = children[point[i]];
bgneal@312 5351 }
bgneal@312 5352
bgneal@312 5353 // Set offset within container node
bgneal@312 5354 if (start)
bgneal@312 5355 rng.setStart(node, point[0]);
bgneal@312 5356 else
bgneal@312 5357 rng.setEnd(node, point[0]);
bgneal@312 5358 }
bgneal@312 5359 };
bgneal@312 5360
bgneal@312 5361 setEndPoint(true);
bgneal@312 5362 setEndPoint();
bgneal@312 5363
bgneal@312 5364 t.setRng(rng);
bgneal@312 5365 } else if (bookmark.id) {
bgneal@312 5366 function restoreEndPoint(suffix) {
bgneal@312 5367 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
bgneal@312 5368
bgneal@312 5369 if (marker) {
bgneal@312 5370 node = marker.parentNode;
bgneal@312 5371
bgneal@312 5372 if (suffix == 'start') {
bgneal@312 5373 if (!keep) {
bgneal@312 5374 idx = dom.nodeIndex(marker);
bgneal@312 5375 } else {
bgneal@312 5376 node = marker.firstChild;
bgneal@312 5377 idx = 1;
bgneal@312 5378 }
bgneal@312 5379
bgneal@312 5380 startContainer = endContainer = node;
bgneal@312 5381 startOffset = endOffset = idx;
bgneal@312 5382 } else {
bgneal@312 5383 if (!keep) {
bgneal@312 5384 idx = dom.nodeIndex(marker);
bgneal@312 5385 } else {
bgneal@312 5386 node = marker.firstChild;
bgneal@312 5387 idx = 1;
bgneal@312 5388 }
bgneal@312 5389
bgneal@312 5390 endContainer = node;
bgneal@312 5391 endOffset = idx;
bgneal@312 5392 }
bgneal@312 5393
bgneal@312 5394 if (!keep) {
bgneal@312 5395 prev = marker.previousSibling;
bgneal@312 5396 next = marker.nextSibling;
bgneal@312 5397
bgneal@312 5398 // Remove all marker text nodes
bgneal@312 5399 each(tinymce.grep(marker.childNodes), function(node) {
bgneal@312 5400 if (node.nodeType == 3)
bgneal@312 5401 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
bgneal@312 5402 });
bgneal@312 5403
bgneal@312 5404 // Remove marker but keep children if for example contents where inserted into the marker
bgneal@312 5405 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
bgneal@312 5406 while (marker = dom.get(bookmark.id + '_' + suffix))
bgneal@312 5407 dom.remove(marker, 1);
bgneal@312 5408
bgneal@312 5409 // If siblings are text nodes then merge them
bgneal@312 5410 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
bgneal@312 5411 idx = prev.nodeValue.length;
bgneal@312 5412 prev.appendData(next.nodeValue);
bgneal@312 5413 dom.remove(next);
bgneal@312 5414
bgneal@312 5415 if (suffix == 'start') {
bgneal@312 5416 startContainer = endContainer = prev;
bgneal@312 5417 startOffset = endOffset = idx;
bgneal@312 5418 } else {
bgneal@312 5419 endContainer = prev;
bgneal@312 5420 endOffset = idx;
bgneal@312 5421 }
bgneal@312 5422 }
bgneal@312 5423 }
bgneal@312 5424 }
bgneal@312 5425 };
bgneal@312 5426
bgneal@312 5427 function addBogus(node) {
bgneal@312 5428 // Adds a bogus BR element for empty block elements
bgneal@312 5429 // on non IE browsers just to have a place to put the caret
bgneal@312 5430 if (!isIE && dom.isBlock(node) && !node.innerHTML)
bgneal@312 5431 node.innerHTML = '<br _mce_bogus="1" />';
bgneal@312 5432
bgneal@312 5433 return node;
bgneal@312 5434 };
bgneal@312 5435
bgneal@312 5436 // Restore start/end points
bgneal@312 5437 restoreEndPoint('start');
bgneal@312 5438 restoreEndPoint('end');
bgneal@312 5439
bgneal@312 5440 if (startContainer) {
bgneal@312 5441 rng = dom.createRng();
bgneal@312 5442 rng.setStart(addBogus(startContainer), startOffset);
bgneal@312 5443 rng.setEnd(addBogus(endContainer), endOffset);
bgneal@312 5444 t.setRng(rng);
bgneal@312 5445 }
bgneal@312 5446 } else if (bookmark.name) {
bgneal@312 5447 t.select(dom.select(bookmark.name)[bookmark.index]);
bgneal@312 5448 } else if (bookmark.rng)
bgneal@312 5449 t.setRng(bookmark.rng);
bgneal@312 5450 }
bgneal@312 5451 },
bgneal@312 5452
bgneal@312 5453 select : function(node, content) {
bgneal@312 5454 var t = this, dom = t.dom, rng = dom.createRng(), idx;
bgneal@312 5455
bgneal@312 5456 idx = dom.nodeIndex(node);
bgneal@312 5457 rng.setStart(node.parentNode, idx);
bgneal@312 5458 rng.setEnd(node.parentNode, idx + 1);
bgneal@312 5459
bgneal@312 5460 // Find first/last text node or BR element
bgneal@312 5461 if (content) {
bgneal@312 5462 function setPoint(node, start) {
bgneal@312 5463 var walker = new tinymce.dom.TreeWalker(node, node);
bgneal@312 5464
bgneal@312 5465 do {
bgneal@312 5466 // Text node
bgneal@312 5467 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
bgneal@312 5468 if (start)
bgneal@312 5469 rng.setStart(node, 0);
bgneal@312 5470 else
bgneal@312 5471 rng.setEnd(node, node.nodeValue.length);
bgneal@312 5472
bgneal@312 5473 return;
bgneal@312 5474 }
bgneal@312 5475
bgneal@312 5476 // BR element
bgneal@312 5477 if (node.nodeName == 'BR') {
bgneal@312 5478 if (start)
bgneal@312 5479 rng.setStartBefore(node);
bgneal@312 5480 else
bgneal@312 5481 rng.setEndBefore(node);
bgneal@312 5482
bgneal@312 5483 return;
bgneal@312 5484 }
bgneal@312 5485 } while (node = (start ? walker.next() : walker.prev()));
bgneal@312 5486 };
bgneal@312 5487
bgneal@312 5488 setPoint(node, 1);
bgneal@312 5489 setPoint(node);
bgneal@312 5490 }
bgneal@312 5491
bgneal@312 5492 t.setRng(rng);
bgneal@312 5493
bgneal@312 5494 return node;
bgneal@312 5495 },
bgneal@312 5496
bgneal@312 5497 isCollapsed : function() {
bgneal@312 5498 var t = this, r = t.getRng(), s = t.getSel();
bgneal@312 5499
bgneal@312 5500 if (!r || r.item)
bgneal@312 5501 return false;
bgneal@312 5502
bgneal@312 5503 if (r.compareEndPoints)
bgneal@312 5504 return r.compareEndPoints('StartToEnd', r) === 0;
bgneal@312 5505
bgneal@312 5506 return !s || r.collapsed;
bgneal@312 5507 },
bgneal@312 5508
bgneal@312 5509 collapse : function(b) {
bgneal@312 5510 var t = this, r = t.getRng(), n;
bgneal@312 5511
bgneal@312 5512 // Control range on IE
bgneal@312 5513 if (r.item) {
bgneal@312 5514 n = r.item(0);
bgneal@312 5515 r = this.win.document.body.createTextRange();
bgneal@312 5516 r.moveToElementText(n);
bgneal@312 5517 }
bgneal@312 5518
bgneal@312 5519 r.collapse(!!b);
bgneal@312 5520 t.setRng(r);
bgneal@312 5521 },
bgneal@312 5522
bgneal@312 5523 getSel : function() {
bgneal@312 5524 var t = this, w = this.win;
bgneal@312 5525
bgneal@312 5526 return w.getSelection ? w.getSelection() : w.document.selection;
bgneal@312 5527 },
bgneal@312 5528
bgneal@312 5529 getRng : function(w3c) {
bgneal@312 5530 var t = this, s, r;
bgneal@312 5531
bgneal@312 5532 // Found tridentSel object then we need to use that one
bgneal@312 5533 if (w3c && t.tridentSel)
bgneal@312 5534 return t.tridentSel.getRangeAt(0);
bgneal@312 5535
bgneal@312 5536 try {
bgneal@312 5537 if (s = t.getSel())
bgneal@312 5538 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
bgneal@312 5539 } catch (ex) {
bgneal@312 5540 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
bgneal@312 5541 }
bgneal@312 5542
bgneal@312 5543 // No range found then create an empty one
bgneal@312 5544 // This can occur when the editor is placed in a hidden container element on Gecko
bgneal@312 5545 // Or on IE when there was an exception
bgneal@312 5546 if (!r)
bgneal@312 5547 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
bgneal@312 5548
bgneal@312 5549 if (t.selectedRange && t.explicitRange) {
bgneal@312 5550 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
bgneal@312 5551 // Safari, Opera and Chrome only ever select text which causes the range to change.
bgneal@312 5552 // This lets us use the originally set range if the selection hasn't been changed by the user.
bgneal@312 5553 r = t.explicitRange;
bgneal@312 5554 } else {
bgneal@312 5555 t.selectedRange = null;
bgneal@312 5556 t.explicitRange = null;
bgneal@312 5557 }
bgneal@312 5558 }
bgneal@312 5559 return r;
bgneal@312 5560 },
bgneal@312 5561
bgneal@312 5562 setRng : function(r) {
bgneal@312 5563 var s, t = this;
bgneal@312 5564
bgneal@312 5565 if (!t.tridentSel) {
bgneal@312 5566 s = t.getSel();
bgneal@312 5567
bgneal@312 5568 if (s) {
bgneal@312 5569 t.explicitRange = r;
bgneal@312 5570 s.removeAllRanges();
bgneal@312 5571 s.addRange(r);
bgneal@312 5572 t.selectedRange = s.getRangeAt(0);
bgneal@312 5573 }
bgneal@312 5574 } else {
bgneal@312 5575 // Is W3C Range
bgneal@312 5576 if (r.cloneRange) {
bgneal@312 5577 t.tridentSel.addRange(r);
bgneal@312 5578 return;
bgneal@312 5579 }
bgneal@312 5580
bgneal@312 5581 // Is IE specific range
bgneal@312 5582 try {
bgneal@312 5583 r.select();
bgneal@312 5584 } catch (ex) {
bgneal@312 5585 // Needed for some odd IE bug #1843306
bgneal@312 5586 }
bgneal@312 5587 }
bgneal@312 5588 },
bgneal@312 5589
bgneal@312 5590 setNode : function(n) {
bgneal@312 5591 var t = this;
bgneal@312 5592
bgneal@312 5593 t.setContent(t.dom.getOuterHTML(n));
bgneal@312 5594
bgneal@312 5595 return n;
bgneal@312 5596 },
bgneal@312 5597
bgneal@312 5598 getNode : function() {
bgneal@312 5599 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
bgneal@312 5600
bgneal@312 5601 if (rng.setStart) {
bgneal@312 5602 // Range maybe lost after the editor is made visible again
bgneal@312 5603 if (!rng)
bgneal@312 5604 return t.dom.getRoot();
bgneal@312 5605
bgneal@312 5606 elm = rng.commonAncestorContainer;
bgneal@312 5607
bgneal@312 5608 // Handle selection a image or other control like element such as anchors
bgneal@312 5609 if (!rng.collapsed) {
bgneal@312 5610 if (rng.startContainer == rng.endContainer) {
bgneal@312 5611 if (rng.startOffset - rng.endOffset < 2) {
bgneal@312 5612 if (rng.startContainer.hasChildNodes())
bgneal@312 5613 elm = rng.startContainer.childNodes[rng.startOffset];
bgneal@312 5614 }
bgneal@312 5615 }
bgneal@312 5616
bgneal@312 5617 // If the anchor node is a element instead of a text node then return this element
bgneal@312 5618 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
bgneal@312 5619 return sel.anchorNode.childNodes[sel.anchorOffset];
bgneal@312 5620 }
bgneal@312 5621
bgneal@312 5622 if (elm && elm.nodeType == 3)
bgneal@312 5623 return elm.parentNode;
bgneal@312 5624
bgneal@312 5625 return elm;
bgneal@312 5626 }
bgneal@312 5627
bgneal@312 5628 return rng.item ? rng.item(0) : rng.parentElement();
bgneal@312 5629 },
bgneal@312 5630
bgneal@312 5631 getSelectedBlocks : function(st, en) {
bgneal@312 5632 var t = this, dom = t.dom, sb, eb, n, bl = [];
bgneal@312 5633
bgneal@312 5634 sb = dom.getParent(st || t.getStart(), dom.isBlock);
bgneal@312 5635 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
bgneal@312 5636
bgneal@312 5637 if (sb)
bgneal@312 5638 bl.push(sb);
bgneal@312 5639
bgneal@312 5640 if (sb && eb && sb != eb) {
bgneal@312 5641 n = sb;
bgneal@312 5642
bgneal@312 5643 while ((n = n.nextSibling) && n != eb) {
bgneal@312 5644 if (dom.isBlock(n))
bgneal@312 5645 bl.push(n);
bgneal@312 5646 }
bgneal@312 5647 }
bgneal@312 5648
bgneal@312 5649 if (eb && sb != eb)
bgneal@312 5650 bl.push(eb);
bgneal@312 5651
bgneal@312 5652 return bl;
bgneal@312 5653 },
bgneal@312 5654
bgneal@312 5655 destroy : function(s) {
bgneal@312 5656 var t = this;
bgneal@312 5657
bgneal@312 5658 t.win = null;
bgneal@312 5659
bgneal@312 5660 if (t.tridentSel)
bgneal@312 5661 t.tridentSel.destroy();
bgneal@312 5662
bgneal@312 5663 // Manual destroy then remove unload handler
bgneal@312 5664 if (!s)
bgneal@312 5665 tinymce.removeUnload(t.destroy);
bgneal@312 5666 }
bgneal@312 5667 });
bgneal@312 5668 })(tinymce);
bgneal@312 5669
bgneal@312 5670 (function(tinymce) {
bgneal@312 5671 tinymce.create('tinymce.dom.XMLWriter', {
bgneal@312 5672 node : null,
bgneal@312 5673
bgneal@312 5674 XMLWriter : function(s) {
bgneal@312 5675 // Get XML document
bgneal@312 5676 function getXML() {
bgneal@312 5677 var i = document.implementation;
bgneal@312 5678
bgneal@312 5679 if (!i || !i.createDocument) {
bgneal@312 5680 // Try IE objects
bgneal@312 5681 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
bgneal@312 5682 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
bgneal@312 5683 } else
bgneal@312 5684 return i.createDocument('', '', null);
bgneal@312 5685 };
bgneal@312 5686
bgneal@312 5687 this.doc = getXML();
bgneal@312 5688
bgneal@312 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@312 5690 this.valid = tinymce.isOpera || tinymce.isWebKit;
bgneal@312 5691
bgneal@312 5692 this.reset();
bgneal@312 5693 },
bgneal@312 5694
bgneal@312 5695 reset : function() {
bgneal@312 5696 var t = this, d = t.doc;
bgneal@312 5697
bgneal@312 5698 if (d.firstChild)
bgneal@312 5699 d.removeChild(d.firstChild);
bgneal@312 5700
bgneal@312 5701 t.node = d.appendChild(d.createElement("html"));
bgneal@312 5702 },
bgneal@312 5703
bgneal@312 5704 writeStartElement : function(n) {
bgneal@312 5705 var t = this;
bgneal@312 5706
bgneal@312 5707 t.node = t.node.appendChild(t.doc.createElement(n));
bgneal@312 5708 },
bgneal@312 5709
bgneal@312 5710 writeAttribute : function(n, v) {
bgneal@312 5711 if (this.valid)
bgneal@312 5712 v = v.replace(/>/g, '%MCGT%');
bgneal@312 5713
bgneal@312 5714 this.node.setAttribute(n, v);
bgneal@312 5715 },
bgneal@312 5716
bgneal@312 5717 writeEndElement : function() {
bgneal@312 5718 this.node = this.node.parentNode;
bgneal@312 5719 },
bgneal@312 5720
bgneal@312 5721 writeFullEndElement : function() {
bgneal@312 5722 var t = this, n = t.node;
bgneal@312 5723
bgneal@312 5724 n.appendChild(t.doc.createTextNode(""));
bgneal@312 5725 t.node = n.parentNode;
bgneal@312 5726 },
bgneal@312 5727
bgneal@312 5728 writeText : function(v) {
bgneal@312 5729 if (this.valid)
bgneal@312 5730 v = v.replace(/>/g, '%MCGT%');
bgneal@312 5731
bgneal@312 5732 this.node.appendChild(this.doc.createTextNode(v));
bgneal@312 5733 },
bgneal@312 5734
bgneal@312 5735 writeCDATA : function(v) {
bgneal@312 5736 this.node.appendChild(this.doc.createCDATASection(v));
bgneal@312 5737 },
bgneal@312 5738
bgneal@312 5739 writeComment : function(v) {
bgneal@312 5740 // Fix for bug #2035694
bgneal@312 5741 if (tinymce.isIE)
bgneal@312 5742 v = v.replace(/^\-|\-$/g, ' ');
bgneal@312 5743
bgneal@312 5744 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
bgneal@312 5745 },
bgneal@312 5746
bgneal@312 5747 getContent : function() {
bgneal@312 5748 var h;
bgneal@312 5749
bgneal@312 5750 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
bgneal@312 5751 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
bgneal@312 5752 h = h.replace(/ ?\/>/g, ' />');
bgneal@312 5753
bgneal@312 5754 if (this.valid)
bgneal@312 5755 h = h.replace(/\%MCGT%/g, '&gt;');
bgneal@312 5756
bgneal@312 5757 return h;
bgneal@312 5758 }
bgneal@312 5759 });
bgneal@312 5760 })(tinymce);
bgneal@312 5761
bgneal@312 5762 (function(tinymce) {
bgneal@312 5763 tinymce.create('tinymce.dom.StringWriter', {
bgneal@312 5764 str : null,
bgneal@312 5765 tags : null,
bgneal@312 5766 count : 0,
bgneal@312 5767 settings : null,
bgneal@312 5768 indent : null,
bgneal@312 5769
bgneal@312 5770 StringWriter : function(s) {
bgneal@312 5771 this.settings = tinymce.extend({
bgneal@312 5772 indent_char : ' ',
bgneal@312 5773 indentation : 0
bgneal@312 5774 }, s);
bgneal@312 5775
bgneal@312 5776 this.reset();
bgneal@312 5777 },
bgneal@312 5778
bgneal@312 5779 reset : function() {
bgneal@312 5780 this.indent = '';
bgneal@312 5781 this.str = "";
bgneal@312 5782 this.tags = [];
bgneal@312 5783 this.count = 0;
bgneal@312 5784 },
bgneal@312 5785
bgneal@312 5786 writeStartElement : function(n) {
bgneal@312 5787 this._writeAttributesEnd();
bgneal@312 5788 this.writeRaw('<' + n);
bgneal@312 5789 this.tags.push(n);
bgneal@312 5790 this.inAttr = true;
bgneal@312 5791 this.count++;
bgneal@312 5792 this.elementCount = this.count;
bgneal@312 5793 },
bgneal@312 5794
bgneal@312 5795 writeAttribute : function(n, v) {
bgneal@312 5796 var t = this;
bgneal@312 5797
bgneal@312 5798 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
bgneal@312 5799 },
bgneal@312 5800
bgneal@312 5801 writeEndElement : function() {
bgneal@312 5802 var n;
bgneal@312 5803
bgneal@312 5804 if (this.tags.length > 0) {
bgneal@312 5805 n = this.tags.pop();
bgneal@312 5806
bgneal@312 5807 if (this._writeAttributesEnd(1))
bgneal@312 5808 this.writeRaw('</' + n + '>');
bgneal@312 5809
bgneal@312 5810 if (this.settings.indentation > 0)
bgneal@312 5811 this.writeRaw('\n');
bgneal@312 5812 }
bgneal@312 5813 },
bgneal@312 5814
bgneal@312 5815 writeFullEndElement : function() {
bgneal@312 5816 if (this.tags.length > 0) {
bgneal@312 5817 this._writeAttributesEnd();
bgneal@312 5818 this.writeRaw('</' + this.tags.pop() + '>');
bgneal@312 5819
bgneal@312 5820 if (this.settings.indentation > 0)
bgneal@312 5821 this.writeRaw('\n');
bgneal@312 5822 }
bgneal@312 5823 },
bgneal@312 5824
bgneal@312 5825 writeText : function(v) {
bgneal@312 5826 this._writeAttributesEnd();
bgneal@312 5827 this.writeRaw(this.encode(v));
bgneal@312 5828 this.count++;
bgneal@312 5829 },
bgneal@312 5830
bgneal@312 5831 writeCDATA : function(v) {
bgneal@312 5832 this._writeAttributesEnd();
bgneal@312 5833 this.writeRaw('<![CDATA[' + v + ']]>');
bgneal@312 5834 this.count++;
bgneal@312 5835 },
bgneal@312 5836
bgneal@312 5837 writeComment : function(v) {
bgneal@312 5838 this._writeAttributesEnd();
bgneal@312 5839 this.writeRaw('<!-- ' + v + '-->');
bgneal@312 5840 this.count++;
bgneal@312 5841 },
bgneal@312 5842
bgneal@312 5843 writeRaw : function(v) {
bgneal@312 5844 this.str += v;
bgneal@312 5845 },
bgneal@312 5846
bgneal@312 5847 encode : function(s) {
bgneal@312 5848 return s.replace(/[<>&"]/g, function(v) {
bgneal@312 5849 switch (v) {
bgneal@312 5850 case '<':
bgneal@312 5851 return '&lt;';
bgneal@312 5852
bgneal@312 5853 case '>':
bgneal@312 5854 return '&gt;';
bgneal@312 5855
bgneal@312 5856 case '&':
bgneal@312 5857 return '&amp;';
bgneal@312 5858
bgneal@312 5859 case '"':
bgneal@312 5860 return '&quot;';
bgneal@312 5861 }
bgneal@312 5862
bgneal@312 5863 return v;
bgneal@312 5864 });
bgneal@312 5865 },
bgneal@312 5866
bgneal@312 5867 getContent : function() {
bgneal@312 5868 return this.str;
bgneal@312 5869 },
bgneal@312 5870
bgneal@312 5871 _writeAttributesEnd : function(s) {
bgneal@312 5872 if (!this.inAttr)
bgneal@312 5873 return;
bgneal@312 5874
bgneal@312 5875 this.inAttr = false;
bgneal@312 5876
bgneal@312 5877 if (s && this.elementCount == this.count) {
bgneal@312 5878 this.writeRaw(' />');
bgneal@312 5879 return false;
bgneal@312 5880 }
bgneal@312 5881
bgneal@312 5882 this.writeRaw('>');
bgneal@312 5883
bgneal@312 5884 return true;
bgneal@312 5885 }
bgneal@312 5886 });
bgneal@312 5887 })(tinymce);
bgneal@312 5888
bgneal@312 5889 (function(tinymce) {
bgneal@312 5890 // Shorten names
bgneal@312 5891 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
bgneal@312 5892
bgneal@312 5893 function wildcardToRE(s) {
bgneal@312 5894 return s.replace(/([?+*])/g, '.$1');
bgneal@312 5895 };
bgneal@312 5896
bgneal@312 5897 tinymce.create('tinymce.dom.Serializer', {
bgneal@312 5898 Serializer : function(s) {
bgneal@312 5899 var t = this;
bgneal@312 5900
bgneal@312 5901 t.key = 0;
bgneal@312 5902 t.onPreProcess = new Dispatcher(t);
bgneal@312 5903 t.onPostProcess = new Dispatcher(t);
bgneal@312 5904
bgneal@312 5905 try {
bgneal@312 5906 t.writer = new tinymce.dom.XMLWriter();
bgneal@312 5907 } catch (ex) {
bgneal@312 5908 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
bgneal@312 5909 t.writer = new tinymce.dom.StringWriter();
bgneal@312 5910 }
bgneal@312 5911
bgneal@312 5912 // Default settings
bgneal@312 5913 t.settings = s = extend({
bgneal@312 5914 dom : tinymce.DOM,
bgneal@312 5915 valid_nodes : 0,
bgneal@312 5916 node_filter : 0,
bgneal@312 5917 attr_filter : 0,
bgneal@312 5918 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
bgneal@312 5919 closed : /^(br|hr|input|meta|img|link|param|area)$/,
bgneal@312 5920 entity_encoding : 'named',
bgneal@312 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@312 5922 valid_elements : '*[*]',
bgneal@312 5923 extended_valid_elements : 0,
bgneal@312 5924 invalid_elements : 0,
bgneal@312 5925 fix_table_elements : 1,
bgneal@312 5926 fix_list_elements : true,
bgneal@312 5927 fix_content_duplication : true,
bgneal@312 5928 convert_fonts_to_spans : false,
bgneal@312 5929 font_size_classes : 0,
bgneal@312 5930 apply_source_formatting : 0,
bgneal@312 5931 indent_mode : 'simple',
bgneal@312 5932 indent_char : '\t',
bgneal@312 5933 indent_levels : 1,
bgneal@312 5934 remove_linebreaks : 1,
bgneal@312 5935 remove_redundant_brs : 1,
bgneal@312 5936 element_format : 'xhtml'
bgneal@312 5937 }, s);
bgneal@312 5938
bgneal@312 5939 t.dom = s.dom;
bgneal@312 5940 t.schema = s.schema;
bgneal@312 5941
bgneal@312 5942 // Use raw entities if no entities are defined
bgneal@312 5943 if (s.entity_encoding == 'named' && !s.entities)
bgneal@312 5944 s.entity_encoding = 'raw';
bgneal@312 5945
bgneal@312 5946 if (s.remove_redundant_brs) {
bgneal@312 5947 t.onPostProcess.add(function(se, o) {
bgneal@312 5948 // Remove single BR at end of block elements since they get rendered
bgneal@312 5949 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
bgneal@312 5950 // Check if it's a single element
bgneal@312 5951 if (/^<br \/>\s*<\//.test(a))
bgneal@312 5952 return '</' + c + '>';
bgneal@312 5953
bgneal@312 5954 return a;
bgneal@312 5955 });
bgneal@312 5956 });
bgneal@312 5957 }
bgneal@312 5958
bgneal@312 5959 // Remove XHTML element endings i.e. produce crap :) XHTML is better
bgneal@312 5960 if (s.element_format == 'html') {
bgneal@312 5961 t.onPostProcess.add(function(se, o) {
bgneal@312 5962 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
bgneal@312 5963 });
bgneal@312 5964 }
bgneal@312 5965
bgneal@312 5966 if (s.fix_list_elements) {
bgneal@312 5967 t.onPreProcess.add(function(se, o) {
bgneal@312 5968 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
bgneal@312 5969
bgneal@312 5970 function prevNode(e, n) {
bgneal@312 5971 var a = n.split(','), i;
bgneal@312 5972
bgneal@312 5973 while ((e = e.previousSibling) != null) {
bgneal@312 5974 for (i=0; i<a.length; i++) {
bgneal@312 5975 if (e.nodeName == a[i])
bgneal@312 5976 return e;
bgneal@312 5977 }
bgneal@312 5978 }
bgneal@312 5979
bgneal@312 5980 return null;
bgneal@312 5981 };
bgneal@312 5982
bgneal@312 5983 for (x=0; x<a.length; x++) {
bgneal@312 5984 nl = t.dom.select(a[x], o.node);
bgneal@312 5985
bgneal@312 5986 for (i=0; i<nl.length; i++) {
bgneal@312 5987 n = nl[i];
bgneal@312 5988 p = n.parentNode;
bgneal@312 5989
bgneal@312 5990 if (r.test(p.nodeName)) {
bgneal@312 5991 np = prevNode(n, 'LI');
bgneal@312 5992
bgneal@312 5993 if (!np) {
bgneal@312 5994 np = t.dom.create('li');
bgneal@312 5995 np.innerHTML = '&nbsp;';
bgneal@312 5996 np.appendChild(n);
bgneal@312 5997 p.insertBefore(np, p.firstChild);
bgneal@312 5998 } else
bgneal@312 5999 np.appendChild(n);
bgneal@312 6000 }
bgneal@312 6001 }
bgneal@312 6002 }
bgneal@312 6003 });
bgneal@312 6004 }
bgneal@312 6005
bgneal@312 6006 if (s.fix_table_elements) {
bgneal@312 6007 t.onPreProcess.add(function(se, o) {
bgneal@312 6008 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
bgneal@312 6009 // so Opera users with an older version will have to live with less compaible output not much we can do here
bgneal@312 6010 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
bgneal@312 6011 each(t.dom.select('p table', o.node).reverse(), function(n) {
bgneal@312 6012 var parent = t.dom.getParent(n.parentNode, 'table,p');
bgneal@312 6013
bgneal@312 6014 if (parent.nodeName != 'TABLE') {
bgneal@312 6015 try {
bgneal@312 6016 t.dom.split(parent, n);
bgneal@312 6017 } catch (ex) {
bgneal@312 6018 // IE can sometimes fire an unknown runtime error so we just ignore it
bgneal@312 6019 }
bgneal@312 6020 }
bgneal@312 6021 });
bgneal@312 6022 }
bgneal@312 6023 });
bgneal@312 6024 }
bgneal@312 6025 },
bgneal@312 6026
bgneal@312 6027 setEntities : function(s) {
bgneal@312 6028 var t = this, a, i, l = {}, v;
bgneal@312 6029
bgneal@312 6030 // No need to setup more than once
bgneal@312 6031 if (t.entityLookup)
bgneal@312 6032 return;
bgneal@312 6033
bgneal@312 6034 // Build regex and lookup array
bgneal@312 6035 a = s.split(',');
bgneal@312 6036 for (i = 0; i < a.length; i += 2) {
bgneal@312 6037 v = a[i];
bgneal@312 6038
bgneal@312 6039 // Don't add default &amp; &quot; etc.
bgneal@312 6040 if (v == 34 || v == 38 || v == 60 || v == 62)
bgneal@312 6041 continue;
bgneal@312 6042
bgneal@312 6043 l[String.fromCharCode(a[i])] = a[i + 1];
bgneal@312 6044
bgneal@312 6045 v = parseInt(a[i]).toString(16);
bgneal@312 6046 }
bgneal@312 6047
bgneal@312 6048 t.entityLookup = l;
bgneal@312 6049 },
bgneal@312 6050
bgneal@312 6051 setRules : function(s) {
bgneal@312 6052 var t = this;
bgneal@312 6053
bgneal@312 6054 t._setup();
bgneal@312 6055 t.rules = {};
bgneal@312 6056 t.wildRules = [];
bgneal@312 6057 t.validElements = {};
bgneal@312 6058
bgneal@312 6059 return t.addRules(s);
bgneal@312 6060 },
bgneal@312 6061
bgneal@312 6062 addRules : function(s) {
bgneal@312 6063 var t = this, dr;
bgneal@312 6064
bgneal@312 6065 if (!s)
bgneal@312 6066 return;
bgneal@312 6067
bgneal@312 6068 t._setup();
bgneal@312 6069
bgneal@312 6070 each(s.split(','), function(s) {
bgneal@312 6071 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
bgneal@312 6072
bgneal@312 6073 // Extend with default rules
bgneal@312 6074 if (dr)
bgneal@312 6075 at = tinymce.extend([], dr.attribs);
bgneal@312 6076
bgneal@312 6077 // Parse attributes
bgneal@312 6078 if (p.length > 1) {
bgneal@312 6079 each(p[1].split('|'), function(s) {
bgneal@312 6080 var ar = {}, i;
bgneal@312 6081
bgneal@312 6082 at = at || [];
bgneal@312 6083
bgneal@312 6084 // Parse attribute rule
bgneal@312 6085 s = s.replace(/::/g, '~');
bgneal@312 6086 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
bgneal@312 6087 s[2] = s[2].replace(/~/g, ':');
bgneal@312 6088
bgneal@312 6089 // Add required attributes
bgneal@312 6090 if (s[1] == '!') {
bgneal@312 6091 ra = ra || [];
bgneal@312 6092 ra.push(s[2]);
bgneal@312 6093 }
bgneal@312 6094
bgneal@312 6095 // Remove inherited attributes
bgneal@312 6096 if (s[1] == '-') {
bgneal@312 6097 for (i = 0; i <at.length; i++) {
bgneal@312 6098 if (at[i].name == s[2]) {
bgneal@312 6099 at.splice(i, 1);
bgneal@312 6100 return;
bgneal@312 6101 }
bgneal@312 6102 }
bgneal@312 6103 }
bgneal@312 6104
bgneal@312 6105 switch (s[3]) {
bgneal@312 6106 // Add default attrib values
bgneal@312 6107 case '=':
bgneal@312 6108 ar.defaultVal = s[4] || '';
bgneal@312 6109 break;
bgneal@312 6110
bgneal@312 6111 // Add forced attrib values
bgneal@312 6112 case ':':
bgneal@312 6113 ar.forcedVal = s[4];
bgneal@312 6114 break;
bgneal@312 6115
bgneal@312 6116 // Add validation values
bgneal@312 6117 case '<':
bgneal@312 6118 ar.validVals = s[4].split('?');
bgneal@312 6119 break;
bgneal@312 6120 }
bgneal@312 6121
bgneal@312 6122 if (/[*.?]/.test(s[2])) {
bgneal@312 6123 wat = wat || [];
bgneal@312 6124 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
bgneal@312 6125 wat.push(ar);
bgneal@312 6126 } else {
bgneal@312 6127 ar.name = s[2];
bgneal@312 6128 at.push(ar);
bgneal@312 6129 }
bgneal@312 6130
bgneal@312 6131 va.push(s[2]);
bgneal@312 6132 });
bgneal@312 6133 }
bgneal@312 6134
bgneal@312 6135 // Handle element names
bgneal@312 6136 each(tn, function(s, i) {
bgneal@312 6137 var pr = s.charAt(0), x = 1, ru = {};
bgneal@312 6138
bgneal@312 6139 // Extend with default rule data
bgneal@312 6140 if (dr) {
bgneal@312 6141 if (dr.noEmpty)
bgneal@312 6142 ru.noEmpty = dr.noEmpty;
bgneal@312 6143
bgneal@312 6144 if (dr.fullEnd)
bgneal@312 6145 ru.fullEnd = dr.fullEnd;
bgneal@312 6146
bgneal@312 6147 if (dr.padd)
bgneal@312 6148 ru.padd = dr.padd;
bgneal@312 6149 }
bgneal@312 6150
bgneal@312 6151 // Handle prefixes
bgneal@312 6152 switch (pr) {
bgneal@312 6153 case '-':
bgneal@312 6154 ru.noEmpty = true;
bgneal@312 6155 break;
bgneal@312 6156
bgneal@312 6157 case '+':
bgneal@312 6158 ru.fullEnd = true;
bgneal@312 6159 break;
bgneal@312 6160
bgneal@312 6161 case '#':
bgneal@312 6162 ru.padd = true;
bgneal@312 6163 break;
bgneal@312 6164
bgneal@312 6165 default:
bgneal@312 6166 x = 0;
bgneal@312 6167 }
bgneal@312 6168
bgneal@312 6169 tn[i] = s = s.substring(x);
bgneal@312 6170 t.validElements[s] = 1;
bgneal@312 6171
bgneal@312 6172 // Add element name or element regex
bgneal@312 6173 if (/[*.?]/.test(tn[0])) {
bgneal@312 6174 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
bgneal@312 6175 t.wildRules = t.wildRules || {};
bgneal@312 6176 t.wildRules.push(ru);
bgneal@312 6177 } else {
bgneal@312 6178 ru.name = tn[0];
bgneal@312 6179
bgneal@312 6180 // Store away default rule
bgneal@312 6181 if (tn[0] == '@')
bgneal@312 6182 dr = ru;
bgneal@312 6183
bgneal@312 6184 t.rules[s] = ru;
bgneal@312 6185 }
bgneal@312 6186
bgneal@312 6187 ru.attribs = at;
bgneal@312 6188
bgneal@312 6189 if (ra)
bgneal@312 6190 ru.requiredAttribs = ra;
bgneal@312 6191
bgneal@312 6192 if (wat) {
bgneal@312 6193 // Build valid attributes regexp
bgneal@312 6194 s = '';
bgneal@312 6195 each(va, function(v) {
bgneal@312 6196 if (s)
bgneal@312 6197 s += '|';
bgneal@312 6198
bgneal@312 6199 s += '(' + wildcardToRE(v) + ')';
bgneal@312 6200 });
bgneal@312 6201 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
bgneal@312 6202 ru.wildAttribs = wat;
bgneal@312 6203 }
bgneal@312 6204 });
bgneal@312 6205 });
bgneal@312 6206
bgneal@312 6207 // Build valid elements regexp
bgneal@312 6208 s = '';
bgneal@312 6209 each(t.validElements, function(v, k) {
bgneal@312 6210 if (s)
bgneal@312 6211 s += '|';
bgneal@312 6212
bgneal@312 6213 if (k != '@')
bgneal@312 6214 s += k;
bgneal@312 6215 });
bgneal@312 6216 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
bgneal@312 6217
bgneal@312 6218 //console.debug(t.validElementsRE.toString());
bgneal@312 6219 //console.dir(t.rules);
bgneal@312 6220 //console.dir(t.wildRules);
bgneal@312 6221 },
bgneal@312 6222
bgneal@312 6223 findRule : function(n) {
bgneal@312 6224 var t = this, rl = t.rules, i, r;
bgneal@312 6225
bgneal@312 6226 t._setup();
bgneal@312 6227
bgneal@312 6228 // Exact match
bgneal@312 6229 r = rl[n];
bgneal@312 6230 if (r)
bgneal@312 6231 return r;
bgneal@312 6232
bgneal@312 6233 // Try wildcards
bgneal@312 6234 rl = t.wildRules;
bgneal@312 6235 for (i = 0; i < rl.length; i++) {
bgneal@312 6236 if (rl[i].nameRE.test(n))
bgneal@312 6237 return rl[i];
bgneal@312 6238 }
bgneal@312 6239
bgneal@312 6240 return null;
bgneal@312 6241 },
bgneal@312 6242
bgneal@312 6243 findAttribRule : function(ru, n) {
bgneal@312 6244 var i, wa = ru.wildAttribs;
bgneal@312 6245
bgneal@312 6246 for (i = 0; i < wa.length; i++) {
bgneal@312 6247 if (wa[i].nameRE.test(n))
bgneal@312 6248 return wa[i];
bgneal@312 6249 }
bgneal@312 6250
bgneal@312 6251 return null;
bgneal@312 6252 },
bgneal@312 6253
bgneal@312 6254 serialize : function(n, o) {
bgneal@312 6255 var h, t = this, doc, oldDoc, impl, selected;
bgneal@312 6256
bgneal@312 6257 t._setup();
bgneal@312 6258 o = o || {};
bgneal@312 6259 o.format = o.format || 'html';
bgneal@312 6260 t.processObj = o;
bgneal@312 6261
bgneal@312 6262 // IE looses the selected attribute on option elements so we need to store it
bgneal@312 6263 // See: http://support.microsoft.com/kb/829907
bgneal@312 6264 if (isIE) {
bgneal@312 6265 selected = [];
bgneal@312 6266 each(n.getElementsByTagName('option'), function(n) {
bgneal@312 6267 var v = t.dom.getAttrib(n, 'selected');
bgneal@312 6268
bgneal@312 6269 selected.push(v ? v : null);
bgneal@312 6270 });
bgneal@312 6271 }
bgneal@312 6272
bgneal@312 6273 n = n.cloneNode(true);
bgneal@312 6274
bgneal@312 6275 // IE looses the selected attribute on option elements so we need to restore it
bgneal@312 6276 if (isIE) {
bgneal@312 6277 each(n.getElementsByTagName('option'), function(n, i) {
bgneal@312 6278 t.dom.setAttrib(n, 'selected', selected[i]);
bgneal@312 6279 });
bgneal@312 6280 }
bgneal@312 6281
bgneal@312 6282 // Nodes needs to be attached to something in WebKit/Opera
bgneal@312 6283 // Older builds of Opera crashes if you attach the node to an document created dynamically
bgneal@312 6284 // and since we can't feature detect a crash we need to sniff the acutal build number
bgneal@312 6285 // This fix will make DOM ranges and make Sizzle happy!
bgneal@312 6286 impl = n.ownerDocument.implementation;
bgneal@312 6287 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
bgneal@312 6288 // Create an empty HTML document
bgneal@312 6289 doc = impl.createHTMLDocument("");
bgneal@312 6290
bgneal@312 6291 // Add the element or it's children if it's a body element to the new document
bgneal@312 6292 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
bgneal@312 6293 doc.body.appendChild(doc.importNode(node, true));
bgneal@312 6294 });
bgneal@312 6295
bgneal@312 6296 // Grab first child or body element for serialization
bgneal@312 6297 if (n.nodeName != 'BODY')
bgneal@312 6298 n = doc.body.firstChild;
bgneal@312 6299 else
bgneal@312 6300 n = doc.body;
bgneal@312 6301
bgneal@312 6302 // set the new document in DOMUtils so createElement etc works
bgneal@312 6303 oldDoc = t.dom.doc;
bgneal@312 6304 t.dom.doc = doc;
bgneal@312 6305 }
bgneal@312 6306
bgneal@312 6307 t.key = '' + (parseInt(t.key) + 1);
bgneal@312 6308
bgneal@312 6309 // Pre process
bgneal@312 6310 if (!o.no_events) {
bgneal@312 6311 o.node = n;
bgneal@312 6312 t.onPreProcess.dispatch(t, o);
bgneal@312 6313 }
bgneal@312 6314
bgneal@312 6315 // Serialize HTML DOM into a string
bgneal@312 6316 t.writer.reset();
bgneal@312 6317 t._info = o;
bgneal@312 6318 t._serializeNode(n, o.getInner);
bgneal@312 6319
bgneal@312 6320 // Post process
bgneal@312 6321 o.content = t.writer.getContent();
bgneal@312 6322
bgneal@312 6323 // Restore the old document if it was changed
bgneal@312 6324 if (oldDoc)
bgneal@312 6325 t.dom.doc = oldDoc;
bgneal@312 6326
bgneal@312 6327 if (!o.no_events)
bgneal@312 6328 t.onPostProcess.dispatch(t, o);
bgneal@312 6329
bgneal@312 6330 t._postProcess(o);
bgneal@312 6331 o.node = null;
bgneal@312 6332
bgneal@312 6333 return tinymce.trim(o.content);
bgneal@312 6334 },
bgneal@312 6335
bgneal@312 6336 // Internal functions
bgneal@312 6337
bgneal@312 6338 _postProcess : function(o) {
bgneal@312 6339 var t = this, s = t.settings, h = o.content, sc = [], p;
bgneal@312 6340
bgneal@312 6341 if (o.format == 'html') {
bgneal@312 6342 // Protect some elements
bgneal@312 6343 p = t._protect({
bgneal@312 6344 content : h,
bgneal@312 6345 patterns : [
bgneal@312 6346 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
bgneal@312 6347 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
bgneal@312 6348 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
bgneal@312 6349 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
bgneal@312 6350 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
bgneal@312 6351 ]
bgneal@312 6352 });
bgneal@312 6353
bgneal@312 6354 h = p.content;
bgneal@312 6355
bgneal@312 6356 // Entity encode
bgneal@312 6357 if (s.entity_encoding !== 'raw')
bgneal@312 6358 h = t._encode(h);
bgneal@312 6359
bgneal@312 6360 // Use BR instead of &nbsp; padded P elements inside editor and use <p>&nbsp;</p> outside editor
bgneal@312 6361 /* if (o.set)
bgneal@312 6362 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
bgneal@312 6363 else
bgneal@312 6364 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
bgneal@312 6365
bgneal@312 6366 // Since Gecko and Safari keeps whitespace in the DOM we need to
bgneal@312 6367 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
bgneal@312 6368 // This process is only done when getting contents out from the editor.
bgneal@312 6369 if (!o.set) {
bgneal@312 6370 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
bgneal@312 6371 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1>&#160;</p>' : '<p$1>&nbsp;</p>');
bgneal@312 6372
bgneal@312 6373 if (s.remove_linebreaks) {
bgneal@312 6374 h = h.replace(/\r?\n|\r/g, ' ');
bgneal@312 6375 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
bgneal@312 6376 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
bgneal@312 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@312 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@312 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@312 6380 }
bgneal@312 6381
bgneal@312 6382 // Simple indentation
bgneal@312 6383 if (s.apply_source_formatting && s.indent_mode == 'simple') {
bgneal@312 6384 // Add line breaks before and after block elements
bgneal@312 6385 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
bgneal@312 6386 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
bgneal@312 6387 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
bgneal@312 6388 h = h.replace(/\n\n/g, '\n');
bgneal@312 6389 }
bgneal@312 6390 }
bgneal@312 6391
bgneal@312 6392 h = t._unprotect(h, p);
bgneal@312 6393
bgneal@312 6394 // Restore CDATA sections
bgneal@312 6395 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
bgneal@312 6396
bgneal@312 6397 // Restore the \u00a0 character if raw mode is enabled
bgneal@312 6398 if (s.entity_encoding == 'raw')
bgneal@312 6399 h = h.replace(/<p>&nbsp;<\/p>|<p([^>]+)>&nbsp;<\/p>/g, '<p$1>\u00a0</p>');
bgneal@312 6400
bgneal@312 6401 // Restore noscript elements
bgneal@312 6402 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@312 6403 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
bgneal@312 6404 });
bgneal@312 6405 }
bgneal@312 6406
bgneal@312 6407 o.content = h;
bgneal@312 6408 },
bgneal@312 6409
bgneal@312 6410 _serializeNode : function(n, inner) {
bgneal@312 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@312 6412
bgneal@312 6413 if (!s.node_filter || s.node_filter(n)) {
bgneal@312 6414 switch (n.nodeType) {
bgneal@312 6415 case 1: // Element
bgneal@312 6416 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
bgneal@312 6417 return;
bgneal@312 6418
bgneal@312 6419 iv = keep = false;
bgneal@312 6420 hc = n.hasChildNodes();
bgneal@312 6421 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
bgneal@312 6422
bgneal@312 6423 // Get internal type
bgneal@312 6424 type = n.getAttribute('_mce_type');
bgneal@312 6425 if (type) {
bgneal@312 6426 if (!t._info.cleanup) {
bgneal@312 6427 iv = true;
bgneal@312 6428 return;
bgneal@312 6429 } else
bgneal@312 6430 keep = 1;
bgneal@312 6431 }
bgneal@312 6432
bgneal@312 6433 // Add correct prefix on IE
bgneal@312 6434 if (isIE) {
bgneal@312 6435 scopeName = n.scopeName;
bgneal@312 6436 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
bgneal@312 6437 nn = scopeName + ':' + nn;
bgneal@312 6438 }
bgneal@312 6439
bgneal@312 6440 // Remove mce prefix on IE needed for the abbr element
bgneal@312 6441 if (nn.indexOf('mce:') === 0)
bgneal@312 6442 nn = nn.substring(4);
bgneal@312 6443
bgneal@312 6444 // Check if valid
bgneal@312 6445 if (!keep) {
bgneal@312 6446 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
bgneal@312 6447 iv = true;
bgneal@312 6448 break;
bgneal@312 6449 }
bgneal@312 6450 }
bgneal@312 6451
bgneal@312 6452 if (isIE) {
bgneal@312 6453 // Fix IE content duplication (DOM can have multiple copies of the same node)
bgneal@312 6454 if (s.fix_content_duplication) {
bgneal@312 6455 if (n._mce_serialized == t.key)
bgneal@312 6456 return;
bgneal@312 6457
bgneal@312 6458 n._mce_serialized = t.key;
bgneal@312 6459 }
bgneal@312 6460
bgneal@312 6461 // IE sometimes adds a / infront of the node name
bgneal@312 6462 if (nn.charAt(0) == '/')
bgneal@312 6463 nn = nn.substring(1);
bgneal@312 6464 } else if (isGecko) {
bgneal@312 6465 // Ignore br elements
bgneal@312 6466 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
bgneal@312 6467 return;
bgneal@312 6468 }
bgneal@312 6469
bgneal@312 6470 // Check if valid child
bgneal@312 6471 if (s.validate_children) {
bgneal@312 6472 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
bgneal@312 6473 iv = true;
bgneal@312 6474 break;
bgneal@312 6475 }
bgneal@312 6476
bgneal@312 6477 t.elementName = nn;
bgneal@312 6478 }
bgneal@312 6479
bgneal@312 6480 ru = t.findRule(nn);
bgneal@312 6481
bgneal@312 6482 // No valid rule for this element could be found then skip it
bgneal@312 6483 if (!ru) {
bgneal@312 6484 iv = true;
bgneal@312 6485 break;
bgneal@312 6486 }
bgneal@312 6487
bgneal@312 6488 nn = ru.name || nn;
bgneal@312 6489 closed = s.closed.test(nn);
bgneal@312 6490
bgneal@312 6491 // Skip empty nodes or empty node name in IE
bgneal@312 6492 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
bgneal@312 6493 iv = true;
bgneal@312 6494 break;
bgneal@312 6495 }
bgneal@312 6496
bgneal@312 6497 // Check required
bgneal@312 6498 if (ru.requiredAttribs) {
bgneal@312 6499 a = ru.requiredAttribs;
bgneal@312 6500
bgneal@312 6501 for (i = a.length - 1; i >= 0; i--) {
bgneal@312 6502 if (this.dom.getAttrib(n, a[i]) !== '')
bgneal@312 6503 break;
bgneal@312 6504 }
bgneal@312 6505
bgneal@312 6506 // None of the required was there
bgneal@312 6507 if (i == -1) {
bgneal@312 6508 iv = true;
bgneal@312 6509 break;
bgneal@312 6510 }
bgneal@312 6511 }
bgneal@312 6512
bgneal@312 6513 w.writeStartElement(nn);
bgneal@312 6514
bgneal@312 6515 // Add ordered attributes
bgneal@312 6516 if (ru.attribs) {
bgneal@312 6517 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
bgneal@312 6518 a = at[i];
bgneal@312 6519 v = t._getAttrib(n, a);
bgneal@312 6520
bgneal@312 6521 if (v !== null)
bgneal@312 6522 w.writeAttribute(a.name, v);
bgneal@312 6523 }
bgneal@312 6524 }
bgneal@312 6525
bgneal@312 6526 // Add wild attributes
bgneal@312 6527 if (ru.validAttribsRE) {
bgneal@312 6528 at = t.dom.getAttribs(n);
bgneal@312 6529 for (i=at.length-1; i>-1; i--) {
bgneal@312 6530 no = at[i];
bgneal@312 6531
bgneal@312 6532 if (no.specified) {
bgneal@312 6533 a = no.nodeName.toLowerCase();
bgneal@312 6534
bgneal@312 6535 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
bgneal@312 6536 continue;
bgneal@312 6537
bgneal@312 6538 ar = t.findAttribRule(ru, a);
bgneal@312 6539 v = t._getAttrib(n, ar, a);
bgneal@312 6540
bgneal@312 6541 if (v !== null)
bgneal@312 6542 w.writeAttribute(a, v);
bgneal@312 6543 }
bgneal@312 6544 }
bgneal@312 6545 }
bgneal@312 6546
bgneal@312 6547 // Keep type attribute
bgneal@312 6548 if (type && keep)
bgneal@312 6549 w.writeAttribute('_mce_type', type);
bgneal@312 6550
bgneal@312 6551 // Write text from script
bgneal@312 6552 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
bgneal@312 6553 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
bgneal@312 6554 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
bgneal@312 6555 hc = false;
bgneal@312 6556 break;
bgneal@312 6557 }
bgneal@312 6558
bgneal@312 6559 // Padd empty nodes with a &nbsp;
bgneal@312 6560 if (ru.padd) {
bgneal@312 6561 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
bgneal@312 6562 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
bgneal@312 6563 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
bgneal@312 6564 w.writeText('\u00a0');
bgneal@312 6565 } else if (!hc)
bgneal@312 6566 w.writeText('\u00a0'); // No children then padd it
bgneal@312 6567 }
bgneal@312 6568
bgneal@312 6569 break;
bgneal@312 6570
bgneal@312 6571 case 3: // Text
bgneal@312 6572 // Check if valid child
bgneal@312 6573 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
bgneal@312 6574 return;
bgneal@312 6575
bgneal@312 6576 return w.writeText(n.nodeValue);
bgneal@312 6577
bgneal@312 6578 case 4: // CDATA
bgneal@312 6579 return w.writeCDATA(n.nodeValue);
bgneal@312 6580
bgneal@312 6581 case 8: // Comment
bgneal@312 6582 return w.writeComment(n.nodeValue);
bgneal@312 6583 }
bgneal@312 6584 } else if (n.nodeType == 1)
bgneal@312 6585 hc = n.hasChildNodes();
bgneal@312 6586
bgneal@312 6587 if (hc && !closed) {
bgneal@312 6588 cn = n.firstChild;
bgneal@312 6589
bgneal@312 6590 while (cn) {
bgneal@312 6591 t._serializeNode(cn);
bgneal@312 6592 t.elementName = nn;
bgneal@312 6593 cn = cn.nextSibling;
bgneal@312 6594 }
bgneal@312 6595 }
bgneal@312 6596
bgneal@312 6597 // Write element end
bgneal@312 6598 if (!iv) {
bgneal@312 6599 if (!closed)
bgneal@312 6600 w.writeFullEndElement();
bgneal@312 6601 else
bgneal@312 6602 w.writeEndElement();
bgneal@312 6603 }
bgneal@312 6604 },
bgneal@312 6605
bgneal@312 6606 _protect : function(o) {
bgneal@312 6607 var t = this;
bgneal@312 6608
bgneal@312 6609 o.items = o.items || [];
bgneal@312 6610
bgneal@312 6611 function enc(s) {
bgneal@312 6612 return s.replace(/[\r\n\\]/g, function(c) {
bgneal@312 6613 if (c === '\n')
bgneal@312 6614 return '\\n';
bgneal@312 6615 else if (c === '\\')
bgneal@312 6616 return '\\\\';
bgneal@312 6617
bgneal@312 6618 return '\\r';
bgneal@312 6619 });
bgneal@312 6620 };
bgneal@312 6621
bgneal@312 6622 function dec(s) {
bgneal@312 6623 return s.replace(/\\[\\rn]/g, function(c) {
bgneal@312 6624 if (c === '\\n')
bgneal@312 6625 return '\n';
bgneal@312 6626 else if (c === '\\\\')
bgneal@312 6627 return '\\';
bgneal@312 6628
bgneal@312 6629 return '\r';
bgneal@312 6630 });
bgneal@312 6631 };
bgneal@312 6632
bgneal@312 6633 each(o.patterns, function(p) {
bgneal@312 6634 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
bgneal@312 6635 b = dec(b);
bgneal@312 6636
bgneal@312 6637 if (p.encode)
bgneal@312 6638 b = t._encode(b);
bgneal@312 6639
bgneal@312 6640 o.items.push(b);
bgneal@312 6641 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
bgneal@312 6642 }));
bgneal@312 6643 });
bgneal@312 6644
bgneal@312 6645 return o;
bgneal@312 6646 },
bgneal@312 6647
bgneal@312 6648 _unprotect : function(h, o) {
bgneal@312 6649 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
bgneal@312 6650 return o.items[parseInt(b)];
bgneal@312 6651 });
bgneal@312 6652
bgneal@312 6653 o.items = [];
bgneal@312 6654
bgneal@312 6655 return h;
bgneal@312 6656 },
bgneal@312 6657
bgneal@312 6658 _encode : function(h) {
bgneal@312 6659 var t = this, s = t.settings, l;
bgneal@312 6660
bgneal@312 6661 // Entity encode
bgneal@312 6662 if (s.entity_encoding !== 'raw') {
bgneal@312 6663 if (s.entity_encoding.indexOf('named') != -1) {
bgneal@312 6664 t.setEntities(s.entities);
bgneal@312 6665 l = t.entityLookup;
bgneal@312 6666
bgneal@312 6667 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@312 6668 var v;
bgneal@312 6669
bgneal@312 6670 if (v = l[a])
bgneal@312 6671 a = '&' + v + ';';
bgneal@312 6672
bgneal@312 6673 return a;
bgneal@312 6674 });
bgneal@312 6675 }
bgneal@312 6676
bgneal@312 6677 if (s.entity_encoding.indexOf('numeric') != -1) {
bgneal@312 6678 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@312 6679 return '&#' + a.charCodeAt(0) + ';';
bgneal@312 6680 });
bgneal@312 6681 }
bgneal@312 6682 }
bgneal@312 6683
bgneal@312 6684 return h;
bgneal@312 6685 },
bgneal@312 6686
bgneal@312 6687 _setup : function() {
bgneal@312 6688 var t = this, s = this.settings;
bgneal@312 6689
bgneal@312 6690 if (t.done)
bgneal@312 6691 return;
bgneal@312 6692
bgneal@312 6693 t.done = 1;
bgneal@312 6694
bgneal@312 6695 t.setRules(s.valid_elements);
bgneal@312 6696 t.addRules(s.extended_valid_elements);
bgneal@312 6697
bgneal@312 6698 if (s.invalid_elements)
bgneal@312 6699 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
bgneal@312 6700
bgneal@312 6701 if (s.attrib_value_filter)
bgneal@312 6702 t.attribValueFilter = s.attribValueFilter;
bgneal@312 6703 },
bgneal@312 6704
bgneal@312 6705 _getAttrib : function(n, a, na) {
bgneal@312 6706 var i, v;
bgneal@312 6707
bgneal@312 6708 na = na || a.name;
bgneal@312 6709
bgneal@312 6710 if (a.forcedVal && (v = a.forcedVal)) {
bgneal@312 6711 if (v === '{$uid}')
bgneal@312 6712 return this.dom.uniqueId();
bgneal@312 6713
bgneal@312 6714 return v;
bgneal@312 6715 }
bgneal@312 6716
bgneal@312 6717 v = this.dom.getAttrib(n, na);
bgneal@312 6718
bgneal@312 6719 switch (na) {
bgneal@312 6720 case 'rowspan':
bgneal@312 6721 case 'colspan':
bgneal@312 6722 // Whats the point? Remove usless attribute value
bgneal@312 6723 if (v == '1')
bgneal@312 6724 v = '';
bgneal@312 6725
bgneal@312 6726 break;
bgneal@312 6727 }
bgneal@312 6728
bgneal@312 6729 if (this.attribValueFilter)
bgneal@312 6730 v = this.attribValueFilter(na, v, n);
bgneal@312 6731
bgneal@312 6732 if (a.validVals) {
bgneal@312 6733 for (i = a.validVals.length - 1; i >= 0; i--) {
bgneal@312 6734 if (v == a.validVals[i])
bgneal@312 6735 break;
bgneal@312 6736 }
bgneal@312 6737
bgneal@312 6738 if (i == -1)
bgneal@312 6739 return null;
bgneal@312 6740 }
bgneal@312 6741
bgneal@312 6742 if (v === '' && typeof(a.defaultVal) != 'undefined') {
bgneal@312 6743 v = a.defaultVal;
bgneal@312 6744
bgneal@312 6745 if (v === '{$uid}')
bgneal@312 6746 return this.dom.uniqueId();
bgneal@312 6747
bgneal@312 6748 return v;
bgneal@312 6749 } else {
bgneal@312 6750 // Remove internal mceItemXX classes when content is extracted from editor
bgneal@312 6751 if (na == 'class' && this.processObj.get)
bgneal@312 6752 v = v.replace(/\s?mceItem\w+\s?/g, '');
bgneal@312 6753 }
bgneal@312 6754
bgneal@312 6755 if (v === '')
bgneal@312 6756 return null;
bgneal@312 6757
bgneal@312 6758
bgneal@312 6759 return v;
bgneal@312 6760 }
bgneal@312 6761 });
bgneal@312 6762 })(tinymce);
bgneal@312 6763
bgneal@312 6764 (function(tinymce) {
bgneal@312 6765 tinymce.dom.ScriptLoader = function(settings) {
bgneal@312 6766 var QUEUED = 0,
bgneal@312 6767 LOADING = 1,
bgneal@312 6768 LOADED = 2,
bgneal@312 6769 states = {},
bgneal@312 6770 queue = [],
bgneal@312 6771 scriptLoadedCallbacks = {},
bgneal@312 6772 queueLoadedCallbacks = [],
bgneal@312 6773 loading = 0,
bgneal@312 6774 undefined;
bgneal@312 6775
bgneal@312 6776 function loadScript(url, callback) {
bgneal@312 6777 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
bgneal@312 6778
bgneal@312 6779 // Execute callback when script is loaded
bgneal@312 6780 function done() {
bgneal@312 6781 dom.remove(id);
bgneal@312 6782
bgneal@312 6783 if (elm)
bgneal@312 6784 elm.onreadystatechange = elm.onload = elm = null;
bgneal@312 6785
bgneal@312 6786 callback();
bgneal@312 6787 };
bgneal@312 6788
bgneal@312 6789 id = dom.uniqueId();
bgneal@312 6790
bgneal@312 6791 if (tinymce.isIE6) {
bgneal@312 6792 uri = new tinymce.util.URI(url);
bgneal@312 6793 loc = location;
bgneal@312 6794
bgneal@312 6795 // If script is from same domain and we
bgneal@312 6796 // use IE 6 then use XHR since it's more reliable
bgneal@312 6797 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
bgneal@312 6798 tinymce.util.XHR.send({
bgneal@312 6799 url : tinymce._addVer(uri.getURI()),
bgneal@312 6800 success : function(content) {
bgneal@312 6801 // Create new temp script element
bgneal@312 6802 var script = dom.create('script', {
bgneal@312 6803 type : 'text/javascript'
bgneal@312 6804 });
bgneal@312 6805
bgneal@312 6806 // Evaluate script in global scope
bgneal@312 6807 script.text = content;
bgneal@312 6808 document.getElementsByTagName('head')[0].appendChild(script);
bgneal@312 6809 dom.remove(script);
bgneal@312 6810
bgneal@312 6811 done();
bgneal@312 6812 }
bgneal@312 6813 });
bgneal@312 6814
bgneal@312 6815 return;
bgneal@312 6816 }
bgneal@312 6817 }
bgneal@312 6818
bgneal@312 6819 // Create new script element
bgneal@312 6820 elm = dom.create('script', {
bgneal@312 6821 id : id,
bgneal@312 6822 type : 'text/javascript',
bgneal@312 6823 src : tinymce._addVer(url)
bgneal@312 6824 });
bgneal@312 6825
bgneal@312 6826 // Add onload and readystate listeners
bgneal@312 6827 elm.onload = done;
bgneal@312 6828 elm.onreadystatechange = function() {
bgneal@312 6829 var state = elm.readyState;
bgneal@312 6830
bgneal@312 6831 // Loaded state is passed on IE 6 however there
bgneal@312 6832 // are known issues with this method but we can't use
bgneal@312 6833 // XHR in a cross domain loading
bgneal@312 6834 if (state == 'complete' || state == 'loaded')
bgneal@312 6835 done();
bgneal@312 6836 };
bgneal@312 6837
bgneal@312 6838 // Most browsers support this feature so we report errors
bgneal@312 6839 // for those at least to help users track their missing plugins etc
bgneal@312 6840 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
bgneal@312 6841 /*elm.onerror = function() {
bgneal@312 6842 alert('Failed to load: ' + url);
bgneal@312 6843 };*/
bgneal@312 6844
bgneal@312 6845 // Add script to document
bgneal@312 6846 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
bgneal@312 6847 };
bgneal@312 6848
bgneal@312 6849 this.isDone = function(url) {
bgneal@312 6850 return states[url] == LOADED;
bgneal@312 6851 };
bgneal@312 6852
bgneal@312 6853 this.markDone = function(url) {
bgneal@312 6854 states[url] = LOADED;
bgneal@312 6855 };
bgneal@312 6856
bgneal@312 6857 this.add = this.load = function(url, callback, scope) {
bgneal@312 6858 var item, state = states[url];
bgneal@312 6859
bgneal@312 6860 // Add url to load queue
bgneal@312 6861 if (state == undefined) {
bgneal@312 6862 queue.push(url);
bgneal@312 6863 states[url] = QUEUED;
bgneal@312 6864 }
bgneal@312 6865
bgneal@312 6866 if (callback) {
bgneal@312 6867 // Store away callback for later execution
bgneal@312 6868 if (!scriptLoadedCallbacks[url])
bgneal@312 6869 scriptLoadedCallbacks[url] = [];
bgneal@312 6870
bgneal@312 6871 scriptLoadedCallbacks[url].push({
bgneal@312 6872 func : callback,
bgneal@312 6873 scope : scope || this
bgneal@312 6874 });
bgneal@312 6875 }
bgneal@312 6876 };
bgneal@312 6877
bgneal@312 6878 this.loadQueue = function(callback, scope) {
bgneal@312 6879 this.loadScripts(queue, callback, scope);
bgneal@312 6880 };
bgneal@312 6881
bgneal@312 6882 this.loadScripts = function(scripts, callback, scope) {
bgneal@312 6883 var loadScripts;
bgneal@312 6884
bgneal@312 6885 function execScriptLoadedCallbacks(url) {
bgneal@312 6886 // Execute URL callback functions
bgneal@312 6887 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
bgneal@312 6888 callback.func.call(callback.scope);
bgneal@312 6889 });
bgneal@312 6890
bgneal@312 6891 scriptLoadedCallbacks[url] = undefined;
bgneal@312 6892 };
bgneal@312 6893
bgneal@312 6894 queueLoadedCallbacks.push({
bgneal@312 6895 func : callback,
bgneal@312 6896 scope : scope || this
bgneal@312 6897 });
bgneal@312 6898
bgneal@312 6899 loadScripts = function() {
bgneal@312 6900 var loadingScripts = tinymce.grep(scripts);
bgneal@312 6901
bgneal@312 6902 // Current scripts has been handled
bgneal@312 6903 scripts.length = 0;
bgneal@312 6904
bgneal@312 6905 // Load scripts that needs to be loaded
bgneal@312 6906 tinymce.each(loadingScripts, function(url) {
bgneal@312 6907 // Script is already loaded then execute script callbacks directly
bgneal@312 6908 if (states[url] == LOADED) {
bgneal@312 6909 execScriptLoadedCallbacks(url);
bgneal@312 6910 return;
bgneal@312 6911 }
bgneal@312 6912
bgneal@312 6913 // Is script not loading then start loading it
bgneal@312 6914 if (states[url] != LOADING) {
bgneal@312 6915 states[url] = LOADING;
bgneal@312 6916 loading++;
bgneal@312 6917
bgneal@312 6918 loadScript(url, function() {
bgneal@312 6919 states[url] = LOADED;
bgneal@312 6920 loading--;
bgneal@312 6921
bgneal@312 6922 execScriptLoadedCallbacks(url);
bgneal@312 6923
bgneal@312 6924 // Load more scripts if they where added by the recently loaded script
bgneal@312 6925 loadScripts();
bgneal@312 6926 });
bgneal@312 6927 }
bgneal@312 6928 });
bgneal@312 6929
bgneal@312 6930 // No scripts are currently loading then execute all pending queue loaded callbacks
bgneal@312 6931 if (!loading) {
bgneal@312 6932 tinymce.each(queueLoadedCallbacks, function(callback) {
bgneal@312 6933 callback.func.call(callback.scope);
bgneal@312 6934 });
bgneal@312 6935
bgneal@312 6936 queueLoadedCallbacks.length = 0;
bgneal@312 6937 }
bgneal@312 6938 };
bgneal@312 6939
bgneal@312 6940 loadScripts();
bgneal@312 6941 };
bgneal@312 6942 };
bgneal@312 6943
bgneal@312 6944 // Global script loader
bgneal@312 6945 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
bgneal@312 6946 })(tinymce);
bgneal@312 6947
bgneal@312 6948 tinymce.dom.TreeWalker = function(start_node, root_node) {
bgneal@312 6949 var node = start_node;
bgneal@312 6950
bgneal@312 6951 function findSibling(node, start_name, sibling_name, shallow) {
bgneal@312 6952 var sibling, parent;
bgneal@312 6953
bgneal@312 6954 if (node) {
bgneal@312 6955 // Walk into nodes if it has a start
bgneal@312 6956 if (!shallow && node[start_name])
bgneal@312 6957 return node[start_name];
bgneal@312 6958
bgneal@312 6959 // Return the sibling if it has one
bgneal@312 6960 if (node != root_node) {
bgneal@312 6961 sibling = node[sibling_name];
bgneal@312 6962 if (sibling)
bgneal@312 6963 return sibling;
bgneal@312 6964
bgneal@312 6965 // Walk up the parents to look for siblings
bgneal@312 6966 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
bgneal@312 6967 sibling = parent[sibling_name];
bgneal@312 6968 if (sibling)
bgneal@312 6969 return sibling;
bgneal@312 6970 }
bgneal@312 6971 }
bgneal@312 6972 }
bgneal@312 6973 };
bgneal@312 6974
bgneal@312 6975 this.current = function() {
bgneal@312 6976 return node;
bgneal@312 6977 };
bgneal@312 6978
bgneal@312 6979 this.next = function(shallow) {
bgneal@312 6980 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
bgneal@312 6981 };
bgneal@312 6982
bgneal@312 6983 this.prev = function(shallow) {
bgneal@312 6984 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
bgneal@312 6985 };
bgneal@312 6986 };
bgneal@312 6987
bgneal@312 6988 (function() {
bgneal@312 6989 var transitional = {};
bgneal@312 6990
bgneal@312 6991 function unpack(lookup, data) {
bgneal@312 6992 var key;
bgneal@312 6993
bgneal@312 6994 function replace(value) {
bgneal@312 6995 return value.replace(/[A-Z]+/g, function(key) {
bgneal@312 6996 return replace(lookup[key]);
bgneal@312 6997 });
bgneal@312 6998 };
bgneal@312 6999
bgneal@312 7000 // Unpack lookup
bgneal@312 7001 for (key in lookup) {
bgneal@312 7002 if (lookup.hasOwnProperty(key))
bgneal@312 7003 lookup[key] = replace(lookup[key]);
bgneal@312 7004 }
bgneal@312 7005
bgneal@312 7006 // Unpack and parse data into object map
bgneal@312 7007 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
bgneal@312 7008 var i, map = {};
bgneal@312 7009
bgneal@312 7010 children = children.split(/\|/);
bgneal@312 7011
bgneal@312 7012 for (i = children.length - 1; i >= 0; i--)
bgneal@312 7013 map[children[i]] = 1;
bgneal@312 7014
bgneal@312 7015 transitional[name] = map;
bgneal@312 7016 });
bgneal@312 7017 };
bgneal@312 7018
bgneal@312 7019 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
bgneal@312 7020 // we will later include the attributes here and use it as a default for valid elements but it
bgneal@312 7021 // requires us to rewrite the serializer engine
bgneal@312 7022 unpack({
bgneal@312 7023 Z : '#|H|K|N|O|P',
bgneal@312 7024 Y : '#|X|form|R|Q',
bgneal@312 7025 X : 'p|T|div|U|W|isindex|fieldset|table',
bgneal@312 7026 W : 'pre|hr|blockquote|address|center|noframes',
bgneal@312 7027 U : 'ul|ol|dl|menu|dir',
bgneal@312 7028 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
bgneal@312 7029 T : 'h1|h2|h3|h4|h5|h6',
bgneal@312 7030 ZB : '#|X|S|Q',
bgneal@312 7031 S : 'R|P',
bgneal@312 7032 ZA : '#|a|G|J|M|O|P',
bgneal@312 7033 R : '#|a|H|K|N|O',
bgneal@312 7034 Q : 'noscript|P',
bgneal@312 7035 P : 'ins|del|script',
bgneal@312 7036 O : 'input|select|textarea|label|button',
bgneal@312 7037 N : 'M|L',
bgneal@312 7038 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
bgneal@312 7039 L : 'sub|sup',
bgneal@312 7040 K : 'J|I',
bgneal@312 7041 J : 'tt|i|b|u|s|strike',
bgneal@312 7042 I : 'big|small|font|basefont',
bgneal@312 7043 H : 'G|F',
bgneal@312 7044 G : 'br|span|bdo',
bgneal@312 7045 F : 'object|applet|img|map|iframe'
bgneal@312 7046 }, 'script[]' +
bgneal@312 7047 'style[]' +
bgneal@312 7048 'object[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@312 7049 'param[]' +
bgneal@312 7050 'p[S]' +
bgneal@312 7051 'a[Z]' +
bgneal@312 7052 'br[]' +
bgneal@312 7053 'span[S]' +
bgneal@312 7054 'bdo[S]' +
bgneal@312 7055 'applet[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@312 7056 'h1[S]' +
bgneal@312 7057 'img[]' +
bgneal@312 7058 'map[X|form|Q|area]' +
bgneal@312 7059 'h2[S]' +
bgneal@312 7060 'iframe[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7061 'h3[S]' +
bgneal@312 7062 'tt[S]' +
bgneal@312 7063 'i[S]' +
bgneal@312 7064 'b[S]' +
bgneal@312 7065 'u[S]' +
bgneal@312 7066 's[S]' +
bgneal@312 7067 'strike[S]' +
bgneal@312 7068 'big[S]' +
bgneal@312 7069 'small[S]' +
bgneal@312 7070 'font[S]' +
bgneal@312 7071 'basefont[]' +
bgneal@312 7072 'em[S]' +
bgneal@312 7073 'strong[S]' +
bgneal@312 7074 'dfn[S]' +
bgneal@312 7075 'code[S]' +
bgneal@312 7076 'q[S]' +
bgneal@312 7077 'samp[S]' +
bgneal@312 7078 'kbd[S]' +
bgneal@312 7079 'var[S]' +
bgneal@312 7080 'cite[S]' +
bgneal@312 7081 'abbr[S]' +
bgneal@312 7082 'acronym[S]' +
bgneal@312 7083 'sub[S]' +
bgneal@312 7084 'sup[S]' +
bgneal@312 7085 'input[]' +
bgneal@312 7086 'select[optgroup|option]' +
bgneal@312 7087 'optgroup[option]' +
bgneal@312 7088 'option[]' +
bgneal@312 7089 'textarea[]' +
bgneal@312 7090 'label[S]' +
bgneal@312 7091 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
bgneal@312 7092 'h4[S]' +
bgneal@312 7093 'ins[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7094 'h5[S]' +
bgneal@312 7095 'del[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7096 'h6[S]' +
bgneal@312 7097 'div[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7098 'ul[li]' +
bgneal@312 7099 'li[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7100 'ol[li]' +
bgneal@312 7101 'dl[dt|dd]' +
bgneal@312 7102 'dt[S]' +
bgneal@312 7103 'dd[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7104 'menu[li]' +
bgneal@312 7105 'dir[li]' +
bgneal@312 7106 'pre[ZA]' +
bgneal@312 7107 'hr[]' +
bgneal@312 7108 'blockquote[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7109 'address[S|p]' +
bgneal@312 7110 'center[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7111 'noframes[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7112 'isindex[]' +
bgneal@312 7113 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
bgneal@312 7114 'legend[S]' +
bgneal@312 7115 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
bgneal@312 7116 'caption[S]' +
bgneal@312 7117 'col[]' +
bgneal@312 7118 'colgroup[col]' +
bgneal@312 7119 'thead[tr]' +
bgneal@312 7120 'tr[th|td]' +
bgneal@312 7121 'th[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7122 'form[#|X|a|H|K|N|O|Q]' +
bgneal@312 7123 'noscript[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7124 'td[#|X|form|a|H|K|N|O|Q]' +
bgneal@312 7125 'tfoot[tr]' +
bgneal@312 7126 'tbody[tr]' +
bgneal@312 7127 'area[]' +
bgneal@312 7128 'base[]' +
bgneal@312 7129 'body[#|X|form|a|H|K|N|O|Q]'
bgneal@312 7130 );
bgneal@312 7131
bgneal@312 7132 tinymce.dom.Schema = function() {
bgneal@312 7133 var t = this, elements = transitional;
bgneal@312 7134
bgneal@312 7135 t.isValid = function(name, child_name) {
bgneal@312 7136 var element = elements[name];
bgneal@312 7137
bgneal@312 7138 return !!(element && (!child_name || element[child_name]));
bgneal@312 7139 };
bgneal@312 7140 };
bgneal@312 7141 })();
bgneal@312 7142 (function(tinymce) {
bgneal@312 7143 tinymce.dom.RangeUtils = function(dom) {
bgneal@312 7144 var INVISIBLE_CHAR = '\uFEFF';
bgneal@312 7145
bgneal@312 7146 this.walk = function(rng, callback) {
bgneal@312 7147 var startContainer = rng.startContainer,
bgneal@312 7148 startOffset = rng.startOffset,
bgneal@312 7149 endContainer = rng.endContainer,
bgneal@312 7150 endOffset = rng.endOffset,
bgneal@312 7151 ancestor, startPoint,
bgneal@312 7152 endPoint, node, parent, siblings, nodes;
bgneal@312 7153
bgneal@312 7154 // Handle table cell selection the table plugin enables
bgneal@312 7155 // you to fake select table cells and perform formatting actions on them
bgneal@312 7156 nodes = dom.select('td.mceSelected,th.mceSelected');
bgneal@312 7157 if (nodes.length > 0) {
bgneal@312 7158 tinymce.each(nodes, function(node) {
bgneal@312 7159 callback([node]);
bgneal@312 7160 });
bgneal@312 7161
bgneal@312 7162 return;
bgneal@312 7163 }
bgneal@312 7164
bgneal@312 7165 function collectSiblings(node, name, end_node) {
bgneal@312 7166 var siblings = [];
bgneal@312 7167
bgneal@312 7168 for (; node && node != end_node; node = node[name])
bgneal@312 7169 siblings.push(node);
bgneal@312 7170
bgneal@312 7171 return siblings;
bgneal@312 7172 };
bgneal@312 7173
bgneal@312 7174 function findEndPoint(node, root) {
bgneal@312 7175 do {
bgneal@312 7176 if (node.parentNode == root)
bgneal@312 7177 return node;
bgneal@312 7178
bgneal@312 7179 node = node.parentNode;
bgneal@312 7180 } while(node);
bgneal@312 7181 };
bgneal@312 7182
bgneal@312 7183 function walkBoundary(start_node, end_node, next) {
bgneal@312 7184 var siblingName = next ? 'nextSibling' : 'previousSibling';
bgneal@312 7185
bgneal@312 7186 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
bgneal@312 7187 parent = node.parentNode;
bgneal@312 7188 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
bgneal@312 7189
bgneal@312 7190 if (siblings.length) {
bgneal@312 7191 if (!next)
bgneal@312 7192 siblings.reverse();
bgneal@312 7193
bgneal@312 7194 callback(siblings);
bgneal@312 7195 }
bgneal@312 7196 }
bgneal@312 7197 };
bgneal@312 7198
bgneal@312 7199 // If index based start position then resolve it
bgneal@312 7200 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
bgneal@312 7201 startContainer = startContainer.childNodes[startOffset];
bgneal@312 7202
bgneal@312 7203 // If index based end position then resolve it
bgneal@312 7204 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
bgneal@312 7205 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
bgneal@312 7206
bgneal@312 7207 // Find common ancestor and end points
bgneal@312 7208 ancestor = dom.findCommonAncestor(startContainer, endContainer);
bgneal@312 7209
bgneal@312 7210 // Same container
bgneal@312 7211 if (startContainer == endContainer)
bgneal@312 7212 return callback([startContainer]);
bgneal@312 7213
bgneal@312 7214 // Process left side
bgneal@312 7215 for (node = startContainer; node; node = node.parentNode) {
bgneal@312 7216 if (node == endContainer)
bgneal@312 7217 return walkBoundary(startContainer, ancestor, true);
bgneal@312 7218
bgneal@312 7219 if (node == ancestor)
bgneal@312 7220 break;
bgneal@312 7221 }
bgneal@312 7222
bgneal@312 7223 // Process right side
bgneal@312 7224 for (node = endContainer; node; node = node.parentNode) {
bgneal@312 7225 if (node == startContainer)
bgneal@312 7226 return walkBoundary(endContainer, ancestor);
bgneal@312 7227
bgneal@312 7228 if (node == ancestor)
bgneal@312 7229 break;
bgneal@312 7230 }
bgneal@312 7231
bgneal@312 7232 // Find start/end point
bgneal@312 7233 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
bgneal@312 7234 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
bgneal@312 7235
bgneal@312 7236 // Walk left leaf
bgneal@312 7237 walkBoundary(startContainer, startPoint, true);
bgneal@312 7238
bgneal@312 7239 // Walk the middle from start to end point
bgneal@312 7240 siblings = collectSiblings(
bgneal@312 7241 startPoint == startContainer ? startPoint : startPoint.nextSibling,
bgneal@312 7242 'nextSibling',
bgneal@312 7243 endPoint == endContainer ? endPoint.nextSibling : endPoint
bgneal@312 7244 );
bgneal@312 7245
bgneal@312 7246 if (siblings.length)
bgneal@312 7247 callback(siblings);
bgneal@312 7248
bgneal@312 7249 // Walk right leaf
bgneal@312 7250 walkBoundary(endContainer, endPoint);
bgneal@312 7251 };
bgneal@312 7252
bgneal@312 7253 /* this.split = function(rng) {
bgneal@312 7254 var startContainer = rng.startContainer,
bgneal@312 7255 startOffset = rng.startOffset,
bgneal@312 7256 endContainer = rng.endContainer,
bgneal@312 7257 endOffset = rng.endOffset;
bgneal@312 7258
bgneal@312 7259 function splitText(node, offset) {
bgneal@312 7260 if (offset == node.nodeValue.length)
bgneal@312 7261 node.appendData(INVISIBLE_CHAR);
bgneal@312 7262
bgneal@312 7263 node = node.splitText(offset);
bgneal@312 7264
bgneal@312 7265 if (node.nodeValue === INVISIBLE_CHAR)
bgneal@312 7266 node.nodeValue = '';
bgneal@312 7267
bgneal@312 7268 return node;
bgneal@312 7269 };
bgneal@312 7270
bgneal@312 7271 // Handle single text node
bgneal@312 7272 if (startContainer == endContainer) {
bgneal@312 7273 if (startContainer.nodeType == 3) {
bgneal@312 7274 if (startOffset != 0)
bgneal@312 7275 startContainer = endContainer = splitText(startContainer, startOffset);
bgneal@312 7276
bgneal@312 7277 if (endOffset - startOffset != startContainer.nodeValue.length)
bgneal@312 7278 splitText(startContainer, endOffset - startOffset);
bgneal@312 7279 }
bgneal@312 7280 } else {
bgneal@312 7281 // Split startContainer text node if needed
bgneal@312 7282 if (startContainer.nodeType == 3 && startOffset != 0) {
bgneal@312 7283 startContainer = splitText(startContainer, startOffset);
bgneal@312 7284 startOffset = 0;
bgneal@312 7285 }
bgneal@312 7286
bgneal@312 7287 // Split endContainer text node if needed
bgneal@312 7288 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
bgneal@312 7289 endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@312 7290 endOffset = endContainer.nodeValue.length;
bgneal@312 7291 }
bgneal@312 7292 }
bgneal@312 7293
bgneal@312 7294 return {
bgneal@312 7295 startContainer : startContainer,
bgneal@312 7296 startOffset : startOffset,
bgneal@312 7297 endContainer : endContainer,
bgneal@312 7298 endOffset : endOffset
bgneal@312 7299 };
bgneal@312 7300 };
bgneal@312 7301 */
bgneal@312 7302 };
bgneal@312 7303
bgneal@312 7304 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
bgneal@312 7305 if (rng1 && rng2) {
bgneal@312 7306 // Compare native IE ranges
bgneal@312 7307 if (rng1.item || rng1.duplicate) {
bgneal@312 7308 // Both are control ranges and the selected element matches
bgneal@312 7309 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
bgneal@312 7310 return true;
bgneal@312 7311
bgneal@312 7312 // Both are text ranges and the range matches
bgneal@312 7313 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
bgneal@312 7314 return true;
bgneal@312 7315 } else {
bgneal@312 7316 // Compare w3c ranges
bgneal@312 7317 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
bgneal@312 7318 }
bgneal@312 7319 }
bgneal@312 7320
bgneal@312 7321 return false;
bgneal@312 7322 };
bgneal@312 7323 })(tinymce);
bgneal@312 7324
bgneal@312 7325 (function(tinymce) {
bgneal@312 7326 // Shorten class names
bgneal@312 7327 var DOM = tinymce.DOM, is = tinymce.is;
bgneal@312 7328
bgneal@312 7329 tinymce.create('tinymce.ui.Control', {
bgneal@312 7330 Control : function(id, s) {
bgneal@312 7331 this.id = id;
bgneal@312 7332 this.settings = s = s || {};
bgneal@312 7333 this.rendered = false;
bgneal@312 7334 this.onRender = new tinymce.util.Dispatcher(this);
bgneal@312 7335 this.classPrefix = '';
bgneal@312 7336 this.scope = s.scope || this;
bgneal@312 7337 this.disabled = 0;
bgneal@312 7338 this.active = 0;
bgneal@312 7339 },
bgneal@312 7340
bgneal@312 7341 setDisabled : function(s) {
bgneal@312 7342 var e;
bgneal@312 7343
bgneal@312 7344 if (s != this.disabled) {
bgneal@312 7345 e = DOM.get(this.id);
bgneal@312 7346
bgneal@312 7347 // Add accessibility title for unavailable actions
bgneal@312 7348 if (e && this.settings.unavailable_prefix) {
bgneal@312 7349 if (s) {
bgneal@312 7350 this.prevTitle = e.title;
bgneal@312 7351 e.title = this.settings.unavailable_prefix + ": " + e.title;
bgneal@312 7352 } else
bgneal@312 7353 e.title = this.prevTitle;
bgneal@312 7354 }
bgneal@312 7355
bgneal@312 7356 this.setState('Disabled', s);
bgneal@312 7357 this.setState('Enabled', !s);
bgneal@312 7358 this.disabled = s;
bgneal@312 7359 }
bgneal@312 7360 },
bgneal@312 7361
bgneal@312 7362 isDisabled : function() {
bgneal@312 7363 return this.disabled;
bgneal@312 7364 },
bgneal@312 7365
bgneal@312 7366 setActive : function(s) {
bgneal@312 7367 if (s != this.active) {
bgneal@312 7368 this.setState('Active', s);
bgneal@312 7369 this.active = s;
bgneal@312 7370 }
bgneal@312 7371 },
bgneal@312 7372
bgneal@312 7373 isActive : function() {
bgneal@312 7374 return this.active;
bgneal@312 7375 },
bgneal@312 7376
bgneal@312 7377 setState : function(c, s) {
bgneal@312 7378 var n = DOM.get(this.id);
bgneal@312 7379
bgneal@312 7380 c = this.classPrefix + c;
bgneal@312 7381
bgneal@312 7382 if (s)
bgneal@312 7383 DOM.addClass(n, c);
bgneal@312 7384 else
bgneal@312 7385 DOM.removeClass(n, c);
bgneal@312 7386 },
bgneal@312 7387
bgneal@312 7388 isRendered : function() {
bgneal@312 7389 return this.rendered;
bgneal@312 7390 },
bgneal@312 7391
bgneal@312 7392 renderHTML : function() {
bgneal@312 7393 },
bgneal@312 7394
bgneal@312 7395 renderTo : function(n) {
bgneal@312 7396 DOM.setHTML(n, this.renderHTML());
bgneal@312 7397 },
bgneal@312 7398
bgneal@312 7399 postRender : function() {
bgneal@312 7400 var t = this, b;
bgneal@312 7401
bgneal@312 7402 // Set pending states
bgneal@312 7403 if (is(t.disabled)) {
bgneal@312 7404 b = t.disabled;
bgneal@312 7405 t.disabled = -1;
bgneal@312 7406 t.setDisabled(b);
bgneal@312 7407 }
bgneal@312 7408
bgneal@312 7409 if (is(t.active)) {
bgneal@312 7410 b = t.active;
bgneal@312 7411 t.active = -1;
bgneal@312 7412 t.setActive(b);
bgneal@312 7413 }
bgneal@312 7414 },
bgneal@312 7415
bgneal@312 7416 remove : function() {
bgneal@312 7417 DOM.remove(this.id);
bgneal@312 7418 this.destroy();
bgneal@312 7419 },
bgneal@312 7420
bgneal@312 7421 destroy : function() {
bgneal@312 7422 tinymce.dom.Event.clear(this.id);
bgneal@312 7423 }
bgneal@312 7424 });
bgneal@312 7425 })(tinymce);
bgneal@312 7426 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
bgneal@312 7427 Container : function(id, s) {
bgneal@312 7428 this.parent(id, s);
bgneal@312 7429
bgneal@312 7430 this.controls = [];
bgneal@312 7431
bgneal@312 7432 this.lookup = {};
bgneal@312 7433 },
bgneal@312 7434
bgneal@312 7435 add : function(c) {
bgneal@312 7436 this.lookup[c.id] = c;
bgneal@312 7437 this.controls.push(c);
bgneal@312 7438
bgneal@312 7439 return c;
bgneal@312 7440 },
bgneal@312 7441
bgneal@312 7442 get : function(n) {
bgneal@312 7443 return this.lookup[n];
bgneal@312 7444 }
bgneal@312 7445 });
bgneal@312 7446
bgneal@312 7447
bgneal@312 7448 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
bgneal@312 7449 Separator : function(id, s) {
bgneal@312 7450 this.parent(id, s);
bgneal@312 7451 this.classPrefix = 'mceSeparator';
bgneal@312 7452 },
bgneal@312 7453
bgneal@312 7454 renderHTML : function() {
bgneal@312 7455 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
bgneal@312 7456 }
bgneal@312 7457 });
bgneal@312 7458
bgneal@312 7459 (function(tinymce) {
bgneal@312 7460 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@312 7461
bgneal@312 7462 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
bgneal@312 7463 MenuItem : function(id, s) {
bgneal@312 7464 this.parent(id, s);
bgneal@312 7465 this.classPrefix = 'mceMenuItem';
bgneal@312 7466 },
bgneal@312 7467
bgneal@312 7468 setSelected : function(s) {
bgneal@312 7469 this.setState('Selected', s);
bgneal@312 7470 this.selected = s;
bgneal@312 7471 },
bgneal@312 7472
bgneal@312 7473 isSelected : function() {
bgneal@312 7474 return this.selected;
bgneal@312 7475 },
bgneal@312 7476
bgneal@312 7477 postRender : function() {
bgneal@312 7478 var t = this;
bgneal@312 7479
bgneal@312 7480 t.parent();
bgneal@312 7481
bgneal@312 7482 // Set pending state
bgneal@312 7483 if (is(t.selected))
bgneal@312 7484 t.setSelected(t.selected);
bgneal@312 7485 }
bgneal@312 7486 });
bgneal@312 7487 })(tinymce);
bgneal@312 7488
bgneal@312 7489 (function(tinymce) {
bgneal@312 7490 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@312 7491
bgneal@312 7492 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
bgneal@312 7493 Menu : function(id, s) {
bgneal@312 7494 var t = this;
bgneal@312 7495
bgneal@312 7496 t.parent(id, s);
bgneal@312 7497 t.items = {};
bgneal@312 7498 t.collapsed = false;
bgneal@312 7499 t.menuCount = 0;
bgneal@312 7500 t.onAddItem = new tinymce.util.Dispatcher(this);
bgneal@312 7501 },
bgneal@312 7502
bgneal@312 7503 expand : function(d) {
bgneal@312 7504 var t = this;
bgneal@312 7505
bgneal@312 7506 if (d) {
bgneal@312 7507 walk(t, function(o) {
bgneal@312 7508 if (o.expand)
bgneal@312 7509 o.expand();
bgneal@312 7510 }, 'items', t);
bgneal@312 7511 }
bgneal@312 7512
bgneal@312 7513 t.collapsed = false;
bgneal@312 7514 },
bgneal@312 7515
bgneal@312 7516 collapse : function(d) {
bgneal@312 7517 var t = this;
bgneal@312 7518
bgneal@312 7519 if (d) {
bgneal@312 7520 walk(t, function(o) {
bgneal@312 7521 if (o.collapse)
bgneal@312 7522 o.collapse();
bgneal@312 7523 }, 'items', t);
bgneal@312 7524 }
bgneal@312 7525
bgneal@312 7526 t.collapsed = true;
bgneal@312 7527 },
bgneal@312 7528
bgneal@312 7529 isCollapsed : function() {
bgneal@312 7530 return this.collapsed;
bgneal@312 7531 },
bgneal@312 7532
bgneal@312 7533 add : function(o) {
bgneal@312 7534 if (!o.settings)
bgneal@312 7535 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
bgneal@312 7536
bgneal@312 7537 this.onAddItem.dispatch(this, o);
bgneal@312 7538
bgneal@312 7539 return this.items[o.id] = o;
bgneal@312 7540 },
bgneal@312 7541
bgneal@312 7542 addSeparator : function() {
bgneal@312 7543 return this.add({separator : true});
bgneal@312 7544 },
bgneal@312 7545
bgneal@312 7546 addMenu : function(o) {
bgneal@312 7547 if (!o.collapse)
bgneal@312 7548 o = this.createMenu(o);
bgneal@312 7549
bgneal@312 7550 this.menuCount++;
bgneal@312 7551
bgneal@312 7552 return this.add(o);
bgneal@312 7553 },
bgneal@312 7554
bgneal@312 7555 hasMenus : function() {
bgneal@312 7556 return this.menuCount !== 0;
bgneal@312 7557 },
bgneal@312 7558
bgneal@312 7559 remove : function(o) {
bgneal@312 7560 delete this.items[o.id];
bgneal@312 7561 },
bgneal@312 7562
bgneal@312 7563 removeAll : function() {
bgneal@312 7564 var t = this;
bgneal@312 7565
bgneal@312 7566 walk(t, function(o) {
bgneal@312 7567 if (o.removeAll)
bgneal@312 7568 o.removeAll();
bgneal@312 7569 else
bgneal@312 7570 o.remove();
bgneal@312 7571
bgneal@312 7572 o.destroy();
bgneal@312 7573 }, 'items', t);
bgneal@312 7574
bgneal@312 7575 t.items = {};
bgneal@312 7576 },
bgneal@312 7577
bgneal@312 7578 createMenu : function(o) {
bgneal@312 7579 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
bgneal@312 7580
bgneal@312 7581 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
bgneal@312 7582
bgneal@312 7583 return m;
bgneal@312 7584 }
bgneal@312 7585 });
bgneal@312 7586 })(tinymce);
bgneal@312 7587 (function(tinymce) {
bgneal@312 7588 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
bgneal@312 7589
bgneal@312 7590 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
bgneal@312 7591 DropMenu : function(id, s) {
bgneal@312 7592 s = s || {};
bgneal@312 7593 s.container = s.container || DOM.doc.body;
bgneal@312 7594 s.offset_x = s.offset_x || 0;
bgneal@312 7595 s.offset_y = s.offset_y || 0;
bgneal@312 7596 s.vp_offset_x = s.vp_offset_x || 0;
bgneal@312 7597 s.vp_offset_y = s.vp_offset_y || 0;
bgneal@312 7598
bgneal@312 7599 if (is(s.icons) && !s.icons)
bgneal@312 7600 s['class'] += ' mceNoIcons';
bgneal@312 7601
bgneal@312 7602 this.parent(id, s);
bgneal@312 7603 this.onShowMenu = new tinymce.util.Dispatcher(this);
bgneal@312 7604 this.onHideMenu = new tinymce.util.Dispatcher(this);
bgneal@312 7605 this.classPrefix = 'mceMenu';
bgneal@312 7606 },
bgneal@312 7607
bgneal@312 7608 createMenu : function(s) {
bgneal@312 7609 var t = this, cs = t.settings, m;
bgneal@312 7610
bgneal@312 7611 s.container = s.container || cs.container;
bgneal@312 7612 s.parent = t;
bgneal@312 7613 s.constrain = s.constrain || cs.constrain;
bgneal@312 7614 s['class'] = s['class'] || cs['class'];
bgneal@312 7615 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
bgneal@312 7616 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
bgneal@312 7617 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
bgneal@312 7618
bgneal@312 7619 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
bgneal@312 7620
bgneal@312 7621 return m;
bgneal@312 7622 },
bgneal@312 7623
bgneal@312 7624 update : function() {
bgneal@312 7625 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
bgneal@312 7626
bgneal@312 7627 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
bgneal@312 7628 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
bgneal@312 7629
bgneal@312 7630 if (!DOM.boxModel)
bgneal@312 7631 t.element.setStyles({width : tw + 2, height : th + 2});
bgneal@312 7632 else
bgneal@312 7633 t.element.setStyles({width : tw, height : th});
bgneal@312 7634
bgneal@312 7635 if (s.max_width)
bgneal@312 7636 DOM.setStyle(co, 'width', tw);
bgneal@312 7637
bgneal@312 7638 if (s.max_height) {
bgneal@312 7639 DOM.setStyle(co, 'height', th);
bgneal@312 7640
bgneal@312 7641 if (tb.clientHeight < s.max_height)
bgneal@312 7642 DOM.setStyle(co, 'overflow', 'hidden');
bgneal@312 7643 }
bgneal@312 7644 },
bgneal@312 7645
bgneal@312 7646 showMenu : function(x, y, px) {
bgneal@312 7647 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
bgneal@312 7648
bgneal@312 7649 t.collapse(1);
bgneal@312 7650
bgneal@312 7651 if (t.isMenuVisible)
bgneal@312 7652 return;
bgneal@312 7653
bgneal@312 7654 if (!t.rendered) {
bgneal@312 7655 co = DOM.add(t.settings.container, t.renderNode());
bgneal@312 7656
bgneal@312 7657 each(t.items, function(o) {
bgneal@312 7658 o.postRender();
bgneal@312 7659 });
bgneal@312 7660
bgneal@312 7661 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@312 7662 } else
bgneal@312 7663 co = DOM.get('menu_' + t.id);
bgneal@312 7664
bgneal@312 7665 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
bgneal@312 7666 if (!tinymce.isOpera)
bgneal@312 7667 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
bgneal@312 7668
bgneal@312 7669 DOM.show(co);
bgneal@312 7670 t.update();
bgneal@312 7671
bgneal@312 7672 x += s.offset_x || 0;
bgneal@312 7673 y += s.offset_y || 0;
bgneal@312 7674 vp.w -= 4;
bgneal@312 7675 vp.h -= 4;
bgneal@312 7676
bgneal@312 7677 // Move inside viewport if not submenu
bgneal@312 7678 if (s.constrain) {
bgneal@312 7679 w = co.clientWidth - ot;
bgneal@312 7680 h = co.clientHeight - ot;
bgneal@312 7681 mx = vp.x + vp.w;
bgneal@312 7682 my = vp.y + vp.h;
bgneal@312 7683
bgneal@312 7684 if ((x + s.vp_offset_x + w) > mx)
bgneal@312 7685 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
bgneal@312 7686
bgneal@312 7687 if ((y + s.vp_offset_y + h) > my)
bgneal@312 7688 y = Math.max(0, (my - s.vp_offset_y) - h);
bgneal@312 7689 }
bgneal@312 7690
bgneal@312 7691 DOM.setStyles(co, {left : x , top : y});
bgneal@312 7692 t.element.update();
bgneal@312 7693
bgneal@312 7694 t.isMenuVisible = 1;
bgneal@312 7695 t.mouseClickFunc = Event.add(co, 'click', function(e) {
bgneal@312 7696 var m;
bgneal@312 7697
bgneal@312 7698 e = e.target;
bgneal@312 7699
bgneal@312 7700 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@312 7701 m = t.items[e.id];
bgneal@312 7702
bgneal@312 7703 if (m.isDisabled())
bgneal@312 7704 return;
bgneal@312 7705
bgneal@312 7706 dm = t;
bgneal@312 7707
bgneal@312 7708 while (dm) {
bgneal@312 7709 if (dm.hideMenu)
bgneal@312 7710 dm.hideMenu();
bgneal@312 7711
bgneal@312 7712 dm = dm.settings.parent;
bgneal@312 7713 }
bgneal@312 7714
bgneal@312 7715 if (m.settings.onclick)
bgneal@312 7716 m.settings.onclick(e);
bgneal@312 7717
bgneal@312 7718 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@312 7719 }
bgneal@312 7720 });
bgneal@312 7721
bgneal@312 7722 if (t.hasMenus()) {
bgneal@312 7723 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
bgneal@312 7724 var m, r, mi;
bgneal@312 7725
bgneal@312 7726 e = e.target;
bgneal@312 7727 if (e && (e = DOM.getParent(e, 'tr'))) {
bgneal@312 7728 m = t.items[e.id];
bgneal@312 7729
bgneal@312 7730 if (t.lastMenu)
bgneal@312 7731 t.lastMenu.collapse(1);
bgneal@312 7732
bgneal@312 7733 if (m.isDisabled())
bgneal@312 7734 return;
bgneal@312 7735
bgneal@312 7736 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@312 7737 //p = DOM.getPos(s.container);
bgneal@312 7738 r = DOM.getRect(e);
bgneal@312 7739 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
bgneal@312 7740 t.lastMenu = m;
bgneal@312 7741 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
bgneal@312 7742 }
bgneal@312 7743 }
bgneal@312 7744 });
bgneal@312 7745 }
bgneal@312 7746
bgneal@312 7747 t.onShowMenu.dispatch(t);
bgneal@312 7748
bgneal@312 7749 if (s.keyboard_focus) {
bgneal@312 7750 Event.add(co, 'keydown', t._keyHandler, t);
bgneal@312 7751 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
bgneal@312 7752 t._focusIdx = 0;
bgneal@312 7753 }
bgneal@312 7754 },
bgneal@312 7755
bgneal@312 7756 hideMenu : function(c) {
bgneal@312 7757 var t = this, co = DOM.get('menu_' + t.id), e;
bgneal@312 7758
bgneal@312 7759 if (!t.isMenuVisible)
bgneal@312 7760 return;
bgneal@312 7761
bgneal@312 7762 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@312 7763 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@312 7764 Event.remove(co, 'keydown', t._keyHandler);
bgneal@312 7765 DOM.hide(co);
bgneal@312 7766 t.isMenuVisible = 0;
bgneal@312 7767
bgneal@312 7768 if (!c)
bgneal@312 7769 t.collapse(1);
bgneal@312 7770
bgneal@312 7771 if (t.element)
bgneal@312 7772 t.element.hide();
bgneal@312 7773
bgneal@312 7774 if (e = DOM.get(t.id))
bgneal@312 7775 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
bgneal@312 7776
bgneal@312 7777 t.onHideMenu.dispatch(t);
bgneal@312 7778 },
bgneal@312 7779
bgneal@312 7780 add : function(o) {
bgneal@312 7781 var t = this, co;
bgneal@312 7782
bgneal@312 7783 o = t.parent(o);
bgneal@312 7784
bgneal@312 7785 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
bgneal@312 7786 t._add(DOM.select('tbody', co)[0], o);
bgneal@312 7787
bgneal@312 7788 return o;
bgneal@312 7789 },
bgneal@312 7790
bgneal@312 7791 collapse : function(d) {
bgneal@312 7792 this.parent(d);
bgneal@312 7793 this.hideMenu(1);
bgneal@312 7794 },
bgneal@312 7795
bgneal@312 7796 remove : function(o) {
bgneal@312 7797 DOM.remove(o.id);
bgneal@312 7798 this.destroy();
bgneal@312 7799
bgneal@312 7800 return this.parent(o);
bgneal@312 7801 },
bgneal@312 7802
bgneal@312 7803 destroy : function() {
bgneal@312 7804 var t = this, co = DOM.get('menu_' + t.id);
bgneal@312 7805
bgneal@312 7806 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@312 7807 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@312 7808
bgneal@312 7809 if (t.element)
bgneal@312 7810 t.element.remove();
bgneal@312 7811
bgneal@312 7812 DOM.remove(co);
bgneal@312 7813 },
bgneal@312 7814
bgneal@312 7815 renderNode : function() {
bgneal@312 7816 var t = this, s = t.settings, n, tb, co, w;
bgneal@312 7817
bgneal@312 7818 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
bgneal@312 7819 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
bgneal@312 7820 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@312 7821
bgneal@312 7822 if (s.menu_line)
bgneal@312 7823 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
bgneal@312 7824
bgneal@312 7825 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
bgneal@312 7826 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
bgneal@312 7827 tb = DOM.add(n, 'tbody');
bgneal@312 7828
bgneal@312 7829 each(t.items, function(o) {
bgneal@312 7830 t._add(tb, o);
bgneal@312 7831 });
bgneal@312 7832
bgneal@312 7833 t.rendered = true;
bgneal@312 7834
bgneal@312 7835 return w;
bgneal@312 7836 },
bgneal@312 7837
bgneal@312 7838 // Internal functions
bgneal@312 7839
bgneal@312 7840 _keyHandler : function(e) {
bgneal@312 7841 var t = this, kc = e.keyCode;
bgneal@312 7842
bgneal@312 7843 function focus(d) {
bgneal@312 7844 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
bgneal@312 7845
bgneal@312 7846 if (e) {
bgneal@312 7847 t._focusIdx = i;
bgneal@312 7848 e.focus();
bgneal@312 7849 }
bgneal@312 7850 };
bgneal@312 7851
bgneal@312 7852 switch (kc) {
bgneal@312 7853 case 38:
bgneal@312 7854 focus(-1); // Select first link
bgneal@312 7855 return;
bgneal@312 7856
bgneal@312 7857 case 40:
bgneal@312 7858 focus(1);
bgneal@312 7859 return;
bgneal@312 7860
bgneal@312 7861 case 13:
bgneal@312 7862 return;
bgneal@312 7863
bgneal@312 7864 case 27:
bgneal@312 7865 return this.hideMenu();
bgneal@312 7866 }
bgneal@312 7867 },
bgneal@312 7868
bgneal@312 7869 _add : function(tb, o) {
bgneal@312 7870 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
bgneal@312 7871
bgneal@312 7872 if (s.separator) {
bgneal@312 7873 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
bgneal@312 7874 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
bgneal@312 7875
bgneal@312 7876 if (n = ro.previousSibling)
bgneal@312 7877 DOM.addClass(n, 'mceLast');
bgneal@312 7878
bgneal@312 7879 return;
bgneal@312 7880 }
bgneal@312 7881
bgneal@312 7882 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
bgneal@312 7883 n = it = DOM.add(n, 'td');
bgneal@312 7884 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
bgneal@312 7885
bgneal@312 7886 DOM.addClass(it, s['class']);
bgneal@312 7887 // n = DOM.add(n, 'span', {'class' : 'item'});
bgneal@312 7888
bgneal@312 7889 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
bgneal@312 7890
bgneal@312 7891 if (s.icon_src)
bgneal@312 7892 DOM.add(ic, 'img', {src : s.icon_src});
bgneal@312 7893
bgneal@312 7894 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
bgneal@312 7895
bgneal@312 7896 if (o.settings.style)
bgneal@312 7897 DOM.setAttrib(n, 'style', o.settings.style);
bgneal@312 7898
bgneal@312 7899 if (tb.childNodes.length == 1)
bgneal@312 7900 DOM.addClass(ro, 'mceFirst');
bgneal@312 7901
bgneal@312 7902 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
bgneal@312 7903 DOM.addClass(ro, 'mceFirst');
bgneal@312 7904
bgneal@312 7905 if (o.collapse)
bgneal@312 7906 DOM.addClass(ro, cp + 'ItemSub');
bgneal@312 7907
bgneal@312 7908 if (n = ro.previousSibling)
bgneal@312 7909 DOM.removeClass(n, 'mceLast');
bgneal@312 7910
bgneal@312 7911 DOM.addClass(ro, 'mceLast');
bgneal@312 7912 }
bgneal@312 7913 });
bgneal@312 7914 })(tinymce);
bgneal@312 7915 (function(tinymce) {
bgneal@312 7916 var DOM = tinymce.DOM;
bgneal@312 7917
bgneal@312 7918 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
bgneal@312 7919 Button : function(id, s) {
bgneal@312 7920 this.parent(id, s);
bgneal@312 7921 this.classPrefix = 'mceButton';
bgneal@312 7922 },
bgneal@312 7923
bgneal@312 7924 renderHTML : function() {
bgneal@312 7925 var cp = this.classPrefix, s = this.settings, h, l;
bgneal@312 7926
bgneal@312 7927 l = DOM.encode(s.label || '');
bgneal@312 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@312 7929
bgneal@312 7930 if (s.image)
bgneal@312 7931 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
bgneal@312 7932 else
bgneal@312 7933 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
bgneal@312 7934
bgneal@312 7935 return h;
bgneal@312 7936 },
bgneal@312 7937
bgneal@312 7938 postRender : function() {
bgneal@312 7939 var t = this, s = t.settings;
bgneal@312 7940
bgneal@312 7941 tinymce.dom.Event.add(t.id, 'click', function(e) {
bgneal@312 7942 if (!t.isDisabled())
bgneal@312 7943 return s.onclick.call(s.scope, e);
bgneal@312 7944 });
bgneal@312 7945 }
bgneal@312 7946 });
bgneal@312 7947 })(tinymce);
bgneal@312 7948
bgneal@312 7949 (function(tinymce) {
bgneal@312 7950 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@312 7951
bgneal@312 7952 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
bgneal@312 7953 ListBox : function(id, s) {
bgneal@312 7954 var t = this;
bgneal@312 7955
bgneal@312 7956 t.parent(id, s);
bgneal@312 7957
bgneal@312 7958 t.items = [];
bgneal@312 7959
bgneal@312 7960 t.onChange = new Dispatcher(t);
bgneal@312 7961
bgneal@312 7962 t.onPostRender = new Dispatcher(t);
bgneal@312 7963
bgneal@312 7964 t.onAdd = new Dispatcher(t);
bgneal@312 7965
bgneal@312 7966 t.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@312 7967
bgneal@312 7968 t.classPrefix = 'mceListBox';
bgneal@312 7969 },
bgneal@312 7970
bgneal@312 7971 select : function(va) {
bgneal@312 7972 var t = this, fv, f;
bgneal@312 7973
bgneal@312 7974 if (va == undefined)
bgneal@312 7975 return t.selectByIndex(-1);
bgneal@312 7976
bgneal@312 7977 // Is string or number make function selector
bgneal@312 7978 if (va && va.call)
bgneal@312 7979 f = va;
bgneal@312 7980 else {
bgneal@312 7981 f = function(v) {
bgneal@312 7982 return v == va;
bgneal@312 7983 };
bgneal@312 7984 }
bgneal@312 7985
bgneal@312 7986 // Do we need to do something?
bgneal@312 7987 if (va != t.selectedValue) {
bgneal@312 7988 // Find item
bgneal@312 7989 each(t.items, function(o, i) {
bgneal@312 7990 if (f(o.value)) {
bgneal@312 7991 fv = 1;
bgneal@312 7992 t.selectByIndex(i);
bgneal@312 7993 return false;
bgneal@312 7994 }
bgneal@312 7995 });
bgneal@312 7996
bgneal@312 7997 if (!fv)
bgneal@312 7998 t.selectByIndex(-1);
bgneal@312 7999 }
bgneal@312 8000 },
bgneal@312 8001
bgneal@312 8002 selectByIndex : function(idx) {
bgneal@312 8003 var t = this, e, o;
bgneal@312 8004
bgneal@312 8005 if (idx != t.selectedIndex) {
bgneal@312 8006 e = DOM.get(t.id + '_text');
bgneal@312 8007 o = t.items[idx];
bgneal@312 8008
bgneal@312 8009 if (o) {
bgneal@312 8010 t.selectedValue = o.value;
bgneal@312 8011 t.selectedIndex = idx;
bgneal@312 8012 DOM.setHTML(e, DOM.encode(o.title));
bgneal@312 8013 DOM.removeClass(e, 'mceTitle');
bgneal@312 8014 } else {
bgneal@312 8015 DOM.setHTML(e, DOM.encode(t.settings.title));
bgneal@312 8016 DOM.addClass(e, 'mceTitle');
bgneal@312 8017 t.selectedValue = t.selectedIndex = null;
bgneal@312 8018 }
bgneal@312 8019
bgneal@312 8020 e = 0;
bgneal@312 8021 }
bgneal@312 8022 },
bgneal@312 8023
bgneal@312 8024 add : function(n, v, o) {
bgneal@312 8025 var t = this;
bgneal@312 8026
bgneal@312 8027 o = o || {};
bgneal@312 8028 o = tinymce.extend(o, {
bgneal@312 8029 title : n,
bgneal@312 8030 value : v
bgneal@312 8031 });
bgneal@312 8032
bgneal@312 8033 t.items.push(o);
bgneal@312 8034 t.onAdd.dispatch(t, o);
bgneal@312 8035 },
bgneal@312 8036
bgneal@312 8037 getLength : function() {
bgneal@312 8038 return this.items.length;
bgneal@312 8039 },
bgneal@312 8040
bgneal@312 8041 renderHTML : function() {
bgneal@312 8042 var h = '', t = this, s = t.settings, cp = t.classPrefix;
bgneal@312 8043
bgneal@312 8044 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
bgneal@312 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@312 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@312 8047 h += '</tr></tbody></table>';
bgneal@312 8048
bgneal@312 8049 return h;
bgneal@312 8050 },
bgneal@312 8051
bgneal@312 8052 showMenu : function() {
bgneal@312 8053 var t = this, p1, p2, e = DOM.get(this.id), m;
bgneal@312 8054
bgneal@312 8055 if (t.isDisabled() || t.items.length == 0)
bgneal@312 8056 return;
bgneal@312 8057
bgneal@312 8058 if (t.menu && t.menu.isMenuVisible)
bgneal@312 8059 return t.hideMenu();
bgneal@312 8060
bgneal@312 8061 if (!t.isMenuRendered) {
bgneal@312 8062 t.renderMenu();
bgneal@312 8063 t.isMenuRendered = true;
bgneal@312 8064 }
bgneal@312 8065
bgneal@312 8066 p1 = DOM.getPos(this.settings.menu_container);
bgneal@312 8067 p2 = DOM.getPos(e);
bgneal@312 8068
bgneal@312 8069 m = t.menu;
bgneal@312 8070 m.settings.offset_x = p2.x;
bgneal@312 8071 m.settings.offset_y = p2.y;
bgneal@312 8072 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
bgneal@312 8073
bgneal@312 8074 // Select in menu
bgneal@312 8075 if (t.oldID)
bgneal@312 8076 m.items[t.oldID].setSelected(0);
bgneal@312 8077
bgneal@312 8078 each(t.items, function(o) {
bgneal@312 8079 if (o.value === t.selectedValue) {
bgneal@312 8080 m.items[o.id].setSelected(1);
bgneal@312 8081 t.oldID = o.id;
bgneal@312 8082 }
bgneal@312 8083 });
bgneal@312 8084
bgneal@312 8085 m.showMenu(0, e.clientHeight);
bgneal@312 8086
bgneal@312 8087 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8088 DOM.addClass(t.id, t.classPrefix + 'Selected');
bgneal@312 8089
bgneal@312 8090 //DOM.get(t.id + '_text').focus();
bgneal@312 8091 },
bgneal@312 8092
bgneal@312 8093 hideMenu : function(e) {
bgneal@312 8094 var t = this;
bgneal@312 8095
bgneal@312 8096 if (t.menu && t.menu.isMenuVisible) {
bgneal@312 8097 // Prevent double toogles by canceling the mouse click event to the button
bgneal@312 8098 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
bgneal@312 8099 return;
bgneal@312 8100
bgneal@312 8101 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@312 8102 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@312 8103 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8104 t.menu.hideMenu();
bgneal@312 8105 }
bgneal@312 8106 }
bgneal@312 8107 },
bgneal@312 8108
bgneal@312 8109 renderMenu : function() {
bgneal@312 8110 var t = this, m;
bgneal@312 8111
bgneal@312 8112 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@312 8113 menu_line : 1,
bgneal@312 8114 'class' : t.classPrefix + 'Menu mceNoIcons',
bgneal@312 8115 max_width : 150,
bgneal@312 8116 max_height : 150
bgneal@312 8117 });
bgneal@312 8118
bgneal@312 8119 m.onHideMenu.add(t.hideMenu, t);
bgneal@312 8120
bgneal@312 8121 m.add({
bgneal@312 8122 title : t.settings.title,
bgneal@312 8123 'class' : 'mceMenuItemTitle',
bgneal@312 8124 onclick : function() {
bgneal@312 8125 if (t.settings.onselect('') !== false)
bgneal@312 8126 t.select(''); // Must be runned after
bgneal@312 8127 }
bgneal@312 8128 });
bgneal@312 8129
bgneal@312 8130 each(t.items, function(o) {
bgneal@312 8131 // No value then treat it as a title
bgneal@312 8132 if (o.value === undefined) {
bgneal@312 8133 m.add({
bgneal@312 8134 title : o.title,
bgneal@312 8135 'class' : 'mceMenuItemTitle',
bgneal@312 8136 onclick : function() {
bgneal@312 8137 if (t.settings.onselect('') !== false)
bgneal@312 8138 t.select(''); // Must be runned after
bgneal@312 8139 }
bgneal@312 8140 });
bgneal@312 8141 } else {
bgneal@312 8142 o.id = DOM.uniqueId();
bgneal@312 8143 o.onclick = function() {
bgneal@312 8144 if (t.settings.onselect(o.value) !== false)
bgneal@312 8145 t.select(o.value); // Must be runned after
bgneal@312 8146 };
bgneal@312 8147
bgneal@312 8148 m.add(o);
bgneal@312 8149 }
bgneal@312 8150 });
bgneal@312 8151
bgneal@312 8152 t.onRenderMenu.dispatch(t, m);
bgneal@312 8153 t.menu = m;
bgneal@312 8154 },
bgneal@312 8155
bgneal@312 8156 postRender : function() {
bgneal@312 8157 var t = this, cp = t.classPrefix;
bgneal@312 8158
bgneal@312 8159 Event.add(t.id, 'click', t.showMenu, t);
bgneal@312 8160 Event.add(t.id + '_text', 'focus', function() {
bgneal@312 8161 if (!t._focused) {
bgneal@312 8162 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
bgneal@312 8163 var idx = -1, v, kc = e.keyCode;
bgneal@312 8164
bgneal@312 8165 // Find current index
bgneal@312 8166 each(t.items, function(v, i) {
bgneal@312 8167 if (t.selectedValue == v.value)
bgneal@312 8168 idx = i;
bgneal@312 8169 });
bgneal@312 8170
bgneal@312 8171 // Move up/down
bgneal@312 8172 if (kc == 38)
bgneal@312 8173 v = t.items[idx - 1];
bgneal@312 8174 else if (kc == 40)
bgneal@312 8175 v = t.items[idx + 1];
bgneal@312 8176 else if (kc == 13) {
bgneal@312 8177 // Fake select on enter
bgneal@312 8178 v = t.selectedValue;
bgneal@312 8179 t.selectedValue = null; // Needs to be null to fake change
bgneal@312 8180 t.settings.onselect(v);
bgneal@312 8181 return Event.cancel(e);
bgneal@312 8182 }
bgneal@312 8183
bgneal@312 8184 if (v) {
bgneal@312 8185 t.hideMenu();
bgneal@312 8186 t.select(v.value);
bgneal@312 8187 }
bgneal@312 8188 });
bgneal@312 8189 }
bgneal@312 8190
bgneal@312 8191 t._focused = 1;
bgneal@312 8192 });
bgneal@312 8193 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
bgneal@312 8194
bgneal@312 8195 // Old IE doesn't have hover on all elements
bgneal@312 8196 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@312 8197 Event.add(t.id, 'mouseover', function() {
bgneal@312 8198 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@312 8199 DOM.addClass(t.id, cp + 'Hover');
bgneal@312 8200 });
bgneal@312 8201
bgneal@312 8202 Event.add(t.id, 'mouseout', function() {
bgneal@312 8203 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@312 8204 DOM.removeClass(t.id, cp + 'Hover');
bgneal@312 8205 });
bgneal@312 8206 }
bgneal@312 8207
bgneal@312 8208 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@312 8209 },
bgneal@312 8210
bgneal@312 8211 destroy : function() {
bgneal@312 8212 this.parent();
bgneal@312 8213
bgneal@312 8214 Event.clear(this.id + '_text');
bgneal@312 8215 Event.clear(this.id + '_open');
bgneal@312 8216 }
bgneal@312 8217 });
bgneal@312 8218 })(tinymce);
bgneal@312 8219 (function(tinymce) {
bgneal@312 8220 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@312 8221
bgneal@312 8222 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
bgneal@312 8223 NativeListBox : function(id, s) {
bgneal@312 8224 this.parent(id, s);
bgneal@312 8225 this.classPrefix = 'mceNativeListBox';
bgneal@312 8226 },
bgneal@312 8227
bgneal@312 8228 setDisabled : function(s) {
bgneal@312 8229 DOM.get(this.id).disabled = s;
bgneal@312 8230 },
bgneal@312 8231
bgneal@312 8232 isDisabled : function() {
bgneal@312 8233 return DOM.get(this.id).disabled;
bgneal@312 8234 },
bgneal@312 8235
bgneal@312 8236 select : function(va) {
bgneal@312 8237 var t = this, fv, f;
bgneal@312 8238
bgneal@312 8239 if (va == undefined)
bgneal@312 8240 return t.selectByIndex(-1);
bgneal@312 8241
bgneal@312 8242 // Is string or number make function selector
bgneal@312 8243 if (va && va.call)
bgneal@312 8244 f = va;
bgneal@312 8245 else {
bgneal@312 8246 f = function(v) {
bgneal@312 8247 return v == va;
bgneal@312 8248 };
bgneal@312 8249 }
bgneal@312 8250
bgneal@312 8251 // Do we need to do something?
bgneal@312 8252 if (va != t.selectedValue) {
bgneal@312 8253 // Find item
bgneal@312 8254 each(t.items, function(o, i) {
bgneal@312 8255 if (f(o.value)) {
bgneal@312 8256 fv = 1;
bgneal@312 8257 t.selectByIndex(i);
bgneal@312 8258 return false;
bgneal@312 8259 }
bgneal@312 8260 });
bgneal@312 8261
bgneal@312 8262 if (!fv)
bgneal@312 8263 t.selectByIndex(-1);
bgneal@312 8264 }
bgneal@312 8265 },
bgneal@312 8266
bgneal@312 8267 selectByIndex : function(idx) {
bgneal@312 8268 DOM.get(this.id).selectedIndex = idx + 1;
bgneal@312 8269 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
bgneal@312 8270 },
bgneal@312 8271
bgneal@312 8272 add : function(n, v, a) {
bgneal@312 8273 var o, t = this;
bgneal@312 8274
bgneal@312 8275 a = a || {};
bgneal@312 8276 a.value = v;
bgneal@312 8277
bgneal@312 8278 if (t.isRendered())
bgneal@312 8279 DOM.add(DOM.get(this.id), 'option', a, n);
bgneal@312 8280
bgneal@312 8281 o = {
bgneal@312 8282 title : n,
bgneal@312 8283 value : v,
bgneal@312 8284 attribs : a
bgneal@312 8285 };
bgneal@312 8286
bgneal@312 8287 t.items.push(o);
bgneal@312 8288 t.onAdd.dispatch(t, o);
bgneal@312 8289 },
bgneal@312 8290
bgneal@312 8291 getLength : function() {
bgneal@312 8292 return this.items.length;
bgneal@312 8293 },
bgneal@312 8294
bgneal@312 8295 renderHTML : function() {
bgneal@312 8296 var h, t = this;
bgneal@312 8297
bgneal@312 8298 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
bgneal@312 8299
bgneal@312 8300 each(t.items, function(it) {
bgneal@312 8301 h += DOM.createHTML('option', {value : it.value}, it.title);
bgneal@312 8302 });
bgneal@312 8303
bgneal@312 8304 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
bgneal@312 8305
bgneal@312 8306 return h;
bgneal@312 8307 },
bgneal@312 8308
bgneal@312 8309 postRender : function() {
bgneal@312 8310 var t = this, ch;
bgneal@312 8311
bgneal@312 8312 t.rendered = true;
bgneal@312 8313
bgneal@312 8314 function onChange(e) {
bgneal@312 8315 var v = t.items[e.target.selectedIndex - 1];
bgneal@312 8316
bgneal@312 8317 if (v && (v = v.value)) {
bgneal@312 8318 t.onChange.dispatch(t, v);
bgneal@312 8319
bgneal@312 8320 if (t.settings.onselect)
bgneal@312 8321 t.settings.onselect(v);
bgneal@312 8322 }
bgneal@312 8323 };
bgneal@312 8324
bgneal@312 8325 Event.add(t.id, 'change', onChange);
bgneal@312 8326
bgneal@312 8327 // Accessibility keyhandler
bgneal@312 8328 Event.add(t.id, 'keydown', function(e) {
bgneal@312 8329 var bf;
bgneal@312 8330
bgneal@312 8331 Event.remove(t.id, 'change', ch);
bgneal@312 8332
bgneal@312 8333 bf = Event.add(t.id, 'blur', function() {
bgneal@312 8334 Event.add(t.id, 'change', onChange);
bgneal@312 8335 Event.remove(t.id, 'blur', bf);
bgneal@312 8336 });
bgneal@312 8337
bgneal@312 8338 if (e.keyCode == 13 || e.keyCode == 32) {
bgneal@312 8339 onChange(e);
bgneal@312 8340 return Event.cancel(e);
bgneal@312 8341 }
bgneal@312 8342 });
bgneal@312 8343
bgneal@312 8344 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@312 8345 }
bgneal@312 8346 });
bgneal@312 8347 })(tinymce);
bgneal@312 8348 (function(tinymce) {
bgneal@312 8349 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@312 8350
bgneal@312 8351 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
bgneal@312 8352 MenuButton : function(id, s) {
bgneal@312 8353 this.parent(id, s);
bgneal@312 8354
bgneal@312 8355 this.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@312 8356
bgneal@312 8357 s.menu_container = s.menu_container || DOM.doc.body;
bgneal@312 8358 },
bgneal@312 8359
bgneal@312 8360 showMenu : function() {
bgneal@312 8361 var t = this, p1, p2, e = DOM.get(t.id), m;
bgneal@312 8362
bgneal@312 8363 if (t.isDisabled())
bgneal@312 8364 return;
bgneal@312 8365
bgneal@312 8366 if (!t.isMenuRendered) {
bgneal@312 8367 t.renderMenu();
bgneal@312 8368 t.isMenuRendered = true;
bgneal@312 8369 }
bgneal@312 8370
bgneal@312 8371 if (t.isMenuVisible)
bgneal@312 8372 return t.hideMenu();
bgneal@312 8373
bgneal@312 8374 p1 = DOM.getPos(t.settings.menu_container);
bgneal@312 8375 p2 = DOM.getPos(e);
bgneal@312 8376
bgneal@312 8377 m = t.menu;
bgneal@312 8378 m.settings.offset_x = p2.x;
bgneal@312 8379 m.settings.offset_y = p2.y;
bgneal@312 8380 m.settings.vp_offset_x = p2.x;
bgneal@312 8381 m.settings.vp_offset_y = p2.y;
bgneal@312 8382 m.settings.keyboard_focus = t._focused;
bgneal@312 8383 m.showMenu(0, e.clientHeight);
bgneal@312 8384
bgneal@312 8385 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8386 t.setState('Selected', 1);
bgneal@312 8387
bgneal@312 8388 t.isMenuVisible = 1;
bgneal@312 8389 },
bgneal@312 8390
bgneal@312 8391 renderMenu : function() {
bgneal@312 8392 var t = this, m;
bgneal@312 8393
bgneal@312 8394 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@312 8395 menu_line : 1,
bgneal@312 8396 'class' : this.classPrefix + 'Menu',
bgneal@312 8397 icons : t.settings.icons
bgneal@312 8398 });
bgneal@312 8399
bgneal@312 8400 m.onHideMenu.add(t.hideMenu, t);
bgneal@312 8401
bgneal@312 8402 t.onRenderMenu.dispatch(t, m);
bgneal@312 8403 t.menu = m;
bgneal@312 8404 },
bgneal@312 8405
bgneal@312 8406 hideMenu : function(e) {
bgneal@312 8407 var t = this;
bgneal@312 8408
bgneal@312 8409 // Prevent double toogles by canceling the mouse click event to the button
bgneal@312 8410 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
bgneal@312 8411 return;
bgneal@312 8412
bgneal@312 8413 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@312 8414 t.setState('Selected', 0);
bgneal@312 8415 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8416 if (t.menu)
bgneal@312 8417 t.menu.hideMenu();
bgneal@312 8418 }
bgneal@312 8419
bgneal@312 8420 t.isMenuVisible = 0;
bgneal@312 8421 },
bgneal@312 8422
bgneal@312 8423 postRender : function() {
bgneal@312 8424 var t = this, s = t.settings;
bgneal@312 8425
bgneal@312 8426 Event.add(t.id, 'click', function() {
bgneal@312 8427 if (!t.isDisabled()) {
bgneal@312 8428 if (s.onclick)
bgneal@312 8429 s.onclick(t.value);
bgneal@312 8430
bgneal@312 8431 t.showMenu();
bgneal@312 8432 }
bgneal@312 8433 });
bgneal@312 8434 }
bgneal@312 8435 });
bgneal@312 8436 })(tinymce);
bgneal@312 8437
bgneal@312 8438 (function(tinymce) {
bgneal@312 8439 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@312 8440
bgneal@312 8441 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
bgneal@312 8442 SplitButton : function(id, s) {
bgneal@312 8443 this.parent(id, s);
bgneal@312 8444 this.classPrefix = 'mceSplitButton';
bgneal@312 8445 },
bgneal@312 8446
bgneal@312 8447 renderHTML : function() {
bgneal@312 8448 var h, t = this, s = t.settings, h1;
bgneal@312 8449
bgneal@312 8450 h = '<tbody><tr>';
bgneal@312 8451
bgneal@312 8452 if (s.image)
bgneal@312 8453 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
bgneal@312 8454 else
bgneal@312 8455 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
bgneal@312 8456
bgneal@312 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@312 8458
bgneal@312 8459 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
bgneal@312 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@312 8461
bgneal@312 8462 h += '</tr></tbody>';
bgneal@312 8463
bgneal@312 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@312 8465 },
bgneal@312 8466
bgneal@312 8467 postRender : function() {
bgneal@312 8468 var t = this, s = t.settings;
bgneal@312 8469
bgneal@312 8470 if (s.onclick) {
bgneal@312 8471 Event.add(t.id + '_action', 'click', function() {
bgneal@312 8472 if (!t.isDisabled())
bgneal@312 8473 s.onclick(t.value);
bgneal@312 8474 });
bgneal@312 8475 }
bgneal@312 8476
bgneal@312 8477 Event.add(t.id + '_open', 'click', t.showMenu, t);
bgneal@312 8478 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
bgneal@312 8479 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
bgneal@312 8480
bgneal@312 8481 // Old IE doesn't have hover on all elements
bgneal@312 8482 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@312 8483 Event.add(t.id, 'mouseover', function() {
bgneal@312 8484 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@312 8485 DOM.addClass(t.id, 'mceSplitButtonHover');
bgneal@312 8486 });
bgneal@312 8487
bgneal@312 8488 Event.add(t.id, 'mouseout', function() {
bgneal@312 8489 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@312 8490 DOM.removeClass(t.id, 'mceSplitButtonHover');
bgneal@312 8491 });
bgneal@312 8492 }
bgneal@312 8493 },
bgneal@312 8494
bgneal@312 8495 destroy : function() {
bgneal@312 8496 this.parent();
bgneal@312 8497
bgneal@312 8498 Event.clear(this.id + '_action');
bgneal@312 8499 Event.clear(this.id + '_open');
bgneal@312 8500 }
bgneal@312 8501 });
bgneal@312 8502 })(tinymce);
bgneal@312 8503
bgneal@312 8504 (function(tinymce) {
bgneal@312 8505 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
bgneal@312 8506
bgneal@312 8507 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
bgneal@312 8508 ColorSplitButton : function(id, s) {
bgneal@312 8509 var t = this;
bgneal@312 8510
bgneal@312 8511 t.parent(id, s);
bgneal@312 8512
bgneal@312 8513 t.settings = s = tinymce.extend({
bgneal@312 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@312 8515 grid_width : 8,
bgneal@312 8516 default_color : '#888888'
bgneal@312 8517 }, t.settings);
bgneal@312 8518
bgneal@312 8519 t.onShowMenu = new tinymce.util.Dispatcher(t);
bgneal@312 8520
bgneal@312 8521 t.onHideMenu = new tinymce.util.Dispatcher(t);
bgneal@312 8522
bgneal@312 8523 t.value = s.default_color;
bgneal@312 8524 },
bgneal@312 8525
bgneal@312 8526 showMenu : function() {
bgneal@312 8527 var t = this, r, p, e, p2;
bgneal@312 8528
bgneal@312 8529 if (t.isDisabled())
bgneal@312 8530 return;
bgneal@312 8531
bgneal@312 8532 if (!t.isMenuRendered) {
bgneal@312 8533 t.renderMenu();
bgneal@312 8534 t.isMenuRendered = true;
bgneal@312 8535 }
bgneal@312 8536
bgneal@312 8537 if (t.isMenuVisible)
bgneal@312 8538 return t.hideMenu();
bgneal@312 8539
bgneal@312 8540 e = DOM.get(t.id);
bgneal@312 8541 DOM.show(t.id + '_menu');
bgneal@312 8542 DOM.addClass(e, 'mceSplitButtonSelected');
bgneal@312 8543 p2 = DOM.getPos(e);
bgneal@312 8544 DOM.setStyles(t.id + '_menu', {
bgneal@312 8545 left : p2.x,
bgneal@312 8546 top : p2.y + e.clientHeight,
bgneal@312 8547 zIndex : 200000
bgneal@312 8548 });
bgneal@312 8549 e = 0;
bgneal@312 8550
bgneal@312 8551 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8552 t.onShowMenu.dispatch(t);
bgneal@312 8553
bgneal@312 8554 if (t._focused) {
bgneal@312 8555 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
bgneal@312 8556 if (e.keyCode == 27)
bgneal@312 8557 t.hideMenu();
bgneal@312 8558 });
bgneal@312 8559
bgneal@312 8560 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
bgneal@312 8561 }
bgneal@312 8562
bgneal@312 8563 t.isMenuVisible = 1;
bgneal@312 8564 },
bgneal@312 8565
bgneal@312 8566 hideMenu : function(e) {
bgneal@312 8567 var t = this;
bgneal@312 8568
bgneal@312 8569 // Prevent double toogles by canceling the mouse click event to the button
bgneal@312 8570 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
bgneal@312 8571 return;
bgneal@312 8572
bgneal@312 8573 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
bgneal@312 8574 DOM.removeClass(t.id, 'mceSplitButtonSelected');
bgneal@312 8575 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 8576 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
bgneal@312 8577 DOM.hide(t.id + '_menu');
bgneal@312 8578 }
bgneal@312 8579
bgneal@312 8580 t.onHideMenu.dispatch(t);
bgneal@312 8581
bgneal@312 8582 t.isMenuVisible = 0;
bgneal@312 8583 },
bgneal@312 8584
bgneal@312 8585 renderMenu : function() {
bgneal@312 8586 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
bgneal@312 8587
bgneal@312 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@312 8589 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
bgneal@312 8590 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
bgneal@312 8591
bgneal@312 8592 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
bgneal@312 8593 tb = DOM.add(n, 'tbody');
bgneal@312 8594
bgneal@312 8595 // Generate color grid
bgneal@312 8596 i = 0;
bgneal@312 8597 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
bgneal@312 8598 c = c.replace(/^#/, '');
bgneal@312 8599
bgneal@312 8600 if (!i--) {
bgneal@312 8601 tr = DOM.add(tb, 'tr');
bgneal@312 8602 i = s.grid_width - 1;
bgneal@312 8603 }
bgneal@312 8604
bgneal@312 8605 n = DOM.add(tr, 'td');
bgneal@312 8606
bgneal@312 8607 n = DOM.add(n, 'a', {
bgneal@312 8608 href : 'javascript:;',
bgneal@312 8609 style : {
bgneal@312 8610 backgroundColor : '#' + c
bgneal@312 8611 },
bgneal@312 8612 _mce_color : '#' + c
bgneal@312 8613 });
bgneal@312 8614 });
bgneal@312 8615
bgneal@312 8616 if (s.more_colors_func) {
bgneal@312 8617 n = DOM.add(tb, 'tr');
bgneal@312 8618 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
bgneal@312 8619 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
bgneal@312 8620
bgneal@312 8621 Event.add(n, 'click', function(e) {
bgneal@312 8622 s.more_colors_func.call(s.more_colors_scope || this);
bgneal@312 8623 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@312 8624 });
bgneal@312 8625 }
bgneal@312 8626
bgneal@312 8627 DOM.addClass(m, 'mceColorSplitMenu');
bgneal@312 8628
bgneal@312 8629 Event.add(t.id + '_menu', 'click', function(e) {
bgneal@312 8630 var c;
bgneal@312 8631
bgneal@312 8632 e = e.target;
bgneal@312 8633
bgneal@312 8634 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
bgneal@312 8635 t.setColor(c);
bgneal@312 8636
bgneal@312 8637 return Event.cancel(e); // Prevent IE auto save warning
bgneal@312 8638 });
bgneal@312 8639
bgneal@312 8640 return w;
bgneal@312 8641 },
bgneal@312 8642
bgneal@312 8643 setColor : function(c) {
bgneal@312 8644 var t = this;
bgneal@312 8645
bgneal@312 8646 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
bgneal@312 8647
bgneal@312 8648 t.value = c;
bgneal@312 8649 t.hideMenu();
bgneal@312 8650 t.settings.onselect(c);
bgneal@312 8651 },
bgneal@312 8652
bgneal@312 8653 postRender : function() {
bgneal@312 8654 var t = this, id = t.id;
bgneal@312 8655
bgneal@312 8656 t.parent();
bgneal@312 8657 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
bgneal@312 8658 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
bgneal@312 8659 },
bgneal@312 8660
bgneal@312 8661 destroy : function() {
bgneal@312 8662 this.parent();
bgneal@312 8663
bgneal@312 8664 Event.clear(this.id + '_menu');
bgneal@312 8665 Event.clear(this.id + '_more');
bgneal@312 8666 DOM.remove(this.id + '_menu');
bgneal@312 8667 }
bgneal@312 8668 });
bgneal@312 8669 })(tinymce);
bgneal@312 8670
bgneal@312 8671 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
bgneal@312 8672 renderHTML : function() {
bgneal@312 8673 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
bgneal@312 8674
bgneal@312 8675 cl = t.controls;
bgneal@312 8676 for (i=0; i<cl.length; i++) {
bgneal@312 8677 // Get current control, prev control, next control and if the control is a list box or not
bgneal@312 8678 co = cl[i];
bgneal@312 8679 pr = cl[i - 1];
bgneal@312 8680 nx = cl[i + 1];
bgneal@312 8681
bgneal@312 8682 // Add toolbar start
bgneal@312 8683 if (i === 0) {
bgneal@312 8684 c = 'mceToolbarStart';
bgneal@312 8685
bgneal@312 8686 if (co.Button)
bgneal@312 8687 c += ' mceToolbarStartButton';
bgneal@312 8688 else if (co.SplitButton)
bgneal@312 8689 c += ' mceToolbarStartSplitButton';
bgneal@312 8690 else if (co.ListBox)
bgneal@312 8691 c += ' mceToolbarStartListBox';
bgneal@312 8692
bgneal@312 8693 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 8694 }
bgneal@312 8695
bgneal@312 8696 // Add toolbar end before list box and after the previous button
bgneal@312 8697 // This is to fix the o2k7 editor skins
bgneal@312 8698 if (pr && co.ListBox) {
bgneal@312 8699 if (pr.Button || pr.SplitButton)
bgneal@312 8700 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 8701 }
bgneal@312 8702
bgneal@312 8703 // Render control HTML
bgneal@312 8704
bgneal@312 8705 // IE 8 quick fix, needed to propertly generate a hit area for anchors
bgneal@312 8706 if (dom.stdMode)
bgneal@312 8707 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
bgneal@312 8708 else
bgneal@312 8709 h += '<td>' + co.renderHTML() + '</td>';
bgneal@312 8710
bgneal@312 8711 // Add toolbar start after list box and before the next button
bgneal@312 8712 // This is to fix the o2k7 editor skins
bgneal@312 8713 if (nx && co.ListBox) {
bgneal@312 8714 if (nx.Button || nx.SplitButton)
bgneal@312 8715 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 8716 }
bgneal@312 8717 }
bgneal@312 8718
bgneal@312 8719 c = 'mceToolbarEnd';
bgneal@312 8720
bgneal@312 8721 if (co.Button)
bgneal@312 8722 c += ' mceToolbarEndButton';
bgneal@312 8723 else if (co.SplitButton)
bgneal@312 8724 c += ' mceToolbarEndSplitButton';
bgneal@312 8725 else if (co.ListBox)
bgneal@312 8726 c += ' mceToolbarEndListBox';
bgneal@312 8727
bgneal@312 8728 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 8729
bgneal@312 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@312 8731 }
bgneal@312 8732 });
bgneal@312 8733
bgneal@312 8734 (function(tinymce) {
bgneal@312 8735 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
bgneal@312 8736
bgneal@312 8737 tinymce.create('tinymce.AddOnManager', {
bgneal@312 8738 AddOnManager : function() {
bgneal@312 8739 var self = this;
bgneal@312 8740
bgneal@312 8741 self.items = [];
bgneal@312 8742 self.urls = {};
bgneal@312 8743 self.lookup = {};
bgneal@312 8744 self.onAdd = new Dispatcher(self);
bgneal@312 8745 },
bgneal@312 8746
bgneal@312 8747 get : function(n) {
bgneal@312 8748 return this.lookup[n];
bgneal@312 8749 },
bgneal@312 8750
bgneal@312 8751 requireLangPack : function(n) {
bgneal@312 8752 var s = tinymce.settings;
bgneal@312 8753
bgneal@312 8754 if (s && s.language)
bgneal@312 8755 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
bgneal@312 8756 },
bgneal@312 8757
bgneal@312 8758 add : function(id, o) {
bgneal@312 8759 this.items.push(o);
bgneal@312 8760 this.lookup[id] = o;
bgneal@312 8761 this.onAdd.dispatch(this, id, o);
bgneal@312 8762
bgneal@312 8763 return o;
bgneal@312 8764 },
bgneal@312 8765
bgneal@312 8766 load : function(n, u, cb, s) {
bgneal@312 8767 var t = this;
bgneal@312 8768
bgneal@312 8769 if (t.urls[n])
bgneal@312 8770 return;
bgneal@312 8771
bgneal@312 8772 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
bgneal@312 8773 u = tinymce.baseURL + '/' + u;
bgneal@312 8774
bgneal@312 8775 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
bgneal@312 8776
bgneal@312 8777 if (!t.lookup[n])
bgneal@312 8778 tinymce.ScriptLoader.add(u, cb, s);
bgneal@312 8779 }
bgneal@312 8780 });
bgneal@312 8781
bgneal@312 8782 // Create plugin and theme managers
bgneal@312 8783 tinymce.PluginManager = new tinymce.AddOnManager();
bgneal@312 8784 tinymce.ThemeManager = new tinymce.AddOnManager();
bgneal@312 8785 }(tinymce));
bgneal@312 8786
bgneal@312 8787 (function(tinymce) {
bgneal@312 8788 // Shorten names
bgneal@312 8789 var each = tinymce.each, extend = tinymce.extend,
bgneal@312 8790 DOM = tinymce.DOM, Event = tinymce.dom.Event,
bgneal@312 8791 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@312 8792 explode = tinymce.explode,
bgneal@312 8793 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
bgneal@312 8794
bgneal@312 8795 // Setup some URLs where the editor API is located and where the document is
bgneal@312 8796 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
bgneal@312 8797 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
bgneal@312 8798 tinymce.documentBaseURL += '/';
bgneal@312 8799
bgneal@312 8800 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
bgneal@312 8801
bgneal@312 8802 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
bgneal@312 8803
bgneal@312 8804 // Add before unload listener
bgneal@312 8805 // This was required since IE was leaking memory if you added and removed beforeunload listeners
bgneal@312 8806 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
bgneal@312 8807 tinymce.onBeforeUnload = new Dispatcher(tinymce);
bgneal@312 8808
bgneal@312 8809 // Must be on window or IE will leak if the editor is placed in frame or iframe
bgneal@312 8810 Event.add(window, 'beforeunload', function(e) {
bgneal@312 8811 tinymce.onBeforeUnload.dispatch(tinymce, e);
bgneal@312 8812 });
bgneal@312 8813
bgneal@312 8814 tinymce.onAddEditor = new Dispatcher(tinymce);
bgneal@312 8815
bgneal@312 8816 tinymce.onRemoveEditor = new Dispatcher(tinymce);
bgneal@312 8817
bgneal@312 8818 tinymce.EditorManager = extend(tinymce, {
bgneal@312 8819 editors : [],
bgneal@312 8820
bgneal@312 8821 i18n : {},
bgneal@312 8822
bgneal@312 8823 activeEditor : null,
bgneal@312 8824
bgneal@312 8825 init : function(s) {
bgneal@312 8826 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
bgneal@312 8827
bgneal@312 8828 function execCallback(se, n, s) {
bgneal@312 8829 var f = se[n];
bgneal@312 8830
bgneal@312 8831 if (!f)
bgneal@312 8832 return;
bgneal@312 8833
bgneal@312 8834 if (tinymce.is(f, 'string')) {
bgneal@312 8835 s = f.replace(/\.\w+$/, '');
bgneal@312 8836 s = s ? tinymce.resolve(s) : 0;
bgneal@312 8837 f = tinymce.resolve(f);
bgneal@312 8838 }
bgneal@312 8839
bgneal@312 8840 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
bgneal@312 8841 };
bgneal@312 8842
bgneal@312 8843 s = extend({
bgneal@312 8844 theme : "simple",
bgneal@312 8845 language : "en"
bgneal@312 8846 }, s);
bgneal@312 8847
bgneal@312 8848 t.settings = s;
bgneal@312 8849
bgneal@312 8850 // Legacy call
bgneal@312 8851 Event.add(document, 'init', function() {
bgneal@312 8852 var l, co;
bgneal@312 8853
bgneal@312 8854 execCallback(s, 'onpageload');
bgneal@312 8855
bgneal@312 8856 switch (s.mode) {
bgneal@312 8857 case "exact":
bgneal@312 8858 l = s.elements || '';
bgneal@312 8859
bgneal@312 8860 if(l.length > 0) {
bgneal@312 8861 each(explode(l), function(v) {
bgneal@312 8862 if (DOM.get(v)) {
bgneal@312 8863 ed = new tinymce.Editor(v, s);
bgneal@312 8864 el.push(ed);
bgneal@312 8865 ed.render(1);
bgneal@312 8866 } else {
bgneal@312 8867 each(document.forms, function(f) {
bgneal@312 8868 each(f.elements, function(e) {
bgneal@312 8869 if (e.name === v) {
bgneal@312 8870 v = 'mce_editor_' + instanceCounter++;
bgneal@312 8871 DOM.setAttrib(e, 'id', v);
bgneal@312 8872
bgneal@312 8873 ed = new tinymce.Editor(v, s);
bgneal@312 8874 el.push(ed);
bgneal@312 8875 ed.render(1);
bgneal@312 8876 }
bgneal@312 8877 });
bgneal@312 8878 });
bgneal@312 8879 }
bgneal@312 8880 });
bgneal@312 8881 }
bgneal@312 8882 break;
bgneal@312 8883
bgneal@312 8884 case "textareas":
bgneal@312 8885 case "specific_textareas":
bgneal@312 8886 function hasClass(n, c) {
bgneal@312 8887 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
bgneal@312 8888 };
bgneal@312 8889
bgneal@312 8890 each(DOM.select('textarea'), function(v) {
bgneal@312 8891 if (s.editor_deselector && hasClass(v, s.editor_deselector))
bgneal@312 8892 return;
bgneal@312 8893
bgneal@312 8894 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
bgneal@312 8895 // Can we use the name
bgneal@312 8896 e = DOM.get(v.name);
bgneal@312 8897 if (!v.id && !e)
bgneal@312 8898 v.id = v.name;
bgneal@312 8899
bgneal@312 8900 // Generate unique name if missing or already exists
bgneal@312 8901 if (!v.id || t.get(v.id))
bgneal@312 8902 v.id = DOM.uniqueId();
bgneal@312 8903
bgneal@312 8904 ed = new tinymce.Editor(v.id, s);
bgneal@312 8905 el.push(ed);
bgneal@312 8906 ed.render(1);
bgneal@312 8907 }
bgneal@312 8908 });
bgneal@312 8909 break;
bgneal@312 8910 }
bgneal@312 8911
bgneal@312 8912 // Call onInit when all editors are initialized
bgneal@312 8913 if (s.oninit) {
bgneal@312 8914 l = co = 0;
bgneal@312 8915
bgneal@312 8916 each(el, function(ed) {
bgneal@312 8917 co++;
bgneal@312 8918
bgneal@312 8919 if (!ed.initialized) {
bgneal@312 8920 // Wait for it
bgneal@312 8921 ed.onInit.add(function() {
bgneal@312 8922 l++;
bgneal@312 8923
bgneal@312 8924 // All done
bgneal@312 8925 if (l == co)
bgneal@312 8926 execCallback(s, 'oninit');
bgneal@312 8927 });
bgneal@312 8928 } else
bgneal@312 8929 l++;
bgneal@312 8930
bgneal@312 8931 // All done
bgneal@312 8932 if (l == co)
bgneal@312 8933 execCallback(s, 'oninit');
bgneal@312 8934 });
bgneal@312 8935 }
bgneal@312 8936 });
bgneal@312 8937 },
bgneal@312 8938
bgneal@312 8939 get : function(id) {
bgneal@312 8940 if (id === undefined)
bgneal@312 8941 return this.editors;
bgneal@312 8942
bgneal@312 8943 return this.editors[id];
bgneal@312 8944 },
bgneal@312 8945
bgneal@312 8946 getInstanceById : function(id) {
bgneal@312 8947 return this.get(id);
bgneal@312 8948 },
bgneal@312 8949
bgneal@312 8950 add : function(editor) {
bgneal@312 8951 var self = this, editors = self.editors;
bgneal@312 8952
bgneal@312 8953 // Add named and index editor instance
bgneal@312 8954 editors[editor.id] = editor;
bgneal@312 8955 editors.push(editor);
bgneal@312 8956
bgneal@312 8957 self._setActive(editor);
bgneal@312 8958 self.onAddEditor.dispatch(self, editor);
bgneal@312 8959
bgneal@312 8960
bgneal@312 8961 return editor;
bgneal@312 8962 },
bgneal@312 8963
bgneal@312 8964 remove : function(editor) {
bgneal@312 8965 var t = this, i, editors = t.editors;
bgneal@312 8966
bgneal@312 8967 // Not in the collection
bgneal@312 8968 if (!editors[editor.id])
bgneal@312 8969 return null;
bgneal@312 8970
bgneal@312 8971 delete editors[editor.id];
bgneal@312 8972
bgneal@312 8973 for (i = 0; i < editors.length; i++) {
bgneal@312 8974 if (editors[i] == editor) {
bgneal@312 8975 editors.splice(i, 1);
bgneal@312 8976 break;
bgneal@312 8977 }
bgneal@312 8978 }
bgneal@312 8979
bgneal@312 8980 // Select another editor since the active one was removed
bgneal@312 8981 if (t.activeEditor == editor)
bgneal@312 8982 t._setActive(editors[0]);
bgneal@312 8983
bgneal@312 8984 editor.destroy();
bgneal@312 8985 t.onRemoveEditor.dispatch(t, editor);
bgneal@312 8986
bgneal@312 8987 return editor;
bgneal@312 8988 },
bgneal@312 8989
bgneal@312 8990 execCommand : function(c, u, v) {
bgneal@312 8991 var t = this, ed = t.get(v), w;
bgneal@312 8992
bgneal@312 8993 // Manager commands
bgneal@312 8994 switch (c) {
bgneal@312 8995 case "mceFocus":
bgneal@312 8996 ed.focus();
bgneal@312 8997 return true;
bgneal@312 8998
bgneal@312 8999 case "mceAddEditor":
bgneal@312 9000 case "mceAddControl":
bgneal@312 9001 if (!t.get(v))
bgneal@312 9002 new tinymce.Editor(v, t.settings).render();
bgneal@312 9003
bgneal@312 9004 return true;
bgneal@312 9005
bgneal@312 9006 case "mceAddFrameControl":
bgneal@312 9007 w = v.window;
bgneal@312 9008
bgneal@312 9009 // Add tinyMCE global instance and tinymce namespace to specified window
bgneal@312 9010 w.tinyMCE = tinyMCE;
bgneal@312 9011 w.tinymce = tinymce;
bgneal@312 9012
bgneal@312 9013 tinymce.DOM.doc = w.document;
bgneal@312 9014 tinymce.DOM.win = w;
bgneal@312 9015
bgneal@312 9016 ed = new tinymce.Editor(v.element_id, v);
bgneal@312 9017 ed.render();
bgneal@312 9018
bgneal@312 9019 // Fix IE memory leaks
bgneal@312 9020 if (tinymce.isIE) {
bgneal@312 9021 function clr() {
bgneal@312 9022 ed.destroy();
bgneal@312 9023 w.detachEvent('onunload', clr);
bgneal@312 9024 w = w.tinyMCE = w.tinymce = null; // IE leak
bgneal@312 9025 };
bgneal@312 9026
bgneal@312 9027 w.attachEvent('onunload', clr);
bgneal@312 9028 }
bgneal@312 9029
bgneal@312 9030 v.page_window = null;
bgneal@312 9031
bgneal@312 9032 return true;
bgneal@312 9033
bgneal@312 9034 case "mceRemoveEditor":
bgneal@312 9035 case "mceRemoveControl":
bgneal@312 9036 if (ed)
bgneal@312 9037 ed.remove();
bgneal@312 9038
bgneal@312 9039 return true;
bgneal@312 9040
bgneal@312 9041 case 'mceToggleEditor':
bgneal@312 9042 if (!ed) {
bgneal@312 9043 t.execCommand('mceAddControl', 0, v);
bgneal@312 9044 return true;
bgneal@312 9045 }
bgneal@312 9046
bgneal@312 9047 if (ed.isHidden())
bgneal@312 9048 ed.show();
bgneal@312 9049 else
bgneal@312 9050 ed.hide();
bgneal@312 9051
bgneal@312 9052 return true;
bgneal@312 9053 }
bgneal@312 9054
bgneal@312 9055 // Run command on active editor
bgneal@312 9056 if (t.activeEditor)
bgneal@312 9057 return t.activeEditor.execCommand(c, u, v);
bgneal@312 9058
bgneal@312 9059 return false;
bgneal@312 9060 },
bgneal@312 9061
bgneal@312 9062 execInstanceCommand : function(id, c, u, v) {
bgneal@312 9063 var ed = this.get(id);
bgneal@312 9064
bgneal@312 9065 if (ed)
bgneal@312 9066 return ed.execCommand(c, u, v);
bgneal@312 9067
bgneal@312 9068 return false;
bgneal@312 9069 },
bgneal@312 9070
bgneal@312 9071 triggerSave : function() {
bgneal@312 9072 each(this.editors, function(e) {
bgneal@312 9073 e.save();
bgneal@312 9074 });
bgneal@312 9075 },
bgneal@312 9076
bgneal@312 9077 addI18n : function(p, o) {
bgneal@312 9078 var lo, i18n = this.i18n;
bgneal@312 9079
bgneal@312 9080 if (!tinymce.is(p, 'string')) {
bgneal@312 9081 each(p, function(o, lc) {
bgneal@312 9082 each(o, function(o, g) {
bgneal@312 9083 each(o, function(o, k) {
bgneal@312 9084 if (g === 'common')
bgneal@312 9085 i18n[lc + '.' + k] = o;
bgneal@312 9086 else
bgneal@312 9087 i18n[lc + '.' + g + '.' + k] = o;
bgneal@312 9088 });
bgneal@312 9089 });
bgneal@312 9090 });
bgneal@312 9091 } else {
bgneal@312 9092 each(o, function(o, k) {
bgneal@312 9093 i18n[p + '.' + k] = o;
bgneal@312 9094 });
bgneal@312 9095 }
bgneal@312 9096 },
bgneal@312 9097
bgneal@312 9098 // Private methods
bgneal@312 9099
bgneal@312 9100 _setActive : function(editor) {
bgneal@312 9101 this.selectedInstance = this.activeEditor = editor;
bgneal@312 9102 }
bgneal@312 9103 });
bgneal@312 9104 })(tinymce);
bgneal@312 9105
bgneal@312 9106 (function(tinymce) {
bgneal@312 9107 // Shorten these names
bgneal@312 9108 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
bgneal@312 9109 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
bgneal@312 9110 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
bgneal@312 9111 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@312 9112 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
bgneal@312 9113
bgneal@312 9114 tinymce.create('tinymce.Editor', {
bgneal@312 9115 Editor : function(id, s) {
bgneal@312 9116 var t = this;
bgneal@312 9117
bgneal@312 9118 t.id = t.editorId = id;
bgneal@312 9119
bgneal@312 9120 t.execCommands = {};
bgneal@312 9121 t.queryStateCommands = {};
bgneal@312 9122 t.queryValueCommands = {};
bgneal@312 9123
bgneal@312 9124 t.isNotDirty = false;
bgneal@312 9125
bgneal@312 9126 t.plugins = {};
bgneal@312 9127
bgneal@312 9128 // Add events to the editor
bgneal@312 9129 each([
bgneal@312 9130 'onPreInit',
bgneal@312 9131
bgneal@312 9132 'onBeforeRenderUI',
bgneal@312 9133
bgneal@312 9134 'onPostRender',
bgneal@312 9135
bgneal@312 9136 'onInit',
bgneal@312 9137
bgneal@312 9138 'onRemove',
bgneal@312 9139
bgneal@312 9140 'onActivate',
bgneal@312 9141
bgneal@312 9142 'onDeactivate',
bgneal@312 9143
bgneal@312 9144 'onClick',
bgneal@312 9145
bgneal@312 9146 'onEvent',
bgneal@312 9147
bgneal@312 9148 'onMouseUp',
bgneal@312 9149
bgneal@312 9150 'onMouseDown',
bgneal@312 9151
bgneal@312 9152 'onDblClick',
bgneal@312 9153
bgneal@312 9154 'onKeyDown',
bgneal@312 9155
bgneal@312 9156 'onKeyUp',
bgneal@312 9157
bgneal@312 9158 'onKeyPress',
bgneal@312 9159
bgneal@312 9160 'onContextMenu',
bgneal@312 9161
bgneal@312 9162 'onSubmit',
bgneal@312 9163
bgneal@312 9164 'onReset',
bgneal@312 9165
bgneal@312 9166 'onPaste',
bgneal@312 9167
bgneal@312 9168 'onPreProcess',
bgneal@312 9169
bgneal@312 9170 'onPostProcess',
bgneal@312 9171
bgneal@312 9172 'onBeforeSetContent',
bgneal@312 9173
bgneal@312 9174 'onBeforeGetContent',
bgneal@312 9175
bgneal@312 9176 'onSetContent',
bgneal@312 9177
bgneal@312 9178 'onGetContent',
bgneal@312 9179
bgneal@312 9180 'onLoadContent',
bgneal@312 9181
bgneal@312 9182 'onSaveContent',
bgneal@312 9183
bgneal@312 9184 'onNodeChange',
bgneal@312 9185
bgneal@312 9186 'onChange',
bgneal@312 9187
bgneal@312 9188 'onBeforeExecCommand',
bgneal@312 9189
bgneal@312 9190 'onExecCommand',
bgneal@312 9191
bgneal@312 9192 'onUndo',
bgneal@312 9193
bgneal@312 9194 'onRedo',
bgneal@312 9195
bgneal@312 9196 'onVisualAid',
bgneal@312 9197
bgneal@312 9198 'onSetProgressState'
bgneal@312 9199 ], function(e) {
bgneal@312 9200 t[e] = new Dispatcher(t);
bgneal@312 9201 });
bgneal@312 9202
bgneal@312 9203 t.settings = s = extend({
bgneal@312 9204 id : id,
bgneal@312 9205 language : 'en',
bgneal@312 9206 docs_language : 'en',
bgneal@312 9207 theme : 'simple',
bgneal@312 9208 skin : 'default',
bgneal@312 9209 delta_width : 0,
bgneal@312 9210 delta_height : 0,
bgneal@312 9211 popup_css : '',
bgneal@312 9212 plugins : '',
bgneal@312 9213 document_base_url : tinymce.documentBaseURL,
bgneal@312 9214 add_form_submit_trigger : 1,
bgneal@312 9215 submit_patch : 1,
bgneal@312 9216 add_unload_trigger : 1,
bgneal@312 9217 convert_urls : 1,
bgneal@312 9218 relative_urls : 1,
bgneal@312 9219 remove_script_host : 1,
bgneal@312 9220 table_inline_editing : 0,
bgneal@312 9221 object_resizing : 1,
bgneal@312 9222 cleanup : 1,
bgneal@312 9223 accessibility_focus : 1,
bgneal@312 9224 custom_shortcuts : 1,
bgneal@312 9225 custom_undo_redo_keyboard_shortcuts : 1,
bgneal@312 9226 custom_undo_redo_restore_selection : 1,
bgneal@312 9227 custom_undo_redo : 1,
bgneal@312 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@312 9229 visual_table_class : 'mceItemTable',
bgneal@312 9230 visual : 1,
bgneal@312 9231 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
bgneal@312 9232 apply_source_formatting : 1,
bgneal@312 9233 directionality : 'ltr',
bgneal@312 9234 forced_root_block : 'p',
bgneal@312 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@312 9236 hidden_input : 1,
bgneal@312 9237 padd_empty_editor : 1,
bgneal@312 9238 render_ui : 1,
bgneal@312 9239 init_theme : 1,
bgneal@312 9240 force_p_newlines : 1,
bgneal@312 9241 indentation : '30px',
bgneal@312 9242 keep_styles : 1,
bgneal@312 9243 fix_table_elements : 1,
bgneal@312 9244 inline_styles : 1,
bgneal@312 9245 convert_fonts_to_spans : true
bgneal@312 9246 }, s);
bgneal@312 9247
bgneal@312 9248 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
bgneal@312 9249 base_uri : tinyMCE.baseURI
bgneal@312 9250 });
bgneal@312 9251
bgneal@312 9252 t.baseURI = tinymce.baseURI;
bgneal@312 9253
bgneal@312 9254 // Call setup
bgneal@312 9255 t.execCallback('setup', t);
bgneal@312 9256 },
bgneal@312 9257
bgneal@312 9258 render : function(nst) {
bgneal@312 9259 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
bgneal@312 9260
bgneal@312 9261 // Page is not loaded yet, wait for it
bgneal@312 9262 if (!Event.domLoaded) {
bgneal@312 9263 Event.add(document, 'init', function() {
bgneal@312 9264 t.render();
bgneal@312 9265 });
bgneal@312 9266 return;
bgneal@312 9267 }
bgneal@312 9268
bgneal@312 9269 tinyMCE.settings = s;
bgneal@312 9270
bgneal@312 9271 // Element not found, then skip initialization
bgneal@312 9272 if (!t.getElement())
bgneal@312 9273 return;
bgneal@312 9274
bgneal@312 9275 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
bgneal@312 9276 // browser says it has contentEditable support but there is no visible caret
bgneal@312 9277 // We will remove this check ones Apple implements full contentEditable support
bgneal@312 9278 if (tinymce.isIDevice)
bgneal@312 9279 return;
bgneal@312 9280
bgneal@312 9281 // Add hidden input for non input elements inside form elements
bgneal@312 9282 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
bgneal@312 9283 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
bgneal@312 9284
bgneal@312 9285 if (tinymce.WindowManager)
bgneal@312 9286 t.windowManager = new tinymce.WindowManager(t);
bgneal@312 9287
bgneal@312 9288 if (s.encoding == 'xml') {
bgneal@312 9289 t.onGetContent.add(function(ed, o) {
bgneal@312 9290 if (o.save)
bgneal@312 9291 o.content = DOM.encode(o.content);
bgneal@312 9292 });
bgneal@312 9293 }
bgneal@312 9294
bgneal@312 9295 if (s.add_form_submit_trigger) {
bgneal@312 9296 t.onSubmit.addToTop(function() {
bgneal@312 9297 if (t.initialized) {
bgneal@312 9298 t.save();
bgneal@312 9299 t.isNotDirty = 1;
bgneal@312 9300 }
bgneal@312 9301 });
bgneal@312 9302 }
bgneal@312 9303
bgneal@312 9304 if (s.add_unload_trigger) {
bgneal@312 9305 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
bgneal@312 9306 if (t.initialized && !t.destroyed && !t.isHidden())
bgneal@312 9307 t.save({format : 'raw', no_events : true});
bgneal@312 9308 });
bgneal@312 9309 }
bgneal@312 9310
bgneal@312 9311 tinymce.addUnload(t.destroy, t);
bgneal@312 9312
bgneal@312 9313 if (s.submit_patch) {
bgneal@312 9314 t.onBeforeRenderUI.add(function() {
bgneal@312 9315 var n = t.getElement().form;
bgneal@312 9316
bgneal@312 9317 if (!n)
bgneal@312 9318 return;
bgneal@312 9319
bgneal@312 9320 // Already patched
bgneal@312 9321 if (n._mceOldSubmit)
bgneal@312 9322 return;
bgneal@312 9323
bgneal@312 9324 // Check page uses id="submit" or name="submit" for it's submit button
bgneal@312 9325 if (!n.submit.nodeType && !n.submit.length) {
bgneal@312 9326 t.formElement = n;
bgneal@312 9327 n._mceOldSubmit = n.submit;
bgneal@312 9328 n.submit = function() {
bgneal@312 9329 // Save all instances
bgneal@312 9330 tinymce.triggerSave();
bgneal@312 9331 t.isNotDirty = 1;
bgneal@312 9332
bgneal@312 9333 return t.formElement._mceOldSubmit(t.formElement);
bgneal@312 9334 };
bgneal@312 9335 }
bgneal@312 9336
bgneal@312 9337 n = null;
bgneal@312 9338 });
bgneal@312 9339 }
bgneal@312 9340
bgneal@312 9341 // Load scripts
bgneal@312 9342 function loadScripts() {
bgneal@312 9343 if (s.language)
bgneal@312 9344 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
bgneal@312 9345
bgneal@312 9346 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
bgneal@312 9347 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
bgneal@312 9348
bgneal@312 9349 each(explode(s.plugins), function(p) {
bgneal@312 9350 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
bgneal@312 9351 // Skip safari plugin, since it is removed as of 3.3b1
bgneal@312 9352 if (p == 'safari')
bgneal@312 9353 return;
bgneal@312 9354
bgneal@312 9355 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
bgneal@312 9356 }
bgneal@312 9357 });
bgneal@312 9358
bgneal@312 9359 // Init when que is loaded
bgneal@312 9360 sl.loadQueue(function() {
bgneal@312 9361 if (!t.removed)
bgneal@312 9362 t.init();
bgneal@312 9363 });
bgneal@312 9364 };
bgneal@312 9365
bgneal@312 9366 loadScripts();
bgneal@312 9367 },
bgneal@312 9368
bgneal@312 9369 init : function() {
bgneal@312 9370 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
bgneal@312 9371
bgneal@312 9372 tinymce.add(t);
bgneal@312 9373
bgneal@312 9374 if (s.theme) {
bgneal@312 9375 s.theme = s.theme.replace(/-/, '');
bgneal@312 9376 o = ThemeManager.get(s.theme);
bgneal@312 9377 t.theme = new o();
bgneal@312 9378
bgneal@312 9379 if (t.theme.init && s.init_theme)
bgneal@312 9380 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
bgneal@312 9381 }
bgneal@312 9382
bgneal@312 9383 // Create all plugins
bgneal@312 9384 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
bgneal@312 9385 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
bgneal@312 9386
bgneal@312 9387 if (c) {
bgneal@312 9388 po = new c(t, u);
bgneal@312 9389
bgneal@312 9390 t.plugins[p] = po;
bgneal@312 9391
bgneal@312 9392 if (po.init)
bgneal@312 9393 po.init(t, u);
bgneal@312 9394 }
bgneal@312 9395 });
bgneal@312 9396
bgneal@312 9397 // Setup popup CSS path(s)
bgneal@312 9398 if (s.popup_css !== false) {
bgneal@312 9399 if (s.popup_css)
bgneal@312 9400 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
bgneal@312 9401 else
bgneal@312 9402 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
bgneal@312 9403 }
bgneal@312 9404
bgneal@312 9405 if (s.popup_css_add)
bgneal@312 9406 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
bgneal@312 9407
bgneal@312 9408 t.controlManager = new tinymce.ControlManager(t);
bgneal@312 9409
bgneal@312 9410 if (s.custom_undo_redo) {
bgneal@312 9411 // Add initial undo level
bgneal@312 9412 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@312 9413 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
bgneal@312 9414 if (!t.undoManager.hasUndo())
bgneal@312 9415 t.undoManager.add();
bgneal@312 9416 }
bgneal@312 9417 });
bgneal@312 9418
bgneal@312 9419 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@312 9420 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@312 9421 t.undoManager.add();
bgneal@312 9422 });
bgneal@312 9423 }
bgneal@312 9424
bgneal@312 9425 t.onExecCommand.add(function(ed, c) {
bgneal@312 9426 // Don't refresh the select lists until caret move
bgneal@312 9427 if (!/^(FontName|FontSize)$/.test(c))
bgneal@312 9428 t.nodeChanged();
bgneal@312 9429 });
bgneal@312 9430
bgneal@312 9431 // Remove ghost selections on images and tables in Gecko
bgneal@312 9432 if (isGecko) {
bgneal@312 9433 function repaint(a, o) {
bgneal@312 9434 if (!o || !o.initial)
bgneal@312 9435 t.execCommand('mceRepaint');
bgneal@312 9436 };
bgneal@312 9437
bgneal@312 9438 t.onUndo.add(repaint);
bgneal@312 9439 t.onRedo.add(repaint);
bgneal@312 9440 t.onSetContent.add(repaint);
bgneal@312 9441 }
bgneal@312 9442
bgneal@312 9443 // Enables users to override the control factory
bgneal@312 9444 t.onBeforeRenderUI.dispatch(t, t.controlManager);
bgneal@312 9445
bgneal@312 9446 // Measure box
bgneal@312 9447 if (s.render_ui) {
bgneal@312 9448 w = s.width || e.style.width || e.offsetWidth;
bgneal@312 9449 h = s.height || e.style.height || e.offsetHeight;
bgneal@312 9450 t.orgDisplay = e.style.display;
bgneal@312 9451 re = /^[0-9\.]+(|px)$/i;
bgneal@312 9452
bgneal@312 9453 if (re.test('' + w))
bgneal@312 9454 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
bgneal@312 9455
bgneal@312 9456 if (re.test('' + h))
bgneal@312 9457 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
bgneal@312 9458
bgneal@312 9459 // Render UI
bgneal@312 9460 o = t.theme.renderUI({
bgneal@312 9461 targetNode : e,
bgneal@312 9462 width : w,
bgneal@312 9463 height : h,
bgneal@312 9464 deltaWidth : s.delta_width,
bgneal@312 9465 deltaHeight : s.delta_height
bgneal@312 9466 });
bgneal@312 9467
bgneal@312 9468 t.editorContainer = o.editorContainer;
bgneal@312 9469 }
bgneal@312 9470
bgneal@312 9471
bgneal@312 9472 // User specified a document.domain value
bgneal@312 9473 if (document.domain && location.hostname != document.domain)
bgneal@312 9474 tinymce.relaxedDomain = document.domain;
bgneal@312 9475
bgneal@312 9476 // Resize editor
bgneal@312 9477 DOM.setStyles(o.sizeContainer || o.editorContainer, {
bgneal@312 9478 width : w,
bgneal@312 9479 height : h
bgneal@312 9480 });
bgneal@312 9481
bgneal@312 9482 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
bgneal@312 9483 if (h < 100)
bgneal@312 9484 h = 100;
bgneal@312 9485
bgneal@312 9486 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
bgneal@312 9487
bgneal@312 9488 // We only need to override paths if we have to
bgneal@312 9489 // IE has a bug where it remove site absolute urls to relative ones if this is specified
bgneal@312 9490 if (s.document_base_url != tinymce.documentBaseURL)
bgneal@312 9491 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
bgneal@312 9492
bgneal@312 9493 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
bgneal@312 9494
bgneal@312 9495 if (tinymce.relaxedDomain)
bgneal@312 9496 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
bgneal@312 9497
bgneal@312 9498 bi = s.body_id || 'tinymce';
bgneal@312 9499 if (bi.indexOf('=') != -1) {
bgneal@312 9500 bi = t.getParam('body_id', '', 'hash');
bgneal@312 9501 bi = bi[t.id] || bi;
bgneal@312 9502 }
bgneal@312 9503
bgneal@312 9504 bc = s.body_class || '';
bgneal@312 9505 if (bc.indexOf('=') != -1) {
bgneal@312 9506 bc = t.getParam('body_class', '', 'hash');
bgneal@312 9507 bc = bc[t.id] || '';
bgneal@312 9508 }
bgneal@312 9509
bgneal@312 9510 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
bgneal@312 9511
bgneal@312 9512 // Domain relaxing enabled, then set document domain
bgneal@312 9513 if (tinymce.relaxedDomain) {
bgneal@312 9514 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
bgneal@312 9515 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
bgneal@312 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@312 9517 else if (tinymce.isOpera)
bgneal@312 9518 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
bgneal@312 9519 }
bgneal@312 9520
bgneal@312 9521 // Create iframe
bgneal@312 9522 n = DOM.add(o.iframeContainer, 'iframe', {
bgneal@312 9523 id : t.id + "_ifr",
bgneal@312 9524 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
bgneal@312 9525 frameBorder : '0',
bgneal@312 9526 style : {
bgneal@312 9527 width : '100%',
bgneal@312 9528 height : h
bgneal@312 9529 }
bgneal@312 9530 });
bgneal@312 9531
bgneal@312 9532 t.contentAreaContainer = o.iframeContainer;
bgneal@312 9533 DOM.get(o.editorContainer).style.display = t.orgDisplay;
bgneal@312 9534 DOM.get(t.id).style.display = 'none';
bgneal@312 9535
bgneal@312 9536 if (!isIE || !tinymce.relaxedDomain)
bgneal@312 9537 t.setupIframe();
bgneal@312 9538
bgneal@312 9539 e = n = o = null; // Cleanup
bgneal@312 9540 },
bgneal@312 9541
bgneal@312 9542 setupIframe : function() {
bgneal@312 9543 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
bgneal@312 9544
bgneal@312 9545 // Setup iframe body
bgneal@312 9546 if (!isIE || !tinymce.relaxedDomain) {
bgneal@312 9547 d.open();
bgneal@312 9548 d.write(t.iframeHTML);
bgneal@312 9549 d.close();
bgneal@312 9550 }
bgneal@312 9551
bgneal@312 9552 // Design mode needs to be added here Ctrl+A will fail otherwise
bgneal@312 9553 if (!isIE) {
bgneal@312 9554 try {
bgneal@312 9555 if (!s.readonly)
bgneal@312 9556 d.designMode = 'On';
bgneal@312 9557 } catch (ex) {
bgneal@312 9558 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@312 9559 // The design mode will be set ones the editor is focused
bgneal@312 9560 }
bgneal@312 9561 }
bgneal@312 9562
bgneal@312 9563 // IE needs to use contentEditable or it will display non secure items for HTTPS
bgneal@312 9564 if (isIE) {
bgneal@312 9565 // It will not steal focus if we hide it while setting contentEditable
bgneal@312 9566 b = t.getBody();
bgneal@312 9567 DOM.hide(b);
bgneal@312 9568
bgneal@312 9569 if (!s.readonly)
bgneal@312 9570 b.contentEditable = true;
bgneal@312 9571
bgneal@312 9572 DOM.show(b);
bgneal@312 9573 }
bgneal@312 9574
bgneal@312 9575 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
bgneal@312 9576 keep_values : true,
bgneal@312 9577 url_converter : t.convertURL,
bgneal@312 9578 url_converter_scope : t,
bgneal@312 9579 hex_colors : s.force_hex_style_colors,
bgneal@312 9580 class_filter : s.class_filter,
bgneal@312 9581 update_styles : 1,
bgneal@312 9582 fix_ie_paragraphs : 1,
bgneal@312 9583 valid_styles : s.valid_styles
bgneal@312 9584 });
bgneal@312 9585
bgneal@312 9586 t.schema = new tinymce.dom.Schema();
bgneal@312 9587
bgneal@312 9588 t.serializer = new tinymce.dom.Serializer(extend(s, {
bgneal@312 9589 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
bgneal@312 9590 dom : t.dom,
bgneal@312 9591 schema : t.schema
bgneal@312 9592 }));
bgneal@312 9593
bgneal@312 9594 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
bgneal@312 9595
bgneal@312 9596 t.formatter = new tinymce.Formatter(this);
bgneal@312 9597
bgneal@312 9598 // Register default formats
bgneal@312 9599 t.formatter.register({
bgneal@312 9600 alignleft : [
bgneal@312 9601 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
bgneal@312 9602 {selector : 'img,table', styles : {'float' : 'left'}}
bgneal@312 9603 ],
bgneal@312 9604
bgneal@312 9605 aligncenter : [
bgneal@312 9606 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
bgneal@312 9607 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
bgneal@312 9608 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
bgneal@312 9609 ],
bgneal@312 9610
bgneal@312 9611 alignright : [
bgneal@312 9612 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
bgneal@312 9613 {selector : 'img,table', styles : {'float' : 'right'}}
bgneal@312 9614 ],
bgneal@312 9615
bgneal@312 9616 alignfull : [
bgneal@312 9617 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
bgneal@312 9618 ],
bgneal@312 9619
bgneal@312 9620 bold : [
bgneal@312 9621 {inline : 'strong'},
bgneal@312 9622 {inline : 'span', styles : {fontWeight : 'bold'}},
bgneal@312 9623 {inline : 'b'}
bgneal@312 9624 ],
bgneal@312 9625
bgneal@312 9626 italic : [
bgneal@312 9627 {inline : 'em'},
bgneal@312 9628 {inline : 'span', styles : {fontStyle : 'italic'}},
bgneal@312 9629 {inline : 'i'}
bgneal@312 9630 ],
bgneal@312 9631
bgneal@312 9632 underline : [
bgneal@312 9633 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
bgneal@312 9634 {inline : 'u'}
bgneal@312 9635 ],
bgneal@312 9636
bgneal@312 9637 strikethrough : [
bgneal@312 9638 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
bgneal@312 9639 {inline : 'u'}
bgneal@312 9640 ],
bgneal@312 9641
bgneal@312 9642 forecolor : {inline : 'span', styles : {color : '%value'}},
bgneal@312 9643 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
bgneal@312 9644 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
bgneal@312 9645 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
bgneal@312 9646 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
bgneal@312 9647 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
bgneal@312 9648
bgneal@312 9649 removeformat : [
bgneal@312 9650 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
bgneal@312 9651 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
bgneal@312 9652 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
bgneal@312 9653 ]
bgneal@312 9654 });
bgneal@312 9655
bgneal@312 9656 // Register default block formats
bgneal@312 9657 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
bgneal@312 9658 t.formatter.register(name, {block : name, remove : 'all'});
bgneal@312 9659 });
bgneal@312 9660
bgneal@312 9661 // Register user defined formats
bgneal@312 9662 t.formatter.register(t.settings.formats);
bgneal@312 9663
bgneal@312 9664 t.undoManager = new tinymce.UndoManager(t);
bgneal@312 9665
bgneal@312 9666 // Pass through
bgneal@312 9667 t.undoManager.onAdd.add(function(um, l) {
bgneal@312 9668 if (!l.initial)
bgneal@312 9669 return t.onChange.dispatch(t, l, um);
bgneal@312 9670 });
bgneal@312 9671
bgneal@312 9672 t.undoManager.onUndo.add(function(um, l) {
bgneal@312 9673 return t.onUndo.dispatch(t, l, um);
bgneal@312 9674 });
bgneal@312 9675
bgneal@312 9676 t.undoManager.onRedo.add(function(um, l) {
bgneal@312 9677 return t.onRedo.dispatch(t, l, um);
bgneal@312 9678 });
bgneal@312 9679
bgneal@312 9680 t.forceBlocks = new tinymce.ForceBlocks(t, {
bgneal@312 9681 forced_root_block : s.forced_root_block
bgneal@312 9682 });
bgneal@312 9683
bgneal@312 9684 t.editorCommands = new tinymce.EditorCommands(t);
bgneal@312 9685
bgneal@312 9686 // Pass through
bgneal@312 9687 t.serializer.onPreProcess.add(function(se, o) {
bgneal@312 9688 return t.onPreProcess.dispatch(t, o, se);
bgneal@312 9689 });
bgneal@312 9690
bgneal@312 9691 t.serializer.onPostProcess.add(function(se, o) {
bgneal@312 9692 return t.onPostProcess.dispatch(t, o, se);
bgneal@312 9693 });
bgneal@312 9694
bgneal@312 9695 t.onPreInit.dispatch(t);
bgneal@312 9696
bgneal@312 9697 if (!s.gecko_spellcheck)
bgneal@312 9698 t.getBody().spellcheck = 0;
bgneal@312 9699
bgneal@312 9700 if (!s.readonly)
bgneal@312 9701 t._addEvents();
bgneal@312 9702
bgneal@312 9703 t.controlManager.onPostRender.dispatch(t, t.controlManager);
bgneal@312 9704 t.onPostRender.dispatch(t);
bgneal@312 9705
bgneal@312 9706 if (s.directionality)
bgneal@312 9707 t.getBody().dir = s.directionality;
bgneal@312 9708
bgneal@312 9709 if (s.nowrap)
bgneal@312 9710 t.getBody().style.whiteSpace = "nowrap";
bgneal@312 9711
bgneal@312 9712 if (s.custom_elements) {
bgneal@312 9713 function handleCustom(ed, o) {
bgneal@312 9714 each(explode(s.custom_elements), function(v) {
bgneal@312 9715 var n;
bgneal@312 9716
bgneal@312 9717 if (v.indexOf('~') === 0) {
bgneal@312 9718 v = v.substring(1);
bgneal@312 9719 n = 'span';
bgneal@312 9720 } else
bgneal@312 9721 n = 'div';
bgneal@312 9722
bgneal@312 9723 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
bgneal@312 9724 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
bgneal@312 9725 });
bgneal@312 9726 };
bgneal@312 9727
bgneal@312 9728 t.onBeforeSetContent.add(handleCustom);
bgneal@312 9729 t.onPostProcess.add(function(ed, o) {
bgneal@312 9730 if (o.set)
bgneal@312 9731 handleCustom(ed, o);
bgneal@312 9732 });
bgneal@312 9733 }
bgneal@312 9734
bgneal@312 9735 if (s.handle_node_change_callback) {
bgneal@312 9736 t.onNodeChange.add(function(ed, cm, n) {
bgneal@312 9737 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
bgneal@312 9738 });
bgneal@312 9739 }
bgneal@312 9740
bgneal@312 9741 if (s.save_callback) {
bgneal@312 9742 t.onSaveContent.add(function(ed, o) {
bgneal@312 9743 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@312 9744
bgneal@312 9745 if (h)
bgneal@312 9746 o.content = h;
bgneal@312 9747 });
bgneal@312 9748 }
bgneal@312 9749
bgneal@312 9750 if (s.onchange_callback) {
bgneal@312 9751 t.onChange.add(function(ed, l) {
bgneal@312 9752 t.execCallback('onchange_callback', t, l);
bgneal@312 9753 });
bgneal@312 9754 }
bgneal@312 9755
bgneal@312 9756 if (s.convert_newlines_to_brs) {
bgneal@312 9757 t.onBeforeSetContent.add(function(ed, o) {
bgneal@312 9758 if (o.initial)
bgneal@312 9759 o.content = o.content.replace(/\r?\n/g, '<br />');
bgneal@312 9760 });
bgneal@312 9761 }
bgneal@312 9762
bgneal@312 9763 if (s.fix_nesting && isIE) {
bgneal@312 9764 t.onBeforeSetContent.add(function(ed, o) {
bgneal@312 9765 o.content = t._fixNesting(o.content);
bgneal@312 9766 });
bgneal@312 9767 }
bgneal@312 9768
bgneal@312 9769 if (s.preformatted) {
bgneal@312 9770 t.onPostProcess.add(function(ed, o) {
bgneal@312 9771 o.content = o.content.replace(/^\s*<pre.*?>/, '');
bgneal@312 9772 o.content = o.content.replace(/<\/pre>\s*$/, '');
bgneal@312 9773
bgneal@312 9774 if (o.set)
bgneal@312 9775 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
bgneal@312 9776 });
bgneal@312 9777 }
bgneal@312 9778
bgneal@312 9779 if (s.verify_css_classes) {
bgneal@312 9780 t.serializer.attribValueFilter = function(n, v) {
bgneal@312 9781 var s, cl;
bgneal@312 9782
bgneal@312 9783 if (n == 'class') {
bgneal@312 9784 // Build regexp for classes
bgneal@312 9785 if (!t.classesRE) {
bgneal@312 9786 cl = t.dom.getClasses();
bgneal@312 9787
bgneal@312 9788 if (cl.length > 0) {
bgneal@312 9789 s = '';
bgneal@312 9790
bgneal@312 9791 each (cl, function(o) {
bgneal@312 9792 s += (s ? '|' : '') + o['class'];
bgneal@312 9793 });
bgneal@312 9794
bgneal@312 9795 t.classesRE = new RegExp('(' + s + ')', 'gi');
bgneal@312 9796 }
bgneal@312 9797 }
bgneal@312 9798
bgneal@312 9799 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
bgneal@312 9800 }
bgneal@312 9801
bgneal@312 9802 return v;
bgneal@312 9803 };
bgneal@312 9804 }
bgneal@312 9805
bgneal@312 9806 if (s.cleanup_callback) {
bgneal@312 9807 t.onBeforeSetContent.add(function(ed, o) {
bgneal@312 9808 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@312 9809 });
bgneal@312 9810
bgneal@312 9811 t.onPreProcess.add(function(ed, o) {
bgneal@312 9812 if (o.set)
bgneal@312 9813 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
bgneal@312 9814
bgneal@312 9815 if (o.get)
bgneal@312 9816 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
bgneal@312 9817 });
bgneal@312 9818
bgneal@312 9819 t.onPostProcess.add(function(ed, o) {
bgneal@312 9820 if (o.set)
bgneal@312 9821 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@312 9822
bgneal@312 9823 if (o.get)
bgneal@312 9824 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
bgneal@312 9825 });
bgneal@312 9826 }
bgneal@312 9827
bgneal@312 9828 if (s.save_callback) {
bgneal@312 9829 t.onGetContent.add(function(ed, o) {
bgneal@312 9830 if (o.save)
bgneal@312 9831 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@312 9832 });
bgneal@312 9833 }
bgneal@312 9834
bgneal@312 9835 if (s.handle_event_callback) {
bgneal@312 9836 t.onEvent.add(function(ed, e, o) {
bgneal@312 9837 if (t.execCallback('handle_event_callback', e, ed, o) === false)
bgneal@312 9838 Event.cancel(e);
bgneal@312 9839 });
bgneal@312 9840 }
bgneal@312 9841
bgneal@312 9842 // Add visual aids when new contents is added
bgneal@312 9843 t.onSetContent.add(function() {
bgneal@312 9844 t.addVisual(t.getBody());
bgneal@312 9845 });
bgneal@312 9846
bgneal@312 9847 // Remove empty contents
bgneal@312 9848 if (s.padd_empty_editor) {
bgneal@312 9849 t.onPostProcess.add(function(ed, o) {
bgneal@312 9850 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
bgneal@312 9851 });
bgneal@312 9852 }
bgneal@312 9853
bgneal@312 9854 if (isGecko) {
bgneal@312 9855 // Fix gecko link bug, when a link is placed at the end of block elements there is
bgneal@312 9856 // no way to move the caret behind the link. This fix adds a bogus br element after the link
bgneal@312 9857 function fixLinks(ed, o) {
bgneal@312 9858 each(ed.dom.select('a'), function(n) {
bgneal@312 9859 var pn = n.parentNode;
bgneal@312 9860
bgneal@312 9861 if (ed.dom.isBlock(pn) && pn.lastChild === n)
bgneal@312 9862 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
bgneal@312 9863 });
bgneal@312 9864 };
bgneal@312 9865
bgneal@312 9866 t.onExecCommand.add(function(ed, cmd) {
bgneal@312 9867 if (cmd === 'CreateLink')
bgneal@312 9868 fixLinks(ed);
bgneal@312 9869 });
bgneal@312 9870
bgneal@312 9871 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
bgneal@312 9872
bgneal@312 9873 if (!s.readonly) {
bgneal@312 9874 try {
bgneal@312 9875 // Design mode must be set here once again to fix a bug where
bgneal@312 9876 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
bgneal@312 9877 d.designMode = 'Off';
bgneal@312 9878 d.designMode = 'On';
bgneal@312 9879 } catch (ex) {
bgneal@312 9880 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@312 9881 // The design mode will be set ones the editor is focused
bgneal@312 9882 }
bgneal@312 9883 }
bgneal@312 9884 }
bgneal@312 9885
bgneal@312 9886 // A small timeout was needed since firefox will remove. Bug: #1838304
bgneal@312 9887 setTimeout(function () {
bgneal@312 9888 if (t.removed)
bgneal@312 9889 return;
bgneal@312 9890
bgneal@312 9891 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
bgneal@312 9892 t.startContent = t.getContent({format : 'raw'});
bgneal@312 9893 t.initialized = true;
bgneal@312 9894
bgneal@312 9895 t.onInit.dispatch(t);
bgneal@312 9896 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
bgneal@312 9897 t.execCallback('init_instance_callback', t);
bgneal@312 9898 t.focus(true);
bgneal@312 9899 t.nodeChanged({initial : 1});
bgneal@312 9900
bgneal@312 9901 // Load specified content CSS last
bgneal@312 9902 if (s.content_css) {
bgneal@312 9903 tinymce.each(explode(s.content_css), function(u) {
bgneal@312 9904 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
bgneal@312 9905 });
bgneal@312 9906 }
bgneal@312 9907
bgneal@312 9908 // Handle auto focus
bgneal@312 9909 if (s.auto_focus) {
bgneal@312 9910 setTimeout(function () {
bgneal@312 9911 var ed = tinymce.get(s.auto_focus);
bgneal@312 9912
bgneal@312 9913 ed.selection.select(ed.getBody(), 1);
bgneal@312 9914 ed.selection.collapse(1);
bgneal@312 9915 ed.getWin().focus();
bgneal@312 9916 }, 100);
bgneal@312 9917 }
bgneal@312 9918 }, 1);
bgneal@312 9919
bgneal@312 9920 e = null;
bgneal@312 9921 },
bgneal@312 9922
bgneal@312 9923
bgneal@312 9924 focus : function(sf) {
bgneal@312 9925 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
bgneal@312 9926
bgneal@312 9927 if (!sf) {
bgneal@312 9928 // Get selected control element
bgneal@312 9929 ieRng = t.selection.getRng();
bgneal@312 9930 if (ieRng.item) {
bgneal@312 9931 controlElm = ieRng.item(0);
bgneal@312 9932 }
bgneal@312 9933
bgneal@312 9934 // Is not content editable
bgneal@312 9935 if (!ce)
bgneal@312 9936 t.getWin().focus();
bgneal@312 9937
bgneal@312 9938 // Restore selected control element
bgneal@312 9939 // This is needed when for example an image is selected within a
bgneal@312 9940 // layer a call to focus will then remove the control selection
bgneal@312 9941 if (controlElm && controlElm.ownerDocument == doc) {
bgneal@312 9942 ieRng = doc.body.createControlRange();
bgneal@312 9943 ieRng.addElement(controlElm);
bgneal@312 9944 ieRng.select();
bgneal@312 9945 }
bgneal@312 9946
bgneal@312 9947 }
bgneal@312 9948
bgneal@312 9949 if (tinymce.activeEditor != t) {
bgneal@312 9950 if ((oed = tinymce.activeEditor) != null)
bgneal@312 9951 oed.onDeactivate.dispatch(oed, t);
bgneal@312 9952
bgneal@312 9953 t.onActivate.dispatch(t, oed);
bgneal@312 9954 }
bgneal@312 9955
bgneal@312 9956 tinymce._setActive(t);
bgneal@312 9957 },
bgneal@312 9958
bgneal@312 9959 execCallback : function(n) {
bgneal@312 9960 var t = this, f = t.settings[n], s;
bgneal@312 9961
bgneal@312 9962 if (!f)
bgneal@312 9963 return;
bgneal@312 9964
bgneal@312 9965 // Look through lookup
bgneal@312 9966 if (t.callbackLookup && (s = t.callbackLookup[n])) {
bgneal@312 9967 f = s.func;
bgneal@312 9968 s = s.scope;
bgneal@312 9969 }
bgneal@312 9970
bgneal@312 9971 if (is(f, 'string')) {
bgneal@312 9972 s = f.replace(/\.\w+$/, '');
bgneal@312 9973 s = s ? tinymce.resolve(s) : 0;
bgneal@312 9974 f = tinymce.resolve(f);
bgneal@312 9975 t.callbackLookup = t.callbackLookup || {};
bgneal@312 9976 t.callbackLookup[n] = {func : f, scope : s};
bgneal@312 9977 }
bgneal@312 9978
bgneal@312 9979 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
bgneal@312 9980 },
bgneal@312 9981
bgneal@312 9982 translate : function(s) {
bgneal@312 9983 var c = this.settings.language || 'en', i18n = tinymce.i18n;
bgneal@312 9984
bgneal@312 9985 if (!s)
bgneal@312 9986 return '';
bgneal@312 9987
bgneal@312 9988 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
bgneal@312 9989 return i18n[c + '.' + b] || '{#' + b + '}';
bgneal@312 9990 });
bgneal@312 9991 },
bgneal@312 9992
bgneal@312 9993 getLang : function(n, dv) {
bgneal@312 9994 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
bgneal@312 9995 },
bgneal@312 9996
bgneal@312 9997 getParam : function(n, dv, ty) {
bgneal@312 9998 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
bgneal@312 9999
bgneal@312 10000 if (ty === 'hash') {
bgneal@312 10001 o = {};
bgneal@312 10002
bgneal@312 10003 if (is(v, 'string')) {
bgneal@312 10004 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
bgneal@312 10005 v = v.split('=');
bgneal@312 10006
bgneal@312 10007 if (v.length > 1)
bgneal@312 10008 o[tr(v[0])] = tr(v[1]);
bgneal@312 10009 else
bgneal@312 10010 o[tr(v[0])] = tr(v);
bgneal@312 10011 });
bgneal@312 10012 } else
bgneal@312 10013 o = v;
bgneal@312 10014
bgneal@312 10015 return o;
bgneal@312 10016 }
bgneal@312 10017
bgneal@312 10018 return v;
bgneal@312 10019 },
bgneal@312 10020
bgneal@312 10021 nodeChanged : function(o) {
bgneal@312 10022 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
bgneal@312 10023
bgneal@312 10024 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
bgneal@312 10025 if (t.initialized) {
bgneal@312 10026 o = o || {};
bgneal@312 10027 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
bgneal@312 10028
bgneal@312 10029 // Get parents and add them to object
bgneal@312 10030 o.parents = [];
bgneal@312 10031 t.dom.getParent(n, function(node) {
bgneal@312 10032 if (node.nodeName == 'BODY')
bgneal@312 10033 return true;
bgneal@312 10034
bgneal@312 10035 o.parents.push(node);
bgneal@312 10036 });
bgneal@312 10037
bgneal@312 10038 t.onNodeChange.dispatch(
bgneal@312 10039 t,
bgneal@312 10040 o ? o.controlManager || t.controlManager : t.controlManager,
bgneal@312 10041 n,
bgneal@312 10042 s.isCollapsed(),
bgneal@312 10043 o
bgneal@312 10044 );
bgneal@312 10045 }
bgneal@312 10046 },
bgneal@312 10047
bgneal@312 10048 addButton : function(n, s) {
bgneal@312 10049 var t = this;
bgneal@312 10050
bgneal@312 10051 t.buttons = t.buttons || {};
bgneal@312 10052 t.buttons[n] = s;
bgneal@312 10053 },
bgneal@312 10054
bgneal@312 10055 addCommand : function(n, f, s) {
bgneal@312 10056 this.execCommands[n] = {func : f, scope : s || this};
bgneal@312 10057 },
bgneal@312 10058
bgneal@312 10059 addQueryStateHandler : function(n, f, s) {
bgneal@312 10060 this.queryStateCommands[n] = {func : f, scope : s || this};
bgneal@312 10061 },
bgneal@312 10062
bgneal@312 10063 addQueryValueHandler : function(n, f, s) {
bgneal@312 10064 this.queryValueCommands[n] = {func : f, scope : s || this};
bgneal@312 10065 },
bgneal@312 10066
bgneal@312 10067 addShortcut : function(pa, desc, cmd_func, sc) {
bgneal@312 10068 var t = this, c;
bgneal@312 10069
bgneal@312 10070 if (!t.settings.custom_shortcuts)
bgneal@312 10071 return false;
bgneal@312 10072
bgneal@312 10073 t.shortcuts = t.shortcuts || {};
bgneal@312 10074
bgneal@312 10075 if (is(cmd_func, 'string')) {
bgneal@312 10076 c = cmd_func;
bgneal@312 10077
bgneal@312 10078 cmd_func = function() {
bgneal@312 10079 t.execCommand(c, false, null);
bgneal@312 10080 };
bgneal@312 10081 }
bgneal@312 10082
bgneal@312 10083 if (is(cmd_func, 'object')) {
bgneal@312 10084 c = cmd_func;
bgneal@312 10085
bgneal@312 10086 cmd_func = function() {
bgneal@312 10087 t.execCommand(c[0], c[1], c[2]);
bgneal@312 10088 };
bgneal@312 10089 }
bgneal@312 10090
bgneal@312 10091 each(explode(pa), function(pa) {
bgneal@312 10092 var o = {
bgneal@312 10093 func : cmd_func,
bgneal@312 10094 scope : sc || this,
bgneal@312 10095 desc : desc,
bgneal@312 10096 alt : false,
bgneal@312 10097 ctrl : false,
bgneal@312 10098 shift : false
bgneal@312 10099 };
bgneal@312 10100
bgneal@312 10101 each(explode(pa, '+'), function(v) {
bgneal@312 10102 switch (v) {
bgneal@312 10103 case 'alt':
bgneal@312 10104 case 'ctrl':
bgneal@312 10105 case 'shift':
bgneal@312 10106 o[v] = true;
bgneal@312 10107 break;
bgneal@312 10108
bgneal@312 10109 default:
bgneal@312 10110 o.charCode = v.charCodeAt(0);
bgneal@312 10111 o.keyCode = v.toUpperCase().charCodeAt(0);
bgneal@312 10112 }
bgneal@312 10113 });
bgneal@312 10114
bgneal@312 10115 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
bgneal@312 10116 });
bgneal@312 10117
bgneal@312 10118 return true;
bgneal@312 10119 },
bgneal@312 10120
bgneal@312 10121 execCommand : function(cmd, ui, val, a) {
bgneal@312 10122 var t = this, s = 0, o, st;
bgneal@312 10123
bgneal@312 10124 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
bgneal@312 10125 t.focus();
bgneal@312 10126
bgneal@312 10127 o = {};
bgneal@312 10128 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
bgneal@312 10129 if (o.terminate)
bgneal@312 10130 return false;
bgneal@312 10131
bgneal@312 10132 // Command callback
bgneal@312 10133 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
bgneal@312 10134 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10135 return true;
bgneal@312 10136 }
bgneal@312 10137
bgneal@312 10138 // Registred commands
bgneal@312 10139 if (o = t.execCommands[cmd]) {
bgneal@312 10140 st = o.func.call(o.scope, ui, val);
bgneal@312 10141
bgneal@312 10142 // Fall through on true
bgneal@312 10143 if (st !== true) {
bgneal@312 10144 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10145 return st;
bgneal@312 10146 }
bgneal@312 10147 }
bgneal@312 10148
bgneal@312 10149 // Plugin commands
bgneal@312 10150 each(t.plugins, function(p) {
bgneal@312 10151 if (p.execCommand && p.execCommand(cmd, ui, val)) {
bgneal@312 10152 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10153 s = 1;
bgneal@312 10154 return false;
bgneal@312 10155 }
bgneal@312 10156 });
bgneal@312 10157
bgneal@312 10158 if (s)
bgneal@312 10159 return true;
bgneal@312 10160
bgneal@312 10161 // Theme commands
bgneal@312 10162 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
bgneal@312 10163 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10164 return true;
bgneal@312 10165 }
bgneal@312 10166
bgneal@312 10167 // Execute global commands
bgneal@312 10168 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
bgneal@312 10169 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10170 return true;
bgneal@312 10171 }
bgneal@312 10172
bgneal@312 10173 // Editor commands
bgneal@312 10174 if (t.editorCommands.execCommand(cmd, ui, val)) {
bgneal@312 10175 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10176 return true;
bgneal@312 10177 }
bgneal@312 10178
bgneal@312 10179 // Browser commands
bgneal@312 10180 t.getDoc().execCommand(cmd, ui, val);
bgneal@312 10181 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 10182 },
bgneal@312 10183
bgneal@312 10184 queryCommandState : function(cmd) {
bgneal@312 10185 var t = this, o, s;
bgneal@312 10186
bgneal@312 10187 // Is hidden then return undefined
bgneal@312 10188 if (t._isHidden())
bgneal@312 10189 return;
bgneal@312 10190
bgneal@312 10191 // Registred commands
bgneal@312 10192 if (o = t.queryStateCommands[cmd]) {
bgneal@312 10193 s = o.func.call(o.scope);
bgneal@312 10194
bgneal@312 10195 // Fall though on true
bgneal@312 10196 if (s !== true)
bgneal@312 10197 return s;
bgneal@312 10198 }
bgneal@312 10199
bgneal@312 10200 // Registred commands
bgneal@312 10201 o = t.editorCommands.queryCommandState(cmd);
bgneal@312 10202 if (o !== -1)
bgneal@312 10203 return o;
bgneal@312 10204
bgneal@312 10205 // Browser commands
bgneal@312 10206 try {
bgneal@312 10207 return this.getDoc().queryCommandState(cmd);
bgneal@312 10208 } catch (ex) {
bgneal@312 10209 // Fails sometimes see bug: 1896577
bgneal@312 10210 }
bgneal@312 10211 },
bgneal@312 10212
bgneal@312 10213 queryCommandValue : function(c) {
bgneal@312 10214 var t = this, o, s;
bgneal@312 10215
bgneal@312 10216 // Is hidden then return undefined
bgneal@312 10217 if (t._isHidden())
bgneal@312 10218 return;
bgneal@312 10219
bgneal@312 10220 // Registred commands
bgneal@312 10221 if (o = t.queryValueCommands[c]) {
bgneal@312 10222 s = o.func.call(o.scope);
bgneal@312 10223
bgneal@312 10224 // Fall though on true
bgneal@312 10225 if (s !== true)
bgneal@312 10226 return s;
bgneal@312 10227 }
bgneal@312 10228
bgneal@312 10229 // Registred commands
bgneal@312 10230 o = t.editorCommands.queryCommandValue(c);
bgneal@312 10231 if (is(o))
bgneal@312 10232 return o;
bgneal@312 10233
bgneal@312 10234 // Browser commands
bgneal@312 10235 try {
bgneal@312 10236 return this.getDoc().queryCommandValue(c);
bgneal@312 10237 } catch (ex) {
bgneal@312 10238 // Fails sometimes see bug: 1896577
bgneal@312 10239 }
bgneal@312 10240 },
bgneal@312 10241
bgneal@312 10242 show : function() {
bgneal@312 10243 var t = this;
bgneal@312 10244
bgneal@312 10245 DOM.show(t.getContainer());
bgneal@312 10246 DOM.hide(t.id);
bgneal@312 10247 t.load();
bgneal@312 10248 },
bgneal@312 10249
bgneal@312 10250 hide : function() {
bgneal@312 10251 var t = this, d = t.getDoc();
bgneal@312 10252
bgneal@312 10253 // Fixed bug where IE has a blinking cursor left from the editor
bgneal@312 10254 if (isIE && d)
bgneal@312 10255 d.execCommand('SelectAll');
bgneal@312 10256
bgneal@312 10257 // We must save before we hide so Safari doesn't crash
bgneal@312 10258 t.save();
bgneal@312 10259 DOM.hide(t.getContainer());
bgneal@312 10260 DOM.setStyle(t.id, 'display', t.orgDisplay);
bgneal@312 10261 },
bgneal@312 10262
bgneal@312 10263 isHidden : function() {
bgneal@312 10264 return !DOM.isHidden(this.id);
bgneal@312 10265 },
bgneal@312 10266
bgneal@312 10267 setProgressState : function(b, ti, o) {
bgneal@312 10268 this.onSetProgressState.dispatch(this, b, ti, o);
bgneal@312 10269
bgneal@312 10270 return b;
bgneal@312 10271 },
bgneal@312 10272
bgneal@312 10273 load : function(o) {
bgneal@312 10274 var t = this, e = t.getElement(), h;
bgneal@312 10275
bgneal@312 10276 if (e) {
bgneal@312 10277 o = o || {};
bgneal@312 10278 o.load = true;
bgneal@312 10279
bgneal@312 10280 // Double encode existing entities in the value
bgneal@312 10281 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
bgneal@312 10282 o.element = e;
bgneal@312 10283
bgneal@312 10284 if (!o.no_events)
bgneal@312 10285 t.onLoadContent.dispatch(t, o);
bgneal@312 10286
bgneal@312 10287 o.element = e = null;
bgneal@312 10288
bgneal@312 10289 return h;
bgneal@312 10290 }
bgneal@312 10291 },
bgneal@312 10292
bgneal@312 10293 save : function(o) {
bgneal@312 10294 var t = this, e = t.getElement(), h, f;
bgneal@312 10295
bgneal@312 10296 if (!e || !t.initialized)
bgneal@312 10297 return;
bgneal@312 10298
bgneal@312 10299 o = o || {};
bgneal@312 10300 o.save = true;
bgneal@312 10301
bgneal@312 10302 // Add undo level will trigger onchange event
bgneal@312 10303 if (!o.no_events) {
bgneal@312 10304 t.undoManager.typing = 0;
bgneal@312 10305 t.undoManager.add();
bgneal@312 10306 }
bgneal@312 10307
bgneal@312 10308 o.element = e;
bgneal@312 10309 h = o.content = t.getContent(o);
bgneal@312 10310
bgneal@312 10311 if (!o.no_events)
bgneal@312 10312 t.onSaveContent.dispatch(t, o);
bgneal@312 10313
bgneal@312 10314 h = o.content;
bgneal@312 10315
bgneal@312 10316 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
bgneal@312 10317 e.innerHTML = h;
bgneal@312 10318
bgneal@312 10319 // Update hidden form element
bgneal@312 10320 if (f = DOM.getParent(t.id, 'form')) {
bgneal@312 10321 each(f.elements, function(e) {
bgneal@312 10322 if (e.name == t.id) {
bgneal@312 10323 e.value = h;
bgneal@312 10324 return false;
bgneal@312 10325 }
bgneal@312 10326 });
bgneal@312 10327 }
bgneal@312 10328 } else
bgneal@312 10329 e.value = h;
bgneal@312 10330
bgneal@312 10331 o.element = e = null;
bgneal@312 10332
bgneal@312 10333 return h;
bgneal@312 10334 },
bgneal@312 10335
bgneal@312 10336 setContent : function(h, o) {
bgneal@312 10337 var t = this;
bgneal@312 10338
bgneal@312 10339 o = o || {};
bgneal@312 10340 o.format = o.format || 'html';
bgneal@312 10341 o.set = true;
bgneal@312 10342 o.content = h;
bgneal@312 10343
bgneal@312 10344 if (!o.no_events)
bgneal@312 10345 t.onBeforeSetContent.dispatch(t, o);
bgneal@312 10346
bgneal@312 10347 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
bgneal@312 10348 // It will also be impossible to place the caret in the editor unless there is a BR element present
bgneal@312 10349 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
bgneal@312 10350 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
bgneal@312 10351 o.format = 'raw';
bgneal@312 10352 }
bgneal@312 10353
bgneal@312 10354 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
bgneal@312 10355
bgneal@312 10356 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@312 10357 o.getInner = true;
bgneal@312 10358 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
bgneal@312 10359 }
bgneal@312 10360
bgneal@312 10361 if (!o.no_events)
bgneal@312 10362 t.onSetContent.dispatch(t, o);
bgneal@312 10363
bgneal@312 10364 return o.content;
bgneal@312 10365 },
bgneal@312 10366
bgneal@312 10367 getContent : function(o) {
bgneal@312 10368 var t = this, h;
bgneal@312 10369
bgneal@312 10370 o = o || {};
bgneal@312 10371 o.format = o.format || 'html';
bgneal@312 10372 o.get = true;
bgneal@312 10373
bgneal@312 10374 if (!o.no_events)
bgneal@312 10375 t.onBeforeGetContent.dispatch(t, o);
bgneal@312 10376
bgneal@312 10377 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@312 10378 o.getInner = true;
bgneal@312 10379 h = t.serializer.serialize(t.getBody(), o);
bgneal@312 10380 } else
bgneal@312 10381 h = t.getBody().innerHTML;
bgneal@312 10382
bgneal@312 10383 h = h.replace(/^\s*|\s*$/g, '');
bgneal@312 10384 o.content = h;
bgneal@312 10385
bgneal@312 10386 if (!o.no_events)
bgneal@312 10387 t.onGetContent.dispatch(t, o);
bgneal@312 10388
bgneal@312 10389 return o.content;
bgneal@312 10390 },
bgneal@312 10391
bgneal@312 10392 isDirty : function() {
bgneal@312 10393 var t = this;
bgneal@312 10394
bgneal@312 10395 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
bgneal@312 10396 },
bgneal@312 10397
bgneal@312 10398 getContainer : function() {
bgneal@312 10399 var t = this;
bgneal@312 10400
bgneal@312 10401 if (!t.container)
bgneal@312 10402 t.container = DOM.get(t.editorContainer || t.id + '_parent');
bgneal@312 10403
bgneal@312 10404 return t.container;
bgneal@312 10405 },
bgneal@312 10406
bgneal@312 10407 getContentAreaContainer : function() {
bgneal@312 10408 return this.contentAreaContainer;
bgneal@312 10409 },
bgneal@312 10410
bgneal@312 10411 getElement : function() {
bgneal@312 10412 return DOM.get(this.settings.content_element || this.id);
bgneal@312 10413 },
bgneal@312 10414
bgneal@312 10415 getWin : function() {
bgneal@312 10416 var t = this, e;
bgneal@312 10417
bgneal@312 10418 if (!t.contentWindow) {
bgneal@312 10419 e = DOM.get(t.id + "_ifr");
bgneal@312 10420
bgneal@312 10421 if (e)
bgneal@312 10422 t.contentWindow = e.contentWindow;
bgneal@312 10423 }
bgneal@312 10424
bgneal@312 10425 return t.contentWindow;
bgneal@312 10426 },
bgneal@312 10427
bgneal@312 10428 getDoc : function() {
bgneal@312 10429 var t = this, w;
bgneal@312 10430
bgneal@312 10431 if (!t.contentDocument) {
bgneal@312 10432 w = t.getWin();
bgneal@312 10433
bgneal@312 10434 if (w)
bgneal@312 10435 t.contentDocument = w.document;
bgneal@312 10436 }
bgneal@312 10437
bgneal@312 10438 return t.contentDocument;
bgneal@312 10439 },
bgneal@312 10440
bgneal@312 10441 getBody : function() {
bgneal@312 10442 return this.bodyElement || this.getDoc().body;
bgneal@312 10443 },
bgneal@312 10444
bgneal@312 10445 convertURL : function(u, n, e) {
bgneal@312 10446 var t = this, s = t.settings;
bgneal@312 10447
bgneal@312 10448 // Use callback instead
bgneal@312 10449 if (s.urlconverter_callback)
bgneal@312 10450 return t.execCallback('urlconverter_callback', u, e, true, n);
bgneal@312 10451
bgneal@312 10452 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
bgneal@312 10453 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
bgneal@312 10454 return u;
bgneal@312 10455
bgneal@312 10456 // Convert to relative
bgneal@312 10457 if (s.relative_urls)
bgneal@312 10458 return t.documentBaseURI.toRelative(u);
bgneal@312 10459
bgneal@312 10460 // Convert to absolute
bgneal@312 10461 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
bgneal@312 10462
bgneal@312 10463 return u;
bgneal@312 10464 },
bgneal@312 10465
bgneal@312 10466 addVisual : function(e) {
bgneal@312 10467 var t = this, s = t.settings;
bgneal@312 10468
bgneal@312 10469 e = e || t.getBody();
bgneal@312 10470
bgneal@312 10471 if (!is(t.hasVisual))
bgneal@312 10472 t.hasVisual = s.visual;
bgneal@312 10473
bgneal@312 10474 each(t.dom.select('table,a', e), function(e) {
bgneal@312 10475 var v;
bgneal@312 10476
bgneal@312 10477 switch (e.nodeName) {
bgneal@312 10478 case 'TABLE':
bgneal@312 10479 v = t.dom.getAttrib(e, 'border');
bgneal@312 10480
bgneal@312 10481 if (!v || v == '0') {
bgneal@312 10482 if (t.hasVisual)
bgneal@312 10483 t.dom.addClass(e, s.visual_table_class);
bgneal@312 10484 else
bgneal@312 10485 t.dom.removeClass(e, s.visual_table_class);
bgneal@312 10486 }
bgneal@312 10487
bgneal@312 10488 return;
bgneal@312 10489
bgneal@312 10490 case 'A':
bgneal@312 10491 v = t.dom.getAttrib(e, 'name');
bgneal@312 10492
bgneal@312 10493 if (v) {
bgneal@312 10494 if (t.hasVisual)
bgneal@312 10495 t.dom.addClass(e, 'mceItemAnchor');
bgneal@312 10496 else
bgneal@312 10497 t.dom.removeClass(e, 'mceItemAnchor');
bgneal@312 10498 }
bgneal@312 10499
bgneal@312 10500 return;
bgneal@312 10501 }
bgneal@312 10502 });
bgneal@312 10503
bgneal@312 10504 t.onVisualAid.dispatch(t, e, t.hasVisual);
bgneal@312 10505 },
bgneal@312 10506
bgneal@312 10507 remove : function() {
bgneal@312 10508 var t = this, e = t.getContainer();
bgneal@312 10509
bgneal@312 10510 t.removed = 1; // Cancels post remove event execution
bgneal@312 10511 t.hide();
bgneal@312 10512
bgneal@312 10513 t.execCallback('remove_instance_callback', t);
bgneal@312 10514 t.onRemove.dispatch(t);
bgneal@312 10515
bgneal@312 10516 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
bgneal@312 10517 t.onExecCommand.listeners = [];
bgneal@312 10518
bgneal@312 10519 tinymce.remove(t);
bgneal@312 10520 DOM.remove(e);
bgneal@312 10521 },
bgneal@312 10522
bgneal@312 10523 destroy : function(s) {
bgneal@312 10524 var t = this;
bgneal@312 10525
bgneal@312 10526 // One time is enough
bgneal@312 10527 if (t.destroyed)
bgneal@312 10528 return;
bgneal@312 10529
bgneal@312 10530 if (!s) {
bgneal@312 10531 tinymce.removeUnload(t.destroy);
bgneal@312 10532 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
bgneal@312 10533
bgneal@312 10534 // Manual destroy
bgneal@312 10535 if (t.theme && t.theme.destroy)
bgneal@312 10536 t.theme.destroy();
bgneal@312 10537
bgneal@312 10538 // Destroy controls, selection and dom
bgneal@312 10539 t.controlManager.destroy();
bgneal@312 10540 t.selection.destroy();
bgneal@312 10541 t.dom.destroy();
bgneal@312 10542
bgneal@312 10543 // Remove all events
bgneal@312 10544
bgneal@312 10545 // Don't clear the window or document if content editable
bgneal@312 10546 // is enabled since other instances might still be present
bgneal@312 10547 if (!t.settings.content_editable) {
bgneal@312 10548 Event.clear(t.getWin());
bgneal@312 10549 Event.clear(t.getDoc());
bgneal@312 10550 }
bgneal@312 10551
bgneal@312 10552 Event.clear(t.getBody());
bgneal@312 10553 Event.clear(t.formElement);
bgneal@312 10554 }
bgneal@312 10555
bgneal@312 10556 if (t.formElement) {
bgneal@312 10557 t.formElement.submit = t.formElement._mceOldSubmit;
bgneal@312 10558 t.formElement._mceOldSubmit = null;
bgneal@312 10559 }
bgneal@312 10560
bgneal@312 10561 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
bgneal@312 10562
bgneal@312 10563 if (t.selection)
bgneal@312 10564 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
bgneal@312 10565
bgneal@312 10566 t.destroyed = 1;
bgneal@312 10567 },
bgneal@312 10568
bgneal@312 10569 // Internal functions
bgneal@312 10570
bgneal@312 10571 _addEvents : function() {
bgneal@312 10572 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
bgneal@312 10573 var t = this, i, s = t.settings, lo = {
bgneal@312 10574 mouseup : 'onMouseUp',
bgneal@312 10575 mousedown : 'onMouseDown',
bgneal@312 10576 click : 'onClick',
bgneal@312 10577 keyup : 'onKeyUp',
bgneal@312 10578 keydown : 'onKeyDown',
bgneal@312 10579 keypress : 'onKeyPress',
bgneal@312 10580 submit : 'onSubmit',
bgneal@312 10581 reset : 'onReset',
bgneal@312 10582 contextmenu : 'onContextMenu',
bgneal@312 10583 dblclick : 'onDblClick',
bgneal@312 10584 paste : 'onPaste' // Doesn't work in all browsers yet
bgneal@312 10585 };
bgneal@312 10586
bgneal@312 10587 function eventHandler(e, o) {
bgneal@312 10588 var ty = e.type;
bgneal@312 10589
bgneal@312 10590 // Don't fire events when it's removed
bgneal@312 10591 if (t.removed)
bgneal@312 10592 return;
bgneal@312 10593
bgneal@312 10594 // Generic event handler
bgneal@312 10595 if (t.onEvent.dispatch(t, e, o) !== false) {
bgneal@312 10596 // Specific event handler
bgneal@312 10597 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
bgneal@312 10598 }
bgneal@312 10599 };
bgneal@312 10600
bgneal@312 10601 // Add DOM events
bgneal@312 10602 each(lo, function(v, k) {
bgneal@312 10603 switch (k) {
bgneal@312 10604 case 'contextmenu':
bgneal@312 10605 if (tinymce.isOpera) {
bgneal@312 10606 // Fake contextmenu on Opera
bgneal@312 10607 t.dom.bind(t.getBody(), 'mousedown', function(e) {
bgneal@312 10608 if (e.ctrlKey) {
bgneal@312 10609 e.fakeType = 'contextmenu';
bgneal@312 10610 eventHandler(e);
bgneal@312 10611 }
bgneal@312 10612 });
bgneal@312 10613 } else
bgneal@312 10614 t.dom.bind(t.getBody(), k, eventHandler);
bgneal@312 10615 break;
bgneal@312 10616
bgneal@312 10617 case 'paste':
bgneal@312 10618 t.dom.bind(t.getBody(), k, function(e) {
bgneal@312 10619 eventHandler(e);
bgneal@312 10620 });
bgneal@312 10621 break;
bgneal@312 10622
bgneal@312 10623 case 'submit':
bgneal@312 10624 case 'reset':
bgneal@312 10625 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
bgneal@312 10626 break;
bgneal@312 10627
bgneal@312 10628 default:
bgneal@312 10629 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
bgneal@312 10630 }
bgneal@312 10631 });
bgneal@312 10632
bgneal@312 10633 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
bgneal@312 10634 t.focus(true);
bgneal@312 10635 });
bgneal@312 10636
bgneal@312 10637
bgneal@312 10638 // Fixes bug where a specified document_base_uri could result in broken images
bgneal@312 10639 // This will also fix drag drop of images in Gecko
bgneal@312 10640 if (tinymce.isGecko) {
bgneal@312 10641 // Convert all images to absolute URLs
bgneal@312 10642 /* t.onSetContent.add(function(ed, o) {
bgneal@312 10643 each(ed.dom.select('img'), function(e) {
bgneal@312 10644 var v;
bgneal@312 10645
bgneal@312 10646 if (v = e.getAttribute('_mce_src'))
bgneal@312 10647 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@312 10648 })
bgneal@312 10649 });*/
bgneal@312 10650
bgneal@312 10651 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
bgneal@312 10652 var v;
bgneal@312 10653
bgneal@312 10654 e = e.target;
bgneal@312 10655
bgneal@312 10656 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
bgneal@312 10657 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@312 10658 });
bgneal@312 10659 }
bgneal@312 10660
bgneal@312 10661 // Set various midas options in Gecko
bgneal@312 10662 if (isGecko) {
bgneal@312 10663 function setOpts() {
bgneal@312 10664 var t = this, d = t.getDoc(), s = t.settings;
bgneal@312 10665
bgneal@312 10666 if (isGecko && !s.readonly) {
bgneal@312 10667 if (t._isHidden()) {
bgneal@312 10668 try {
bgneal@312 10669 if (!s.content_editable)
bgneal@312 10670 d.designMode = 'On';
bgneal@312 10671 } catch (ex) {
bgneal@312 10672 // Fails if it's hidden
bgneal@312 10673 }
bgneal@312 10674 }
bgneal@312 10675
bgneal@312 10676 try {
bgneal@312 10677 // Try new Gecko method
bgneal@312 10678 d.execCommand("styleWithCSS", 0, false);
bgneal@312 10679 } catch (ex) {
bgneal@312 10680 // Use old method
bgneal@312 10681 if (!t._isHidden())
bgneal@312 10682 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
bgneal@312 10683 }
bgneal@312 10684
bgneal@312 10685 if (!s.table_inline_editing)
bgneal@312 10686 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
bgneal@312 10687
bgneal@312 10688 if (!s.object_resizing)
bgneal@312 10689 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
bgneal@312 10690 }
bgneal@312 10691 };
bgneal@312 10692
bgneal@312 10693 t.onBeforeExecCommand.add(setOpts);
bgneal@312 10694 t.onMouseDown.add(setOpts);
bgneal@312 10695 }
bgneal@312 10696
bgneal@312 10697 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
bgneal@312 10698 // WebKit can't even do simple things like selecting an image
bgneal@312 10699 // This also fixes so it's possible to select mceItemAnchors
bgneal@312 10700 if (tinymce.isWebKit) {
bgneal@312 10701 t.onClick.add(function(ed, e) {
bgneal@312 10702 e = e.target;
bgneal@312 10703
bgneal@312 10704 // Needs tobe the setBaseAndExtend or it will fail to select floated images
bgneal@312 10705 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
bgneal@312 10706 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
bgneal@312 10707 });
bgneal@312 10708 }
bgneal@312 10709
bgneal@312 10710 // Add node change handlers
bgneal@312 10711 t.onMouseUp.add(t.nodeChanged);
bgneal@312 10712 //t.onClick.add(t.nodeChanged);
bgneal@312 10713 t.onKeyUp.add(function(ed, e) {
bgneal@312 10714 var c = e.keyCode;
bgneal@312 10715
bgneal@312 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@312 10717 t.nodeChanged();
bgneal@312 10718 });
bgneal@312 10719
bgneal@312 10720 // Add reset handler
bgneal@312 10721 t.onReset.add(function() {
bgneal@312 10722 t.setContent(t.startContent, {format : 'raw'});
bgneal@312 10723 });
bgneal@312 10724
bgneal@312 10725 // Add shortcuts
bgneal@312 10726 if (s.custom_shortcuts) {
bgneal@312 10727 if (s.custom_undo_redo_keyboard_shortcuts) {
bgneal@312 10728 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
bgneal@312 10729 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
bgneal@312 10730 }
bgneal@312 10731
bgneal@312 10732 // Add default shortcuts for gecko
bgneal@312 10733 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
bgneal@312 10734 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
bgneal@312 10735 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
bgneal@312 10736
bgneal@312 10737 // BlockFormat shortcuts keys
bgneal@312 10738 for (i=1; i<=6; i++)
bgneal@312 10739 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
bgneal@312 10740
bgneal@312 10741 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
bgneal@312 10742 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
bgneal@312 10743 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
bgneal@312 10744
bgneal@312 10745 function find(e) {
bgneal@312 10746 var v = null;
bgneal@312 10747
bgneal@312 10748 if (!e.altKey && !e.ctrlKey && !e.metaKey)
bgneal@312 10749 return v;
bgneal@312 10750
bgneal@312 10751 each(t.shortcuts, function(o) {
bgneal@312 10752 if (tinymce.isMac && o.ctrl != e.metaKey)
bgneal@312 10753 return;
bgneal@312 10754 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
bgneal@312 10755 return;
bgneal@312 10756
bgneal@312 10757 if (o.alt != e.altKey)
bgneal@312 10758 return;
bgneal@312 10759
bgneal@312 10760 if (o.shift != e.shiftKey)
bgneal@312 10761 return;
bgneal@312 10762
bgneal@312 10763 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
bgneal@312 10764 v = o;
bgneal@312 10765 return false;
bgneal@312 10766 }
bgneal@312 10767 });
bgneal@312 10768
bgneal@312 10769 return v;
bgneal@312 10770 };
bgneal@312 10771
bgneal@312 10772 t.onKeyUp.add(function(ed, e) {
bgneal@312 10773 var o = find(e);
bgneal@312 10774
bgneal@312 10775 if (o)
bgneal@312 10776 return Event.cancel(e);
bgneal@312 10777 });
bgneal@312 10778
bgneal@312 10779 t.onKeyPress.add(function(ed, e) {
bgneal@312 10780 var o = find(e);
bgneal@312 10781
bgneal@312 10782 if (o)
bgneal@312 10783 return Event.cancel(e);
bgneal@312 10784 });
bgneal@312 10785
bgneal@312 10786 t.onKeyDown.add(function(ed, e) {
bgneal@312 10787 var o = find(e);
bgneal@312 10788
bgneal@312 10789 if (o) {
bgneal@312 10790 o.func.call(o.scope);
bgneal@312 10791 return Event.cancel(e);
bgneal@312 10792 }
bgneal@312 10793 });
bgneal@312 10794 }
bgneal@312 10795
bgneal@312 10796 if (tinymce.isIE) {
bgneal@312 10797 // Fix so resize will only update the width and height attributes not the styles of an image
bgneal@312 10798 // It will also block mceItemNoResize items
bgneal@312 10799 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
bgneal@312 10800 var re = t.resizeInfo, cb;
bgneal@312 10801
bgneal@312 10802 e = e.target;
bgneal@312 10803
bgneal@312 10804 // Don't do this action for non image elements
bgneal@312 10805 if (e.nodeName !== 'IMG')
bgneal@312 10806 return;
bgneal@312 10807
bgneal@312 10808 if (re)
bgneal@312 10809 t.dom.unbind(re.node, re.ev, re.cb);
bgneal@312 10810
bgneal@312 10811 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
bgneal@312 10812 ev = 'resizeend';
bgneal@312 10813 cb = t.dom.bind(e, ev, function(e) {
bgneal@312 10814 var v;
bgneal@312 10815
bgneal@312 10816 e = e.target;
bgneal@312 10817
bgneal@312 10818 if (v = t.dom.getStyle(e, 'width')) {
bgneal@312 10819 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
bgneal@312 10820 t.dom.setStyle(e, 'width', '');
bgneal@312 10821 }
bgneal@312 10822
bgneal@312 10823 if (v = t.dom.getStyle(e, 'height')) {
bgneal@312 10824 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
bgneal@312 10825 t.dom.setStyle(e, 'height', '');
bgneal@312 10826 }
bgneal@312 10827 });
bgneal@312 10828 } else {
bgneal@312 10829 ev = 'resizestart';
bgneal@312 10830 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
bgneal@312 10831 }
bgneal@312 10832
bgneal@312 10833 re = t.resizeInfo = {
bgneal@312 10834 node : e,
bgneal@312 10835 ev : ev,
bgneal@312 10836 cb : cb
bgneal@312 10837 };
bgneal@312 10838 });
bgneal@312 10839
bgneal@312 10840 t.onKeyDown.add(function(ed, e) {
bgneal@312 10841 switch (e.keyCode) {
bgneal@312 10842 case 8:
bgneal@312 10843 // Fix IE control + backspace browser bug
bgneal@312 10844 if (t.selection.getRng().item) {
bgneal@312 10845 ed.dom.remove(t.selection.getRng().item(0));
bgneal@312 10846 return Event.cancel(e);
bgneal@312 10847 }
bgneal@312 10848 }
bgneal@312 10849 });
bgneal@312 10850
bgneal@312 10851 /*if (t.dom.boxModel) {
bgneal@312 10852 t.getBody().style.height = '100%';
bgneal@312 10853
bgneal@312 10854 Event.add(t.getWin(), 'resize', function(e) {
bgneal@312 10855 var docElm = t.getDoc().documentElement;
bgneal@312 10856
bgneal@312 10857 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
bgneal@312 10858 });
bgneal@312 10859 }*/
bgneal@312 10860 }
bgneal@312 10861
bgneal@312 10862 if (tinymce.isOpera) {
bgneal@312 10863 t.onClick.add(function(ed, e) {
bgneal@312 10864 Event.prevent(e);
bgneal@312 10865 });
bgneal@312 10866 }
bgneal@312 10867
bgneal@312 10868 // Add custom undo/redo handlers
bgneal@312 10869 if (s.custom_undo_redo) {
bgneal@312 10870 function addUndo() {
bgneal@312 10871 t.undoManager.typing = 0;
bgneal@312 10872 t.undoManager.add();
bgneal@312 10873 };
bgneal@312 10874
bgneal@312 10875 t.dom.bind(t.getDoc(), 'focusout', function(e) {
bgneal@312 10876 if (!t.removed && t.undoManager.typing)
bgneal@312 10877 addUndo();
bgneal@312 10878 });
bgneal@312 10879
bgneal@312 10880 t.onKeyUp.add(function(ed, e) {
bgneal@312 10881 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
bgneal@312 10882 addUndo();
bgneal@312 10883 });
bgneal@312 10884
bgneal@312 10885 t.onKeyDown.add(function(ed, e) {
bgneal@312 10886 var rng, parent, bookmark;
bgneal@312 10887
bgneal@312 10888 // IE has a really odd bug where the DOM might include an node that doesn't have
bgneal@312 10889 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
bgneal@312 10890 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
bgneal@312 10891 // after you delete contents from it. See: #3008923
bgneal@312 10892 if (isIE && e.keyCode == 46) {
bgneal@312 10893 rng = t.selection.getRng();
bgneal@312 10894
bgneal@312 10895 if (rng.parentElement) {
bgneal@312 10896 parent = rng.parentElement();
bgneal@312 10897
bgneal@312 10898 // Select next word when ctrl key is used in combo with delete
bgneal@312 10899 if (e.ctrlKey) {
bgneal@312 10900 rng.moveEnd('word', 1);
bgneal@312 10901 rng.select();
bgneal@312 10902 }
bgneal@312 10903
bgneal@312 10904 // Delete contents
bgneal@312 10905 t.selection.getSel().clear();
bgneal@312 10906
bgneal@312 10907 // Check if we are within the same parent
bgneal@312 10908 if (rng.parentElement() == parent) {
bgneal@312 10909 bookmark = t.selection.getBookmark();
bgneal@312 10910
bgneal@312 10911 try {
bgneal@312 10912 // Update the HTML and hopefully it will remove the artifacts
bgneal@312 10913 parent.innerHTML = parent.innerHTML;
bgneal@312 10914 } catch (ex) {
bgneal@312 10915 // And since it's IE it can sometimes produce an unknown runtime error
bgneal@312 10916 }
bgneal@312 10917
bgneal@312 10918 // Restore the caret position
bgneal@312 10919 t.selection.moveToBookmark(bookmark);
bgneal@312 10920 }
bgneal@312 10921
bgneal@312 10922 // Block the default delete behavior since it might be broken
bgneal@312 10923 e.preventDefault();
bgneal@312 10924 return;
bgneal@312 10925 }
bgneal@312 10926 }
bgneal@312 10927
bgneal@312 10928 // Is caracter positon keys
bgneal@312 10929 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
bgneal@312 10930 if (t.undoManager.typing)
bgneal@312 10931 addUndo();
bgneal@312 10932
bgneal@312 10933 return;
bgneal@312 10934 }
bgneal@312 10935
bgneal@312 10936 if (!t.undoManager.typing) {
bgneal@312 10937 t.undoManager.add();
bgneal@312 10938 t.undoManager.typing = 1;
bgneal@312 10939 }
bgneal@312 10940 });
bgneal@312 10941
bgneal@312 10942 t.onMouseDown.add(function() {
bgneal@312 10943 if (t.undoManager.typing)
bgneal@312 10944 addUndo();
bgneal@312 10945 });
bgneal@312 10946 }
bgneal@312 10947 },
bgneal@312 10948
bgneal@312 10949 _isHidden : function() {
bgneal@312 10950 var s;
bgneal@312 10951
bgneal@312 10952 if (!isGecko)
bgneal@312 10953 return 0;
bgneal@312 10954
bgneal@312 10955 // Weird, wheres that cursor selection?
bgneal@312 10956 s = this.selection.getSel();
bgneal@312 10957 return (!s || !s.rangeCount || s.rangeCount == 0);
bgneal@312 10958 },
bgneal@312 10959
bgneal@312 10960 // Fix for bug #1867292
bgneal@312 10961 _fixNesting : function(s) {
bgneal@312 10962 var d = [], i;
bgneal@312 10963
bgneal@312 10964 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
bgneal@312 10965 var e;
bgneal@312 10966
bgneal@312 10967 // Handle end element
bgneal@312 10968 if (b === '/') {
bgneal@312 10969 if (!d.length)
bgneal@312 10970 return '';
bgneal@312 10971
bgneal@312 10972 if (c !== d[d.length - 1].tag) {
bgneal@312 10973 for (i=d.length - 1; i>=0; i--) {
bgneal@312 10974 if (d[i].tag === c) {
bgneal@312 10975 d[i].close = 1;
bgneal@312 10976 break;
bgneal@312 10977 }
bgneal@312 10978 }
bgneal@312 10979
bgneal@312 10980 return '';
bgneal@312 10981 } else {
bgneal@312 10982 d.pop();
bgneal@312 10983
bgneal@312 10984 if (d.length && d[d.length - 1].close) {
bgneal@312 10985 a = a + '</' + d[d.length - 1].tag + '>';
bgneal@312 10986 d.pop();
bgneal@312 10987 }
bgneal@312 10988 }
bgneal@312 10989 } else {
bgneal@312 10990 // Ignore these
bgneal@312 10991 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
bgneal@312 10992 return a;
bgneal@312 10993
bgneal@312 10994 // Ignore closed ones
bgneal@312 10995 if (/\/>$/.test(a))
bgneal@312 10996 return a;
bgneal@312 10997
bgneal@312 10998 d.push({tag : c}); // Push start element
bgneal@312 10999 }
bgneal@312 11000
bgneal@312 11001 return a;
bgneal@312 11002 });
bgneal@312 11003
bgneal@312 11004 // End all open tags
bgneal@312 11005 for (i=d.length - 1; i>=0; i--)
bgneal@312 11006 s += '</' + d[i].tag + '>';
bgneal@312 11007
bgneal@312 11008 return s;
bgneal@312 11009 }
bgneal@312 11010 });
bgneal@312 11011 })(tinymce);
bgneal@312 11012
bgneal@312 11013 (function(tinymce) {
bgneal@312 11014 // Added for compression purposes
bgneal@312 11015 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
bgneal@312 11016
bgneal@312 11017 tinymce.EditorCommands = function(editor) {
bgneal@312 11018 var dom = editor.dom,
bgneal@312 11019 selection = editor.selection,
bgneal@312 11020 commands = {state: {}, exec : {}, value : {}},
bgneal@312 11021 settings = editor.settings,
bgneal@312 11022 bookmark;
bgneal@312 11023
bgneal@312 11024 function execCommand(command, ui, value) {
bgneal@312 11025 var func;
bgneal@312 11026
bgneal@312 11027 command = command.toLowerCase();
bgneal@312 11028 if (func = commands.exec[command]) {
bgneal@312 11029 func(command, ui, value);
bgneal@312 11030 return TRUE;
bgneal@312 11031 }
bgneal@312 11032
bgneal@312 11033 return FALSE;
bgneal@312 11034 };
bgneal@312 11035
bgneal@312 11036 function queryCommandState(command) {
bgneal@312 11037 var func;
bgneal@312 11038
bgneal@312 11039 command = command.toLowerCase();
bgneal@312 11040 if (func = commands.state[command])
bgneal@312 11041 return func(command);
bgneal@312 11042
bgneal@312 11043 return -1;
bgneal@312 11044 };
bgneal@312 11045
bgneal@312 11046 function queryCommandValue(command) {
bgneal@312 11047 var func;
bgneal@312 11048
bgneal@312 11049 command = command.toLowerCase();
bgneal@312 11050 if (func = commands.value[command])
bgneal@312 11051 return func(command);
bgneal@312 11052
bgneal@312 11053 return FALSE;
bgneal@312 11054 };
bgneal@312 11055
bgneal@312 11056 function addCommands(command_list, type) {
bgneal@312 11057 type = type || 'exec';
bgneal@312 11058
bgneal@312 11059 each(command_list, function(callback, command) {
bgneal@312 11060 each(command.toLowerCase().split(','), function(command) {
bgneal@312 11061 commands[type][command] = callback;
bgneal@312 11062 });
bgneal@312 11063 });
bgneal@312 11064 };
bgneal@312 11065
bgneal@312 11066 // Expose public methods
bgneal@312 11067 tinymce.extend(this, {
bgneal@312 11068 execCommand : execCommand,
bgneal@312 11069 queryCommandState : queryCommandState,
bgneal@312 11070 queryCommandValue : queryCommandValue,
bgneal@312 11071 addCommands : addCommands
bgneal@312 11072 });
bgneal@312 11073
bgneal@312 11074 // Private methods
bgneal@312 11075
bgneal@312 11076 function execNativeCommand(command, ui, value) {
bgneal@312 11077 if (ui === undefined)
bgneal@312 11078 ui = FALSE;
bgneal@312 11079
bgneal@312 11080 if (value === undefined)
bgneal@312 11081 value = null;
bgneal@312 11082
bgneal@312 11083 return editor.getDoc().execCommand(command, ui, value);
bgneal@312 11084 };
bgneal@312 11085
bgneal@312 11086 function isFormatMatch(name) {
bgneal@312 11087 return editor.formatter.match(name);
bgneal@312 11088 };
bgneal@312 11089
bgneal@312 11090 function toggleFormat(name, value) {
bgneal@312 11091 editor.formatter.toggle(name, value ? {value : value} : undefined);
bgneal@312 11092 };
bgneal@312 11093
bgneal@312 11094 function storeSelection(type) {
bgneal@312 11095 bookmark = selection.getBookmark(type);
bgneal@312 11096 };
bgneal@312 11097
bgneal@312 11098 function restoreSelection() {
bgneal@312 11099 selection.moveToBookmark(bookmark);
bgneal@312 11100 };
bgneal@312 11101
bgneal@312 11102 // Add execCommand overrides
bgneal@312 11103 addCommands({
bgneal@312 11104 // Ignore these, added for compatibility
bgneal@312 11105 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
bgneal@312 11106
bgneal@312 11107 // Add undo manager logic
bgneal@312 11108 'mceEndUndoLevel,mceAddUndoLevel' : function() {
bgneal@312 11109 editor.undoManager.add();
bgneal@312 11110 },
bgneal@312 11111
bgneal@312 11112 'Cut,Copy,Paste' : function(command) {
bgneal@312 11113 var doc = editor.getDoc(), failed;
bgneal@312 11114
bgneal@312 11115 // Try executing the native command
bgneal@312 11116 try {
bgneal@312 11117 execNativeCommand(command);
bgneal@312 11118 } catch (ex) {
bgneal@312 11119 // Command failed
bgneal@312 11120 failed = TRUE;
bgneal@312 11121 }
bgneal@312 11122
bgneal@312 11123 // Present alert message about clipboard access not being available
bgneal@312 11124 if (failed || !doc.queryCommandSupported(command)) {
bgneal@312 11125 if (tinymce.isGecko) {
bgneal@312 11126 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
bgneal@312 11127 if (state)
bgneal@312 11128 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
bgneal@312 11129 });
bgneal@312 11130 } else
bgneal@312 11131 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
bgneal@312 11132 }
bgneal@312 11133 },
bgneal@312 11134
bgneal@312 11135 // Override unlink command
bgneal@312 11136 unlink : function(command) {
bgneal@312 11137 if (selection.isCollapsed())
bgneal@312 11138 selection.select(selection.getNode());
bgneal@312 11139
bgneal@312 11140 execNativeCommand(command);
bgneal@312 11141 selection.collapse(FALSE);
bgneal@312 11142 },
bgneal@312 11143
bgneal@312 11144 // Override justify commands to use the text formatter engine
bgneal@312 11145 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@312 11146 var align = command.substring(7);
bgneal@312 11147
bgneal@312 11148 // Remove all other alignments first
bgneal@312 11149 each('left,center,right,full'.split(','), function(name) {
bgneal@312 11150 if (align != name)
bgneal@312 11151 editor.formatter.remove('align' + name);
bgneal@312 11152 });
bgneal@312 11153
bgneal@312 11154 toggleFormat('align' + align);
bgneal@312 11155 },
bgneal@312 11156
bgneal@312 11157 // Override list commands to fix WebKit bug
bgneal@312 11158 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@312 11159 var listElm, listParent;
bgneal@312 11160
bgneal@312 11161 execNativeCommand(command);
bgneal@312 11162
bgneal@312 11163 // WebKit produces lists within block elements so we need to split them
bgneal@312 11164 // we will replace the native list creation logic to custom logic later on
bgneal@312 11165 // TODO: Remove this when the list creation logic is removed
bgneal@312 11166 listElm = dom.getParent(selection.getNode(), 'ol,ul');
bgneal@312 11167 if (listElm) {
bgneal@312 11168 listParent = listElm.parentNode;
bgneal@312 11169
bgneal@312 11170 // If list is within a text block then split that block
bgneal@312 11171 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
bgneal@312 11172 storeSelection();
bgneal@312 11173 dom.split(listParent, listElm);
bgneal@312 11174 restoreSelection();
bgneal@312 11175 }
bgneal@312 11176 }
bgneal@312 11177 },
bgneal@312 11178
bgneal@312 11179 // Override commands to use the text formatter engine
bgneal@312 11180 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@312 11181 toggleFormat(command);
bgneal@312 11182 },
bgneal@312 11183
bgneal@312 11184 // Override commands to use the text formatter engine
bgneal@312 11185 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
bgneal@312 11186 toggleFormat(command, value);
bgneal@312 11187 },
bgneal@312 11188
bgneal@312 11189 FontSize : function(command, ui, value) {
bgneal@312 11190 var fontClasses, fontSizes;
bgneal@312 11191
bgneal@312 11192 // Convert font size 1-7 to styles
bgneal@312 11193 if (value >= 1 && value <= 7) {
bgneal@312 11194 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@312 11195 fontClasses = tinymce.explode(settings.font_size_classes);
bgneal@312 11196
bgneal@312 11197 if (fontClasses)
bgneal@312 11198 value = fontClasses[value - 1] || value;
bgneal@312 11199 else
bgneal@312 11200 value = fontSizes[value - 1] || value;
bgneal@312 11201 }
bgneal@312 11202
bgneal@312 11203 toggleFormat(command, value);
bgneal@312 11204 },
bgneal@312 11205
bgneal@312 11206 RemoveFormat : function(command) {
bgneal@312 11207 editor.formatter.remove(command);
bgneal@312 11208 },
bgneal@312 11209
bgneal@312 11210 mceBlockQuote : function(command) {
bgneal@312 11211 toggleFormat('blockquote');
bgneal@312 11212 },
bgneal@312 11213
bgneal@312 11214 FormatBlock : function(command, ui, value) {
bgneal@312 11215 return toggleFormat(value || 'p');
bgneal@312 11216 },
bgneal@312 11217
bgneal@312 11218 mceCleanup : function() {
bgneal@312 11219 var bookmark = selection.getBookmark();
bgneal@312 11220
bgneal@312 11221 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
bgneal@312 11222
bgneal@312 11223 selection.moveToBookmark(bookmark);
bgneal@312 11224 },
bgneal@312 11225
bgneal@312 11226 mceRemoveNode : function(command, ui, value) {
bgneal@312 11227 var node = value || selection.getNode();
bgneal@312 11228
bgneal@312 11229 // Make sure that the body node isn't removed
bgneal@312 11230 if (node != editor.getBody()) {
bgneal@312 11231 storeSelection();
bgneal@312 11232 editor.dom.remove(node, TRUE);
bgneal@312 11233 restoreSelection();
bgneal@312 11234 }
bgneal@312 11235 },
bgneal@312 11236
bgneal@312 11237 mceSelectNodeDepth : function(command, ui, value) {
bgneal@312 11238 var counter = 0;
bgneal@312 11239
bgneal@312 11240 dom.getParent(selection.getNode(), function(node) {
bgneal@312 11241 if (node.nodeType == 1 && counter++ == value) {
bgneal@312 11242 selection.select(node);
bgneal@312 11243 return FALSE;
bgneal@312 11244 }
bgneal@312 11245 }, editor.getBody());
bgneal@312 11246 },
bgneal@312 11247
bgneal@312 11248 mceSelectNode : function(command, ui, value) {
bgneal@312 11249 selection.select(value);
bgneal@312 11250 },
bgneal@312 11251
bgneal@312 11252 mceInsertContent : function(command, ui, value) {
bgneal@312 11253 selection.setContent(value);
bgneal@312 11254 },
bgneal@312 11255
bgneal@312 11256 mceInsertRawHTML : function(command, ui, value) {
bgneal@312 11257 selection.setContent('tiny_mce_marker');
bgneal@312 11258 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
bgneal@312 11259 },
bgneal@312 11260
bgneal@312 11261 mceSetContent : function(command, ui, value) {
bgneal@312 11262 editor.setContent(value);
bgneal@312 11263 },
bgneal@312 11264
bgneal@312 11265 'Indent,Outdent' : function(command) {
bgneal@312 11266 var intentValue, indentUnit, value;
bgneal@312 11267
bgneal@312 11268 // Setup indent level
bgneal@312 11269 intentValue = settings.indentation;
bgneal@312 11270 indentUnit = /[a-z%]+$/i.exec(intentValue);
bgneal@312 11271 intentValue = parseInt(intentValue);
bgneal@312 11272
bgneal@312 11273 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
bgneal@312 11274 each(selection.getSelectedBlocks(), function(element) {
bgneal@312 11275 if (command == 'outdent') {
bgneal@312 11276 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
bgneal@312 11277 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
bgneal@312 11278 } else
bgneal@312 11279 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
bgneal@312 11280 });
bgneal@312 11281 } else
bgneal@312 11282 execNativeCommand(command);
bgneal@312 11283 },
bgneal@312 11284
bgneal@312 11285 mceRepaint : function() {
bgneal@312 11286 var bookmark;
bgneal@312 11287
bgneal@312 11288 if (tinymce.isGecko) {
bgneal@312 11289 try {
bgneal@312 11290 storeSelection(TRUE);
bgneal@312 11291
bgneal@312 11292 if (selection.getSel())
bgneal@312 11293 selection.getSel().selectAllChildren(editor.getBody());
bgneal@312 11294
bgneal@312 11295 selection.collapse(TRUE);
bgneal@312 11296 restoreSelection();
bgneal@312 11297 } catch (ex) {
bgneal@312 11298 // Ignore
bgneal@312 11299 }
bgneal@312 11300 }
bgneal@312 11301 },
bgneal@312 11302
bgneal@312 11303 mceToggleFormat : function(command, ui, value) {
bgneal@312 11304 editor.formatter.toggle(value);
bgneal@312 11305 },
bgneal@312 11306
bgneal@312 11307 InsertHorizontalRule : function() {
bgneal@312 11308 selection.setContent('<hr />');
bgneal@312 11309 },
bgneal@312 11310
bgneal@312 11311 mceToggleVisualAid : function() {
bgneal@312 11312 editor.hasVisual = !editor.hasVisual;
bgneal@312 11313 editor.addVisual();
bgneal@312 11314 },
bgneal@312 11315
bgneal@312 11316 mceReplaceContent : function(command, ui, value) {
bgneal@312 11317 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
bgneal@312 11318 },
bgneal@312 11319
bgneal@312 11320 mceInsertLink : function(command, ui, value) {
bgneal@312 11321 var link = dom.getParent(selection.getNode(), 'a');
bgneal@312 11322
bgneal@312 11323 if (tinymce.is(value, 'string'))
bgneal@312 11324 value = {href : value};
bgneal@312 11325
bgneal@312 11326 if (!link) {
bgneal@312 11327 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
bgneal@312 11328 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
bgneal@312 11329 dom.setAttribs(link, value);
bgneal@312 11330 });
bgneal@312 11331 } else {
bgneal@312 11332 if (value.href)
bgneal@312 11333 dom.setAttribs(link, value);
bgneal@312 11334 else
bgneal@312 11335 editor.dom.remove(link, TRUE);
bgneal@312 11336 }
bgneal@312 11337 },
bgneal@312 11338
bgneal@312 11339 selectAll : function() {
bgneal@312 11340 var root = dom.getRoot(), rng = dom.createRng();
bgneal@312 11341
bgneal@312 11342 rng.setStart(root, 0);
bgneal@312 11343 rng.setEnd(root, root.childNodes.length);
bgneal@312 11344
bgneal@312 11345 editor.selection.setRng(rng);
bgneal@312 11346 }
bgneal@312 11347 });
bgneal@312 11348
bgneal@312 11349 // Add queryCommandState overrides
bgneal@312 11350 addCommands({
bgneal@312 11351 // Override justify commands
bgneal@312 11352 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@312 11353 return isFormatMatch('align' + command.substring(7));
bgneal@312 11354 },
bgneal@312 11355
bgneal@312 11356 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@312 11357 return isFormatMatch(command);
bgneal@312 11358 },
bgneal@312 11359
bgneal@312 11360 mceBlockQuote : function() {
bgneal@312 11361 return isFormatMatch('blockquote');
bgneal@312 11362 },
bgneal@312 11363
bgneal@312 11364 Outdent : function() {
bgneal@312 11365 var node;
bgneal@312 11366
bgneal@312 11367 if (settings.inline_styles) {
bgneal@312 11368 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@312 11369 return TRUE;
bgneal@312 11370
bgneal@312 11371 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@312 11372 return TRUE;
bgneal@312 11373 }
bgneal@312 11374
bgneal@312 11375 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
bgneal@312 11376 },
bgneal@312 11377
bgneal@312 11378 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@312 11379 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
bgneal@312 11380 }
bgneal@312 11381 }, 'state');
bgneal@312 11382
bgneal@312 11383 // Add queryCommandValue overrides
bgneal@312 11384 addCommands({
bgneal@312 11385 'FontSize,FontName' : function(command) {
bgneal@312 11386 var value = 0, parent;
bgneal@312 11387
bgneal@312 11388 if (parent = dom.getParent(selection.getNode(), 'span')) {
bgneal@312 11389 if (command == 'fontsize')
bgneal@312 11390 value = parent.style.fontSize;
bgneal@312 11391 else
bgneal@312 11392 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
bgneal@312 11393 }
bgneal@312 11394
bgneal@312 11395 return value;
bgneal@312 11396 }
bgneal@312 11397 }, 'value');
bgneal@312 11398
bgneal@312 11399 // Add undo manager logic
bgneal@312 11400 if (settings.custom_undo_redo) {
bgneal@312 11401 addCommands({
bgneal@312 11402 Undo : function() {
bgneal@312 11403 editor.undoManager.undo();
bgneal@312 11404 },
bgneal@312 11405
bgneal@312 11406 Redo : function() {
bgneal@312 11407 editor.undoManager.redo();
bgneal@312 11408 }
bgneal@312 11409 });
bgneal@312 11410 }
bgneal@312 11411 };
bgneal@312 11412 })(tinymce);
bgneal@312 11413 (function(tinymce) {
bgneal@312 11414 var Dispatcher = tinymce.util.Dispatcher;
bgneal@312 11415
bgneal@312 11416 tinymce.UndoManager = function(editor) {
bgneal@312 11417 var self, index = 0, data = [];
bgneal@312 11418
bgneal@312 11419 function getContent() {
bgneal@312 11420 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
bgneal@312 11421 };
bgneal@312 11422
bgneal@312 11423 return self = {
bgneal@312 11424 typing : 0,
bgneal@312 11425
bgneal@312 11426 onAdd : new Dispatcher(self),
bgneal@312 11427 onUndo : new Dispatcher(self),
bgneal@312 11428 onRedo : new Dispatcher(self),
bgneal@312 11429
bgneal@312 11430 add : function(level) {
bgneal@312 11431 var i, settings = editor.settings, lastLevel;
bgneal@312 11432
bgneal@312 11433 level = level || {};
bgneal@312 11434 level.content = getContent();
bgneal@312 11435
bgneal@312 11436 // Add undo level if needed
bgneal@312 11437 lastLevel = data[index];
bgneal@312 11438 if (lastLevel && lastLevel.content == level.content) {
bgneal@312 11439 if (index > 0 || data.length == 1)
bgneal@312 11440 return null;
bgneal@312 11441 }
bgneal@312 11442
bgneal@312 11443 // Time to compress
bgneal@312 11444 if (settings.custom_undo_redo_levels) {
bgneal@312 11445 if (data.length > settings.custom_undo_redo_levels) {
bgneal@312 11446 for (i = 0; i < data.length - 1; i++)
bgneal@312 11447 data[i] = data[i + 1];
bgneal@312 11448
bgneal@312 11449 data.length--;
bgneal@312 11450 index = data.length;
bgneal@312 11451 }
bgneal@312 11452 }
bgneal@312 11453
bgneal@312 11454 // Get a non intrusive normalized bookmark
bgneal@312 11455 level.bookmark = editor.selection.getBookmark(2, true);
bgneal@312 11456
bgneal@312 11457 // Crop array if needed
bgneal@312 11458 if (index < data.length - 1) {
bgneal@312 11459 // Treat first level as initial
bgneal@312 11460 if (index == 0)
bgneal@312 11461 data = [];
bgneal@312 11462 else
bgneal@312 11463 data.length = index + 1;
bgneal@312 11464 }
bgneal@312 11465
bgneal@312 11466 data.push(level);
bgneal@312 11467 index = data.length - 1;
bgneal@312 11468
bgneal@312 11469 self.onAdd.dispatch(self, level);
bgneal@312 11470 editor.isNotDirty = 0;
bgneal@312 11471
bgneal@312 11472 return level;
bgneal@312 11473 },
bgneal@312 11474
bgneal@312 11475 undo : function() {
bgneal@312 11476 var level, i;
bgneal@312 11477
bgneal@312 11478 if (self.typing) {
bgneal@312 11479 self.add();
bgneal@312 11480 self.typing = 0;
bgneal@312 11481 }
bgneal@312 11482
bgneal@312 11483 if (index > 0) {
bgneal@312 11484 level = data[--index];
bgneal@312 11485
bgneal@312 11486 editor.setContent(level.content, {format : 'raw'});
bgneal@312 11487 editor.selection.moveToBookmark(level.bookmark);
bgneal@312 11488
bgneal@312 11489 self.onUndo.dispatch(self, level);
bgneal@312 11490 }
bgneal@312 11491
bgneal@312 11492 return level;
bgneal@312 11493 },
bgneal@312 11494
bgneal@312 11495 redo : function() {
bgneal@312 11496 var level;
bgneal@312 11497
bgneal@312 11498 if (index < data.length - 1) {
bgneal@312 11499 level = data[++index];
bgneal@312 11500
bgneal@312 11501 editor.setContent(level.content, {format : 'raw'});
bgneal@312 11502 editor.selection.moveToBookmark(level.bookmark);
bgneal@312 11503
bgneal@312 11504 self.onRedo.dispatch(self, level);
bgneal@312 11505 }
bgneal@312 11506
bgneal@312 11507 return level;
bgneal@312 11508 },
bgneal@312 11509
bgneal@312 11510 clear : function() {
bgneal@312 11511 data = [];
bgneal@312 11512 index = self.typing = 0;
bgneal@312 11513 },
bgneal@312 11514
bgneal@312 11515 hasUndo : function() {
bgneal@312 11516 return index > 0 || self.typing;
bgneal@312 11517 },
bgneal@312 11518
bgneal@312 11519 hasRedo : function() {
bgneal@312 11520 return index < data.length - 1;
bgneal@312 11521 }
bgneal@312 11522 };
bgneal@312 11523 };
bgneal@312 11524 })(tinymce);
bgneal@312 11525
bgneal@312 11526 (function(tinymce) {
bgneal@312 11527 // Shorten names
bgneal@312 11528 var Event = tinymce.dom.Event,
bgneal@312 11529 isIE = tinymce.isIE,
bgneal@312 11530 isGecko = tinymce.isGecko,
bgneal@312 11531 isOpera = tinymce.isOpera,
bgneal@312 11532 each = tinymce.each,
bgneal@312 11533 extend = tinymce.extend,
bgneal@312 11534 TRUE = true,
bgneal@312 11535 FALSE = false;
bgneal@312 11536
bgneal@312 11537 function cloneFormats(node) {
bgneal@312 11538 var clone, temp, inner;
bgneal@312 11539
bgneal@312 11540 do {
bgneal@312 11541 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
bgneal@312 11542 if (clone) {
bgneal@312 11543 temp = node.cloneNode(false);
bgneal@312 11544 temp.appendChild(clone);
bgneal@312 11545 clone = temp;
bgneal@312 11546 } else {
bgneal@312 11547 clone = inner = node.cloneNode(false);
bgneal@312 11548 }
bgneal@312 11549
bgneal@312 11550 clone.removeAttribute('id');
bgneal@312 11551 }
bgneal@312 11552 } while (node = node.parentNode);
bgneal@312 11553
bgneal@312 11554 if (clone)
bgneal@312 11555 return {wrapper : clone, inner : inner};
bgneal@312 11556 };
bgneal@312 11557
bgneal@312 11558 // Checks if the selection/caret is at the end of the specified block element
bgneal@312 11559 function isAtEnd(rng, par) {
bgneal@312 11560 var rng2 = par.ownerDocument.createRange();
bgneal@312 11561
bgneal@312 11562 rng2.setStart(rng.endContainer, rng.endOffset);
bgneal@312 11563 rng2.setEndAfter(par);
bgneal@312 11564
bgneal@312 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@312 11566 return rng2.cloneContents().textContent.length == 0;
bgneal@312 11567 };
bgneal@312 11568
bgneal@312 11569 function isEmpty(n) {
bgneal@312 11570 n = n.innerHTML;
bgneal@312 11571
bgneal@312 11572 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
bgneal@312 11573 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
bgneal@312 11574
bgneal@312 11575 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
bgneal@312 11576 };
bgneal@312 11577
bgneal@312 11578 function splitList(selection, dom, li) {
bgneal@312 11579 var listBlock, block;
bgneal@312 11580
bgneal@312 11581 if (isEmpty(li)) {
bgneal@312 11582 listBlock = dom.getParent(li, 'ul,ol');
bgneal@312 11583
bgneal@312 11584 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
bgneal@312 11585 dom.split(listBlock, li);
bgneal@312 11586 block = dom.create('p', 0, '<br _mce_bogus="1" />');
bgneal@312 11587 dom.replace(block, li);
bgneal@312 11588 selection.select(block, 1);
bgneal@312 11589 }
bgneal@312 11590
bgneal@312 11591 return FALSE;
bgneal@312 11592 }
bgneal@312 11593
bgneal@312 11594 return TRUE;
bgneal@312 11595 };
bgneal@312 11596
bgneal@312 11597 tinymce.create('tinymce.ForceBlocks', {
bgneal@312 11598 ForceBlocks : function(ed) {
bgneal@312 11599 var t = this, s = ed.settings, elm;
bgneal@312 11600
bgneal@312 11601 t.editor = ed;
bgneal@312 11602 t.dom = ed.dom;
bgneal@312 11603 elm = (s.forced_root_block || 'p').toLowerCase();
bgneal@312 11604 s.element = elm.toUpperCase();
bgneal@312 11605
bgneal@312 11606 ed.onPreInit.add(t.setup, t);
bgneal@312 11607
bgneal@312 11608 t.reOpera = new RegExp('(\\u00a0|&#160;|&nbsp;)<\/' + elm + '>', 'gi');
bgneal@312 11609 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@312 11610 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@312 11611 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>(&nbsp;|&#160;)<\\\/%p>|<%p>(&nbsp;|&#160;)<\\\/%p>'.replace(/%p/g, elm), 'gi');
bgneal@312 11612 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@312 11613
bgneal@312 11614 function padd(ed, o) {
bgneal@312 11615 if (isOpera)
bgneal@312 11616 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
bgneal@312 11617
bgneal@312 11618 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
bgneal@312 11619
bgneal@312 11620 if (!isIE && !isOpera && o.set) {
bgneal@312 11621 // Use &nbsp; instead of BR in padded paragraphs
bgneal@312 11622 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@312 11623 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@312 11624 } else
bgneal@312 11625 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
bgneal@312 11626 };
bgneal@312 11627
bgneal@312 11628 ed.onBeforeSetContent.add(padd);
bgneal@312 11629 ed.onPostProcess.add(padd);
bgneal@312 11630
bgneal@312 11631 if (s.forced_root_block) {
bgneal@312 11632 ed.onInit.add(t.forceRoots, t);
bgneal@312 11633 ed.onSetContent.add(t.forceRoots, t);
bgneal@312 11634 ed.onBeforeGetContent.add(t.forceRoots, t);
bgneal@312 11635 }
bgneal@312 11636 },
bgneal@312 11637
bgneal@312 11638 setup : function() {
bgneal@312 11639 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
bgneal@312 11640
bgneal@312 11641 // Force root blocks when typing and when getting output
bgneal@312 11642 if (s.forced_root_block) {
bgneal@312 11643 ed.onBeforeExecCommand.add(t.forceRoots, t);
bgneal@312 11644 ed.onKeyUp.add(t.forceRoots, t);
bgneal@312 11645 ed.onPreProcess.add(t.forceRoots, t);
bgneal@312 11646 }
bgneal@312 11647
bgneal@312 11648 if (s.force_br_newlines) {
bgneal@312 11649 // Force IE to produce BRs on enter
bgneal@312 11650 if (isIE) {
bgneal@312 11651 ed.onKeyPress.add(function(ed, e) {
bgneal@312 11652 var n;
bgneal@312 11653
bgneal@312 11654 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
bgneal@312 11655 selection.setContent('<br id="__" /> ', {format : 'raw'});
bgneal@312 11656 n = dom.get('__');
bgneal@312 11657 n.removeAttribute('id');
bgneal@312 11658 selection.select(n);
bgneal@312 11659 selection.collapse();
bgneal@312 11660 return Event.cancel(e);
bgneal@312 11661 }
bgneal@312 11662 });
bgneal@312 11663 }
bgneal@312 11664 }
bgneal@312 11665
bgneal@312 11666 if (s.force_p_newlines) {
bgneal@312 11667 if (!isIE) {
bgneal@312 11668 ed.onKeyPress.add(function(ed, e) {
bgneal@312 11669 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
bgneal@312 11670 Event.cancel(e);
bgneal@312 11671 });
bgneal@312 11672 } else {
bgneal@312 11673 // Ungly hack to for IE to preserve the formatting when you press
bgneal@312 11674 // enter at the end of a block element with formatted contents
bgneal@312 11675 // This logic overrides the browsers default logic with
bgneal@312 11676 // custom logic that enables us to control the output
bgneal@312 11677 tinymce.addUnload(function() {
bgneal@312 11678 t._previousFormats = 0; // Fix IE leak
bgneal@312 11679 });
bgneal@312 11680
bgneal@312 11681 ed.onKeyPress.add(function(ed, e) {
bgneal@312 11682 t._previousFormats = 0;
bgneal@312 11683
bgneal@312 11684 // Clone the current formats, this will later be applied to the new block contents
bgneal@312 11685 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
bgneal@312 11686 t._previousFormats = cloneFormats(ed.selection.getStart());
bgneal@312 11687 });
bgneal@312 11688
bgneal@312 11689 ed.onKeyUp.add(function(ed, e) {
bgneal@312 11690 // Let IE break the element and the wrap the new caret location in the previous formats
bgneal@312 11691 if (e.keyCode == 13 && !e.shiftKey) {
bgneal@312 11692 var parent = ed.selection.getStart(), fmt = t._previousFormats;
bgneal@312 11693
bgneal@312 11694 // Parent is an empty block
bgneal@312 11695 if (!parent.hasChildNodes() && fmt) {
bgneal@312 11696 parent = dom.getParent(parent, dom.isBlock);
bgneal@312 11697
bgneal@312 11698 if (parent && parent.nodeName != 'LI') {
bgneal@312 11699 parent.innerHTML = '';
bgneal@312 11700
bgneal@312 11701 if (t._previousFormats) {
bgneal@312 11702 parent.appendChild(fmt.wrapper);
bgneal@312 11703 fmt.inner.innerHTML = '\uFEFF';
bgneal@312 11704 } else
bgneal@312 11705 parent.innerHTML = '\uFEFF';
bgneal@312 11706
bgneal@312 11707 selection.select(parent, 1);
bgneal@312 11708 ed.getDoc().execCommand('Delete', false, null);
bgneal@312 11709 t._previousFormats = 0;
bgneal@312 11710 }
bgneal@312 11711 }
bgneal@312 11712 }
bgneal@312 11713 });
bgneal@312 11714 }
bgneal@312 11715
bgneal@312 11716 if (isGecko) {
bgneal@312 11717 ed.onKeyDown.add(function(ed, e) {
bgneal@312 11718 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
bgneal@312 11719 t.backspaceDelete(e, e.keyCode == 8);
bgneal@312 11720 });
bgneal@312 11721 }
bgneal@312 11722 }
bgneal@312 11723
bgneal@312 11724 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
bgneal@312 11725 if (tinymce.isWebKit) {
bgneal@312 11726 function insertBr(ed) {
bgneal@312 11727 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
bgneal@312 11728
bgneal@312 11729 // Insert BR element
bgneal@312 11730 rng.insertNode(br = dom.create('br'));
bgneal@312 11731
bgneal@312 11732 // Place caret after BR
bgneal@312 11733 rng.setStartAfter(br);
bgneal@312 11734 rng.setEndAfter(br);
bgneal@312 11735 selection.setRng(rng);
bgneal@312 11736
bgneal@312 11737 // Could not place caret after BR then insert an nbsp entity and move the caret
bgneal@312 11738 if (selection.getSel().focusNode == br.previousSibling) {
bgneal@312 11739 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
bgneal@312 11740 selection.collapse(TRUE);
bgneal@312 11741 }
bgneal@312 11742
bgneal@312 11743 // Create a temporary DIV after the BR and get the position as it
bgneal@312 11744 // seems like getPos() returns 0 for text nodes and BR elements.
bgneal@312 11745 dom.insertAfter(div, br);
bgneal@312 11746 divYPos = dom.getPos(div).y;
bgneal@312 11747 dom.remove(div);
bgneal@312 11748
bgneal@312 11749 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
bgneal@312 11750 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
bgneal@312 11751 ed.getWin().scrollTo(0, divYPos);
bgneal@312 11752 };
bgneal@312 11753
bgneal@312 11754 ed.onKeyPress.add(function(ed, e) {
bgneal@312 11755 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
bgneal@312 11756 insertBr(ed);
bgneal@312 11757 Event.cancel(e);
bgneal@312 11758 }
bgneal@312 11759 });
bgneal@312 11760 }
bgneal@312 11761
bgneal@312 11762 // Padd empty inline elements within block elements
bgneal@312 11763 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
bgneal@312 11764 ed.onPreProcess.add(function(ed, o) {
bgneal@312 11765 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
bgneal@312 11766 if (isEmpty(p)) {
bgneal@312 11767 each(dom.select('span,em,strong,b,i', o.node), function(n) {
bgneal@312 11768 if (!n.hasChildNodes()) {
bgneal@312 11769 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
bgneal@312 11770 return FALSE; // Break the loop one padding is enough
bgneal@312 11771 }
bgneal@312 11772 });
bgneal@312 11773 }
bgneal@312 11774 });
bgneal@312 11775 });
bgneal@312 11776
bgneal@312 11777 // IE specific fixes
bgneal@312 11778 if (isIE) {
bgneal@312 11779 // Replaces IE:s auto generated paragraphs with the specified element name
bgneal@312 11780 if (s.element != 'P') {
bgneal@312 11781 ed.onKeyPress.add(function(ed, e) {
bgneal@312 11782 t.lastElm = selection.getNode().nodeName;
bgneal@312 11783 });
bgneal@312 11784
bgneal@312 11785 ed.onKeyUp.add(function(ed, e) {
bgneal@312 11786 var bl, n = selection.getNode(), b = ed.getBody();
bgneal@312 11787
bgneal@312 11788 if (b.childNodes.length === 1 && n.nodeName == 'P') {
bgneal@312 11789 n = dom.rename(n, s.element);
bgneal@312 11790 selection.select(n);
bgneal@312 11791 selection.collapse();
bgneal@312 11792 ed.nodeChanged();
bgneal@312 11793 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bgneal@312 11794 bl = dom.getParent(n, 'p');
bgneal@312 11795
bgneal@312 11796 if (bl) {
bgneal@312 11797 dom.rename(bl, s.element);
bgneal@312 11798 ed.nodeChanged();
bgneal@312 11799 }
bgneal@312 11800 }
bgneal@312 11801 });
bgneal@312 11802 }
bgneal@312 11803 }
bgneal@312 11804 },
bgneal@312 11805
bgneal@312 11806 find : function(n, t, s) {
bgneal@312 11807 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
bgneal@312 11808
bgneal@312 11809 while (n = w.nextNode()) {
bgneal@312 11810 c++;
bgneal@312 11811
bgneal@312 11812 // Index by node
bgneal@312 11813 if (t == 0 && n == s)
bgneal@312 11814 return c;
bgneal@312 11815
bgneal@312 11816 // Node by index
bgneal@312 11817 if (t == 1 && c == s)
bgneal@312 11818 return n;
bgneal@312 11819 }
bgneal@312 11820
bgneal@312 11821 return -1;
bgneal@312 11822 },
bgneal@312 11823
bgneal@312 11824 forceRoots : function(ed, e) {
bgneal@312 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@312 11826 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
bgneal@312 11827
bgneal@312 11828 // Fix for bug #1863847
bgneal@312 11829 //if (e && e.keyCode == 13)
bgneal@312 11830 // return TRUE;
bgneal@312 11831
bgneal@312 11832 // Wrap non blocks into blocks
bgneal@312 11833 for (i = nl.length - 1; i >= 0; i--) {
bgneal@312 11834 nx = nl[i];
bgneal@312 11835
bgneal@312 11836 // Ignore internal elements
bgneal@312 11837 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
bgneal@312 11838 bl = null;
bgneal@312 11839 continue;
bgneal@312 11840 }
bgneal@312 11841
bgneal@312 11842 // Is text or non block element
bgneal@312 11843 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
bgneal@312 11844 if (!bl) {
bgneal@312 11845 // Create new block but ignore whitespace
bgneal@312 11846 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
bgneal@312 11847 // Store selection
bgneal@312 11848 if (si == -2 && r) {
bgneal@312 11849 if (!isIE) {
bgneal@312 11850 // If selection is element then mark it
bgneal@312 11851 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
bgneal@312 11852 // Save the id of the selected element
bgneal@312 11853 eid = n.getAttribute("id");
bgneal@312 11854 n.setAttribute("id", "__mce");
bgneal@312 11855 } else {
bgneal@312 11856 // If element is inside body, might not be the case in contentEdiable mode
bgneal@312 11857 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
bgneal@312 11858 so = r.startOffset;
bgneal@312 11859 eo = r.endOffset;
bgneal@312 11860 si = t.find(b, 0, r.startContainer);
bgneal@312 11861 ei = t.find(b, 0, r.endContainer);
bgneal@312 11862 }
bgneal@312 11863 }
bgneal@312 11864 } else {
bgneal@312 11865 // Force control range into text range
bgneal@312 11866 if (r.item) {
bgneal@312 11867 tr = d.body.createTextRange();
bgneal@312 11868 tr.moveToElementText(r.item(0));
bgneal@312 11869 r = tr;
bgneal@312 11870 }
bgneal@312 11871
bgneal@312 11872 tr = d.body.createTextRange();
bgneal@312 11873 tr.moveToElementText(b);
bgneal@312 11874 tr.collapse(1);
bgneal@312 11875 bp = tr.move('character', c) * -1;
bgneal@312 11876
bgneal@312 11877 tr = r.duplicate();
bgneal@312 11878 tr.collapse(1);
bgneal@312 11879 sp = tr.move('character', c) * -1;
bgneal@312 11880
bgneal@312 11881 tr = r.duplicate();
bgneal@312 11882 tr.collapse(0);
bgneal@312 11883 le = (tr.move('character', c) * -1) - sp;
bgneal@312 11884
bgneal@312 11885 si = sp - bp;
bgneal@312 11886 ei = le;
bgneal@312 11887 }
bgneal@312 11888 }
bgneal@312 11889
bgneal@312 11890 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
bgneal@312 11891 // See: http://support.microsoft.com/kb/829907
bgneal@312 11892 bl = ed.dom.create(ed.settings.forced_root_block);
bgneal@312 11893 nx.parentNode.replaceChild(bl, nx);
bgneal@312 11894 bl.appendChild(nx);
bgneal@312 11895 }
bgneal@312 11896 } else {
bgneal@312 11897 if (bl.hasChildNodes())
bgneal@312 11898 bl.insertBefore(nx, bl.firstChild);
bgneal@312 11899 else
bgneal@312 11900 bl.appendChild(nx);
bgneal@312 11901 }
bgneal@312 11902 } else
bgneal@312 11903 bl = null; // Time to create new block
bgneal@312 11904 }
bgneal@312 11905
bgneal@312 11906 // Restore selection
bgneal@312 11907 if (si != -2) {
bgneal@312 11908 if (!isIE) {
bgneal@312 11909 bl = b.getElementsByTagName(ed.settings.element)[0];
bgneal@312 11910 r = d.createRange();
bgneal@312 11911
bgneal@312 11912 // Select last location or generated block
bgneal@312 11913 if (si != -1)
bgneal@312 11914 r.setStart(t.find(b, 1, si), so);
bgneal@312 11915 else
bgneal@312 11916 r.setStart(bl, 0);
bgneal@312 11917
bgneal@312 11918 // Select last location or generated block
bgneal@312 11919 if (ei != -1)
bgneal@312 11920 r.setEnd(t.find(b, 1, ei), eo);
bgneal@312 11921 else
bgneal@312 11922 r.setEnd(bl, 0);
bgneal@312 11923
bgneal@312 11924 if (s) {
bgneal@312 11925 s.removeAllRanges();
bgneal@312 11926 s.addRange(r);
bgneal@312 11927 }
bgneal@312 11928 } else {
bgneal@312 11929 try {
bgneal@312 11930 r = s.createRange();
bgneal@312 11931 r.moveToElementText(b);
bgneal@312 11932 r.collapse(1);
bgneal@312 11933 r.moveStart('character', si);
bgneal@312 11934 r.moveEnd('character', ei);
bgneal@312 11935 r.select();
bgneal@312 11936 } catch (ex) {
bgneal@312 11937 // Ignore
bgneal@312 11938 }
bgneal@312 11939 }
bgneal@312 11940 } else if (!isIE && (n = ed.dom.get('__mce'))) {
bgneal@312 11941 // Restore the id of the selected element
bgneal@312 11942 if (eid)
bgneal@312 11943 n.setAttribute('id', eid);
bgneal@312 11944 else
bgneal@312 11945 n.removeAttribute('id');
bgneal@312 11946
bgneal@312 11947 // Move caret before selected element
bgneal@312 11948 r = d.createRange();
bgneal@312 11949 r.setStartBefore(n);
bgneal@312 11950 r.setEndBefore(n);
bgneal@312 11951 se.setRng(r);
bgneal@312 11952 }
bgneal@312 11953 },
bgneal@312 11954
bgneal@312 11955 getParentBlock : function(n) {
bgneal@312 11956 var d = this.dom;
bgneal@312 11957
bgneal@312 11958 return d.getParent(n, d.isBlock);
bgneal@312 11959 },
bgneal@312 11960
bgneal@312 11961 insertPara : function(e) {
bgneal@312 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@312 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@312 11964
bgneal@312 11965 // If root blocks are forced then use Operas default behavior since it's really good
bgneal@312 11966 // Removed due to bug: #1853816
bgneal@312 11967 // if (se.forced_root_block && isOpera)
bgneal@312 11968 // return TRUE;
bgneal@312 11969
bgneal@312 11970 // Setup before range
bgneal@312 11971 rb = d.createRange();
bgneal@312 11972
bgneal@312 11973 // If is before the first block element and in body, then move it into first block element
bgneal@312 11974 rb.setStart(s.anchorNode, s.anchorOffset);
bgneal@312 11975 rb.collapse(TRUE);
bgneal@312 11976
bgneal@312 11977 // Setup after range
bgneal@312 11978 ra = d.createRange();
bgneal@312 11979
bgneal@312 11980 // If is before the first block element and in body, then move it into first block element
bgneal@312 11981 ra.setStart(s.focusNode, s.focusOffset);
bgneal@312 11982 ra.collapse(TRUE);
bgneal@312 11983
bgneal@312 11984 // Setup start/end points
bgneal@312 11985 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
bgneal@312 11986 sn = dir ? s.anchorNode : s.focusNode;
bgneal@312 11987 so = dir ? s.anchorOffset : s.focusOffset;
bgneal@312 11988 en = dir ? s.focusNode : s.anchorNode;
bgneal@312 11989 eo = dir ? s.focusOffset : s.anchorOffset;
bgneal@312 11990
bgneal@312 11991 // If selection is in empty table cell
bgneal@312 11992 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
bgneal@312 11993 if (sn.firstChild.nodeName == 'BR')
bgneal@312 11994 dom.remove(sn.firstChild); // Remove BR
bgneal@312 11995
bgneal@312 11996 // Create two new block elements
bgneal@312 11997 if (sn.childNodes.length == 0) {
bgneal@312 11998 ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 11999 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 12000 } else {
bgneal@312 12001 n = sn.innerHTML;
bgneal@312 12002 sn.innerHTML = '';
bgneal@312 12003 ed.dom.add(sn, se.element, null, n);
bgneal@312 12004 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 12005 }
bgneal@312 12006
bgneal@312 12007 // Move caret into the last one
bgneal@312 12008 r = d.createRange();
bgneal@312 12009 r.selectNodeContents(aft);
bgneal@312 12010 r.collapse(1);
bgneal@312 12011 ed.selection.setRng(r);
bgneal@312 12012
bgneal@312 12013 return FALSE;
bgneal@312 12014 }
bgneal@312 12015
bgneal@312 12016 // If the caret is in an invalid location in FF we need to move it into the first block
bgneal@312 12017 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
bgneal@312 12018 sn = en = sn.firstChild;
bgneal@312 12019 so = eo = 0;
bgneal@312 12020 rb = d.createRange();
bgneal@312 12021 rb.setStart(sn, 0);
bgneal@312 12022 ra = d.createRange();
bgneal@312 12023 ra.setStart(en, 0);
bgneal@312 12024 }
bgneal@312 12025
bgneal@312 12026 // Never use body as start or end node
bgneal@312 12027 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@312 12028 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
bgneal@312 12029 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@312 12030 en = en.nodeName == "BODY" ? en.firstChild : en;
bgneal@312 12031
bgneal@312 12032 // Get start and end blocks
bgneal@312 12033 sb = t.getParentBlock(sn);
bgneal@312 12034 eb = t.getParentBlock(en);
bgneal@312 12035 bn = sb ? sb.nodeName : se.element; // Get block name to create
bgneal@312 12036
bgneal@312 12037 // Return inside list use default browser behavior
bgneal@312 12038 if (n = t.dom.getParent(sb, 'li,pre')) {
bgneal@312 12039 if (n.nodeName == 'LI')
bgneal@312 12040 return splitList(ed.selection, t.dom, n);
bgneal@312 12041
bgneal@312 12042 return TRUE;
bgneal@312 12043 }
bgneal@312 12044
bgneal@312 12045 // If caption or absolute layers then always generate new blocks within
bgneal@312 12046 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@312 12047 bn = se.element;
bgneal@312 12048 sb = null;
bgneal@312 12049 }
bgneal@312 12050
bgneal@312 12051 // If caption or absolute layers then always generate new blocks within
bgneal@312 12052 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@312 12053 bn = se.element;
bgneal@312 12054 eb = null;
bgneal@312 12055 }
bgneal@312 12056
bgneal@312 12057 // Use P instead
bgneal@312 12058 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bgneal@312 12059 bn = se.element;
bgneal@312 12060 sb = eb = null;
bgneal@312 12061 }
bgneal@312 12062
bgneal@312 12063 // Setup new before and after blocks
bgneal@312 12064 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
bgneal@312 12065 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
bgneal@312 12066
bgneal@312 12067 // Remove id from after clone
bgneal@312 12068 aft.removeAttribute('id');
bgneal@312 12069
bgneal@312 12070 // Is header and cursor is at the end, then force paragraph under
bgneal@312 12071 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
bgneal@312 12072 aft = ed.dom.create(se.element);
bgneal@312 12073
bgneal@312 12074 // Find start chop node
bgneal@312 12075 n = sc = sn;
bgneal@312 12076 do {
bgneal@312 12077 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@312 12078 break;
bgneal@312 12079
bgneal@312 12080 sc = n;
bgneal@312 12081 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
bgneal@312 12082
bgneal@312 12083 // Find end chop node
bgneal@312 12084 n = ec = en;
bgneal@312 12085 do {
bgneal@312 12086 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@312 12087 break;
bgneal@312 12088
bgneal@312 12089 ec = n;
bgneal@312 12090 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
bgneal@312 12091
bgneal@312 12092 // Place first chop part into before block element
bgneal@312 12093 if (sc.nodeName == bn)
bgneal@312 12094 rb.setStart(sc, 0);
bgneal@312 12095 else
bgneal@312 12096 rb.setStartBefore(sc);
bgneal@312 12097
bgneal@312 12098 rb.setEnd(sn, so);
bgneal@312 12099 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@312 12100
bgneal@312 12101 // Place secnd chop part within new block element
bgneal@312 12102 try {
bgneal@312 12103 ra.setEndAfter(ec);
bgneal@312 12104 } catch(ex) {
bgneal@312 12105 //console.debug(s.focusNode, s.focusOffset);
bgneal@312 12106 }
bgneal@312 12107
bgneal@312 12108 ra.setStart(en, eo);
bgneal@312 12109 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@312 12110
bgneal@312 12111 // Create range around everything
bgneal@312 12112 r = d.createRange();
bgneal@312 12113 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
bgneal@312 12114 r.setStartBefore(sc.parentNode);
bgneal@312 12115 } else {
bgneal@312 12116 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
bgneal@312 12117 r.setStartBefore(rb.startContainer);
bgneal@312 12118 else
bgneal@312 12119 r.setStart(rb.startContainer, rb.startOffset);
bgneal@312 12120 }
bgneal@312 12121
bgneal@312 12122 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
bgneal@312 12123 r.setEndAfter(ec.parentNode);
bgneal@312 12124 else
bgneal@312 12125 r.setEnd(ra.endContainer, ra.endOffset);
bgneal@312 12126
bgneal@312 12127 // Delete and replace it with new block elements
bgneal@312 12128 r.deleteContents();
bgneal@312 12129
bgneal@312 12130 if (isOpera)
bgneal@312 12131 ed.getWin().scrollTo(0, vp.y);
bgneal@312 12132
bgneal@312 12133 // Never wrap blocks in blocks
bgneal@312 12134 if (bef.firstChild && bef.firstChild.nodeName == bn)
bgneal@312 12135 bef.innerHTML = bef.firstChild.innerHTML;
bgneal@312 12136
bgneal@312 12137 if (aft.firstChild && aft.firstChild.nodeName == bn)
bgneal@312 12138 aft.innerHTML = aft.firstChild.innerHTML;
bgneal@312 12139
bgneal@312 12140 // Padd empty blocks
bgneal@312 12141 if (isEmpty(bef))
bgneal@312 12142 bef.innerHTML = '<br />';
bgneal@312 12143
bgneal@312 12144 function appendStyles(e, en) {
bgneal@312 12145 var nl = [], nn, n, i;
bgneal@312 12146
bgneal@312 12147 e.innerHTML = '';
bgneal@312 12148
bgneal@312 12149 // Make clones of style elements
bgneal@312 12150 if (se.keep_styles) {
bgneal@312 12151 n = en;
bgneal@312 12152 do {
bgneal@312 12153 // We only want style specific elements
bgneal@312 12154 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
bgneal@312 12155 nn = n.cloneNode(FALSE);
bgneal@312 12156 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
bgneal@312 12157 nl.push(nn);
bgneal@312 12158 }
bgneal@312 12159 } while (n = n.parentNode);
bgneal@312 12160 }
bgneal@312 12161
bgneal@312 12162 // Append style elements to aft
bgneal@312 12163 if (nl.length > 0) {
bgneal@312 12164 for (i = nl.length - 1, nn = e; i >= 0; i--)
bgneal@312 12165 nn = nn.appendChild(nl[i]);
bgneal@312 12166
bgneal@312 12167 // Padd most inner style element
bgneal@312 12168 nl[0].innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@312 12169 return nl[0]; // Move caret to most inner element
bgneal@312 12170 } else
bgneal@312 12171 e.innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@312 12172 };
bgneal@312 12173
bgneal@312 12174 // Fill empty afterblook with current style
bgneal@312 12175 if (isEmpty(aft))
bgneal@312 12176 car = appendStyles(aft, en);
bgneal@312 12177
bgneal@312 12178 // Opera needs this one backwards for older versions
bgneal@312 12179 if (isOpera && parseFloat(opera.version()) < 9.5) {
bgneal@312 12180 r.insertNode(bef);
bgneal@312 12181 r.insertNode(aft);
bgneal@312 12182 } else {
bgneal@312 12183 r.insertNode(aft);
bgneal@312 12184 r.insertNode(bef);
bgneal@312 12185 }
bgneal@312 12186
bgneal@312 12187 // Normalize
bgneal@312 12188 aft.normalize();
bgneal@312 12189 bef.normalize();
bgneal@312 12190
bgneal@312 12191 function first(n) {
bgneal@312 12192 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
bgneal@312 12193 };
bgneal@312 12194
bgneal@312 12195 // Move cursor and scroll into view
bgneal@312 12196 r = d.createRange();
bgneal@312 12197 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
bgneal@312 12198 r.collapse(1);
bgneal@312 12199 s.removeAllRanges();
bgneal@312 12200 s.addRange(r);
bgneal@312 12201
bgneal@312 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@312 12203 y = ed.dom.getPos(aft).y;
bgneal@312 12204 ch = aft.clientHeight;
bgneal@312 12205
bgneal@312 12206 // Is element within viewport
bgneal@312 12207 if (y < vp.y || y + ch > vp.y + vp.h) {
bgneal@312 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@312 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@312 12210 }
bgneal@312 12211
bgneal@312 12212 return FALSE;
bgneal@312 12213 },
bgneal@312 12214
bgneal@312 12215 backspaceDelete : function(e, bs) {
bgneal@312 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@312 12217
bgneal@312 12218 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
bgneal@312 12219 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
bgneal@312 12220 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
bgneal@312 12221
bgneal@312 12222 // Walk the dom backwards until we find a text node
bgneal@312 12223 for (n = sc.lastChild; n; n = walker.prev()) {
bgneal@312 12224 if (n.nodeType == 3) {
bgneal@312 12225 r.setStart(n, n.nodeValue.length);
bgneal@312 12226 r.collapse(true);
bgneal@312 12227 se.setRng(r);
bgneal@312 12228 return;
bgneal@312 12229 }
bgneal@312 12230 }
bgneal@312 12231 }
bgneal@312 12232
bgneal@312 12233 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
bgneal@312 12234 // This workaround removes the element by hand and moves the caret to the previous element
bgneal@312 12235 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
bgneal@312 12236 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
bgneal@312 12237 // Find previous block element
bgneal@312 12238 n = sc;
bgneal@312 12239 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
bgneal@312 12240
bgneal@312 12241 if (n) {
bgneal@312 12242 if (sc != b.firstChild) {
bgneal@312 12243 // Find last text node
bgneal@312 12244 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
bgneal@312 12245 while (tn = w.nextNode())
bgneal@312 12246 n = tn;
bgneal@312 12247
bgneal@312 12248 // Place caret at the end of last text node
bgneal@312 12249 r = ed.getDoc().createRange();
bgneal@312 12250 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@312 12251 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@312 12252 se.setRng(r);
bgneal@312 12253
bgneal@312 12254 // Remove the target container
bgneal@312 12255 ed.dom.remove(sc);
bgneal@312 12256 }
bgneal@312 12257
bgneal@312 12258 return Event.cancel(e);
bgneal@312 12259 }
bgneal@312 12260 }
bgneal@312 12261 }
bgneal@312 12262 }
bgneal@312 12263 });
bgneal@312 12264 })(tinymce);
bgneal@312 12265
bgneal@312 12266 (function(tinymce) {
bgneal@312 12267 // Shorten names
bgneal@312 12268 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
bgneal@312 12269
bgneal@312 12270 tinymce.create('tinymce.ControlManager', {
bgneal@312 12271 ControlManager : function(ed, s) {
bgneal@312 12272 var t = this, i;
bgneal@312 12273
bgneal@312 12274 s = s || {};
bgneal@312 12275 t.editor = ed;
bgneal@312 12276 t.controls = {};
bgneal@312 12277 t.onAdd = new tinymce.util.Dispatcher(t);
bgneal@312 12278 t.onPostRender = new tinymce.util.Dispatcher(t);
bgneal@312 12279 t.prefix = s.prefix || ed.id + '_';
bgneal@312 12280 t._cls = {};
bgneal@312 12281
bgneal@312 12282 t.onPostRender.add(function() {
bgneal@312 12283 each(t.controls, function(c) {
bgneal@312 12284 c.postRender();
bgneal@312 12285 });
bgneal@312 12286 });
bgneal@312 12287 },
bgneal@312 12288
bgneal@312 12289 get : function(id) {
bgneal@312 12290 return this.controls[this.prefix + id] || this.controls[id];
bgneal@312 12291 },
bgneal@312 12292
bgneal@312 12293 setActive : function(id, s) {
bgneal@312 12294 var c = null;
bgneal@312 12295
bgneal@312 12296 if (c = this.get(id))
bgneal@312 12297 c.setActive(s);
bgneal@312 12298
bgneal@312 12299 return c;
bgneal@312 12300 },
bgneal@312 12301
bgneal@312 12302 setDisabled : function(id, s) {
bgneal@312 12303 var c = null;
bgneal@312 12304
bgneal@312 12305 if (c = this.get(id))
bgneal@312 12306 c.setDisabled(s);
bgneal@312 12307
bgneal@312 12308 return c;
bgneal@312 12309 },
bgneal@312 12310
bgneal@312 12311 add : function(c) {
bgneal@312 12312 var t = this;
bgneal@312 12313
bgneal@312 12314 if (c) {
bgneal@312 12315 t.controls[c.id] = c;
bgneal@312 12316 t.onAdd.dispatch(c, t);
bgneal@312 12317 }
bgneal@312 12318
bgneal@312 12319 return c;
bgneal@312 12320 },
bgneal@312 12321
bgneal@312 12322 createControl : function(n) {
bgneal@312 12323 var c, t = this, ed = t.editor;
bgneal@312 12324
bgneal@312 12325 each(ed.plugins, function(p) {
bgneal@312 12326 if (p.createControl) {
bgneal@312 12327 c = p.createControl(n, t);
bgneal@312 12328
bgneal@312 12329 if (c)
bgneal@312 12330 return false;
bgneal@312 12331 }
bgneal@312 12332 });
bgneal@312 12333
bgneal@312 12334 switch (n) {
bgneal@312 12335 case "|":
bgneal@312 12336 case "separator":
bgneal@312 12337 return t.createSeparator();
bgneal@312 12338 }
bgneal@312 12339
bgneal@312 12340 if (!c && ed.buttons && (c = ed.buttons[n]))
bgneal@312 12341 return t.createButton(n, c);
bgneal@312 12342
bgneal@312 12343 return t.add(c);
bgneal@312 12344 },
bgneal@312 12345
bgneal@312 12346 createDropMenu : function(id, s, cc) {
bgneal@312 12347 var t = this, ed = t.editor, c, bm, v, cls;
bgneal@312 12348
bgneal@312 12349 s = extend({
bgneal@312 12350 'class' : 'mceDropDown',
bgneal@312 12351 constrain : ed.settings.constrain_menus
bgneal@312 12352 }, s);
bgneal@312 12353
bgneal@312 12354 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
bgneal@312 12355 if (v = ed.getParam('skin_variant'))
bgneal@312 12356 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
bgneal@312 12357
bgneal@312 12358 id = t.prefix + id;
bgneal@312 12359 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
bgneal@312 12360 c = t.controls[id] = new cls(id, s);
bgneal@312 12361 c.onAddItem.add(function(c, o) {
bgneal@312 12362 var s = o.settings;
bgneal@312 12363
bgneal@312 12364 s.title = ed.getLang(s.title, s.title);
bgneal@312 12365
bgneal@312 12366 if (!s.onclick) {
bgneal@312 12367 s.onclick = function(v) {
bgneal@312 12368 if (s.cmd)
bgneal@312 12369 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@312 12370 };
bgneal@312 12371 }
bgneal@312 12372 });
bgneal@312 12373
bgneal@312 12374 ed.onRemove.add(function() {
bgneal@312 12375 c.destroy();
bgneal@312 12376 });
bgneal@312 12377
bgneal@312 12378 // Fix for bug #1897785, #1898007
bgneal@312 12379 if (tinymce.isIE) {
bgneal@312 12380 c.onShowMenu.add(function() {
bgneal@312 12381 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@312 12382 ed.focus();
bgneal@312 12383
bgneal@312 12384 bm = ed.selection.getBookmark(1);
bgneal@312 12385 });
bgneal@312 12386
bgneal@312 12387 c.onHideMenu.add(function() {
bgneal@312 12388 if (bm) {
bgneal@312 12389 ed.selection.moveToBookmark(bm);
bgneal@312 12390 bm = 0;
bgneal@312 12391 }
bgneal@312 12392 });
bgneal@312 12393 }
bgneal@312 12394
bgneal@312 12395 return t.add(c);
bgneal@312 12396 },
bgneal@312 12397
bgneal@312 12398 createListBox : function(id, s, cc) {
bgneal@312 12399 var t = this, ed = t.editor, cmd, c, cls;
bgneal@312 12400
bgneal@312 12401 if (t.get(id))
bgneal@312 12402 return null;
bgneal@312 12403
bgneal@312 12404 s.title = ed.translate(s.title);
bgneal@312 12405 s.scope = s.scope || ed;
bgneal@312 12406
bgneal@312 12407 if (!s.onselect) {
bgneal@312 12408 s.onselect = function(v) {
bgneal@312 12409 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 12410 };
bgneal@312 12411 }
bgneal@312 12412
bgneal@312 12413 s = extend({
bgneal@312 12414 title : s.title,
bgneal@312 12415 'class' : 'mce_' + id,
bgneal@312 12416 scope : s.scope,
bgneal@312 12417 control_manager : t
bgneal@312 12418 }, s);
bgneal@312 12419
bgneal@312 12420 id = t.prefix + id;
bgneal@312 12421
bgneal@312 12422 if (ed.settings.use_native_selects)
bgneal@312 12423 c = new tinymce.ui.NativeListBox(id, s);
bgneal@312 12424 else {
bgneal@312 12425 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
bgneal@312 12426 c = new cls(id, s);
bgneal@312 12427 }
bgneal@312 12428
bgneal@312 12429 t.controls[id] = c;
bgneal@312 12430
bgneal@312 12431 // Fix focus problem in Safari
bgneal@312 12432 if (tinymce.isWebKit) {
bgneal@312 12433 c.onPostRender.add(function(c, n) {
bgneal@312 12434 // Store bookmark on mousedown
bgneal@312 12435 Event.add(n, 'mousedown', function() {
bgneal@312 12436 ed.bookmark = ed.selection.getBookmark(1);
bgneal@312 12437 });
bgneal@312 12438
bgneal@312 12439 // Restore on focus, since it might be lost
bgneal@312 12440 Event.add(n, 'focus', function() {
bgneal@312 12441 ed.selection.moveToBookmark(ed.bookmark);
bgneal@312 12442 ed.bookmark = null;
bgneal@312 12443 });
bgneal@312 12444 });
bgneal@312 12445 }
bgneal@312 12446
bgneal@312 12447 if (c.hideMenu)
bgneal@312 12448 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 12449
bgneal@312 12450 return t.add(c);
bgneal@312 12451 },
bgneal@312 12452
bgneal@312 12453 createButton : function(id, s, cc) {
bgneal@312 12454 var t = this, ed = t.editor, o, c, cls;
bgneal@312 12455
bgneal@312 12456 if (t.get(id))
bgneal@312 12457 return null;
bgneal@312 12458
bgneal@312 12459 s.title = ed.translate(s.title);
bgneal@312 12460 s.label = ed.translate(s.label);
bgneal@312 12461 s.scope = s.scope || ed;
bgneal@312 12462
bgneal@312 12463 if (!s.onclick && !s.menu_button) {
bgneal@312 12464 s.onclick = function() {
bgneal@312 12465 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@312 12466 };
bgneal@312 12467 }
bgneal@312 12468
bgneal@312 12469 s = extend({
bgneal@312 12470 title : s.title,
bgneal@312 12471 'class' : 'mce_' + id,
bgneal@312 12472 unavailable_prefix : ed.getLang('unavailable', ''),
bgneal@312 12473 scope : s.scope,
bgneal@312 12474 control_manager : t
bgneal@312 12475 }, s);
bgneal@312 12476
bgneal@312 12477 id = t.prefix + id;
bgneal@312 12478
bgneal@312 12479 if (s.menu_button) {
bgneal@312 12480 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
bgneal@312 12481 c = new cls(id, s);
bgneal@312 12482 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 12483 } else {
bgneal@312 12484 cls = t._cls.button || tinymce.ui.Button;
bgneal@312 12485 c = new cls(id, s);
bgneal@312 12486 }
bgneal@312 12487
bgneal@312 12488 return t.add(c);
bgneal@312 12489 },
bgneal@312 12490
bgneal@312 12491 createMenuButton : function(id, s, cc) {
bgneal@312 12492 s = s || {};
bgneal@312 12493 s.menu_button = 1;
bgneal@312 12494
bgneal@312 12495 return this.createButton(id, s, cc);
bgneal@312 12496 },
bgneal@312 12497
bgneal@312 12498 createSplitButton : function(id, s, cc) {
bgneal@312 12499 var t = this, ed = t.editor, cmd, c, cls;
bgneal@312 12500
bgneal@312 12501 if (t.get(id))
bgneal@312 12502 return null;
bgneal@312 12503
bgneal@312 12504 s.title = ed.translate(s.title);
bgneal@312 12505 s.scope = s.scope || ed;
bgneal@312 12506
bgneal@312 12507 if (!s.onclick) {
bgneal@312 12508 s.onclick = function(v) {
bgneal@312 12509 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 12510 };
bgneal@312 12511 }
bgneal@312 12512
bgneal@312 12513 if (!s.onselect) {
bgneal@312 12514 s.onselect = function(v) {
bgneal@312 12515 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 12516 };
bgneal@312 12517 }
bgneal@312 12518
bgneal@312 12519 s = extend({
bgneal@312 12520 title : s.title,
bgneal@312 12521 'class' : 'mce_' + id,
bgneal@312 12522 scope : s.scope,
bgneal@312 12523 control_manager : t
bgneal@312 12524 }, s);
bgneal@312 12525
bgneal@312 12526 id = t.prefix + id;
bgneal@312 12527 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
bgneal@312 12528 c = t.add(new cls(id, s));
bgneal@312 12529 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 12530
bgneal@312 12531 return c;
bgneal@312 12532 },
bgneal@312 12533
bgneal@312 12534 createColorSplitButton : function(id, s, cc) {
bgneal@312 12535 var t = this, ed = t.editor, cmd, c, cls, bm;
bgneal@312 12536
bgneal@312 12537 if (t.get(id))
bgneal@312 12538 return null;
bgneal@312 12539
bgneal@312 12540 s.title = ed.translate(s.title);
bgneal@312 12541 s.scope = s.scope || ed;
bgneal@312 12542
bgneal@312 12543 if (!s.onclick) {
bgneal@312 12544 s.onclick = function(v) {
bgneal@312 12545 if (tinymce.isIE)
bgneal@312 12546 bm = ed.selection.getBookmark(1);
bgneal@312 12547
bgneal@312 12548 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 12549 };
bgneal@312 12550 }
bgneal@312 12551
bgneal@312 12552 if (!s.onselect) {
bgneal@312 12553 s.onselect = function(v) {
bgneal@312 12554 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 12555 };
bgneal@312 12556 }
bgneal@312 12557
bgneal@312 12558 s = extend({
bgneal@312 12559 title : s.title,
bgneal@312 12560 'class' : 'mce_' + id,
bgneal@312 12561 'menu_class' : ed.getParam('skin') + 'Skin',
bgneal@312 12562 scope : s.scope,
bgneal@312 12563 more_colors_title : ed.getLang('more_colors')
bgneal@312 12564 }, s);
bgneal@312 12565
bgneal@312 12566 id = t.prefix + id;
bgneal@312 12567 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
bgneal@312 12568 c = new cls(id, s);
bgneal@312 12569 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 12570
bgneal@312 12571 // Remove the menu element when the editor is removed
bgneal@312 12572 ed.onRemove.add(function() {
bgneal@312 12573 c.destroy();
bgneal@312 12574 });
bgneal@312 12575
bgneal@312 12576 // Fix for bug #1897785, #1898007
bgneal@312 12577 if (tinymce.isIE) {
bgneal@312 12578 c.onShowMenu.add(function() {
bgneal@312 12579 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@312 12580 ed.focus();
bgneal@312 12581 bm = ed.selection.getBookmark(1);
bgneal@312 12582 });
bgneal@312 12583
bgneal@312 12584 c.onHideMenu.add(function() {
bgneal@312 12585 if (bm) {
bgneal@312 12586 ed.selection.moveToBookmark(bm);
bgneal@312 12587 bm = 0;
bgneal@312 12588 }
bgneal@312 12589 });
bgneal@312 12590 }
bgneal@312 12591
bgneal@312 12592 return t.add(c);
bgneal@312 12593 },
bgneal@312 12594
bgneal@312 12595 createToolbar : function(id, s, cc) {
bgneal@312 12596 var c, t = this, cls;
bgneal@312 12597
bgneal@312 12598 id = t.prefix + id;
bgneal@312 12599 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
bgneal@312 12600 c = new cls(id, s);
bgneal@312 12601
bgneal@312 12602 if (t.get(id))
bgneal@312 12603 return null;
bgneal@312 12604
bgneal@312 12605 return t.add(c);
bgneal@312 12606 },
bgneal@312 12607
bgneal@312 12608 createSeparator : function(cc) {
bgneal@312 12609 var cls = cc || this._cls.separator || tinymce.ui.Separator;
bgneal@312 12610
bgneal@312 12611 return new cls();
bgneal@312 12612 },
bgneal@312 12613
bgneal@312 12614 setControlType : function(n, c) {
bgneal@312 12615 return this._cls[n.toLowerCase()] = c;
bgneal@312 12616 },
bgneal@312 12617
bgneal@312 12618 destroy : function() {
bgneal@312 12619 each(this.controls, function(c) {
bgneal@312 12620 c.destroy();
bgneal@312 12621 });
bgneal@312 12622
bgneal@312 12623 this.controls = null;
bgneal@312 12624 }
bgneal@312 12625 });
bgneal@312 12626 })(tinymce);
bgneal@312 12627
bgneal@312 12628 (function(tinymce) {
bgneal@312 12629 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
bgneal@312 12630
bgneal@312 12631 tinymce.create('tinymce.WindowManager', {
bgneal@312 12632 WindowManager : function(ed) {
bgneal@312 12633 var t = this;
bgneal@312 12634
bgneal@312 12635 t.editor = ed;
bgneal@312 12636 t.onOpen = new Dispatcher(t);
bgneal@312 12637 t.onClose = new Dispatcher(t);
bgneal@312 12638 t.params = {};
bgneal@312 12639 t.features = {};
bgneal@312 12640 },
bgneal@312 12641
bgneal@312 12642 open : function(s, p) {
bgneal@312 12643 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
bgneal@312 12644
bgneal@312 12645 // Default some options
bgneal@312 12646 s = s || {};
bgneal@312 12647 p = p || {};
bgneal@312 12648 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
bgneal@312 12649 sh = isOpera ? vp.h : screen.height;
bgneal@312 12650 s.name = s.name || 'mc_' + new Date().getTime();
bgneal@312 12651 s.width = parseInt(s.width || 320);
bgneal@312 12652 s.height = parseInt(s.height || 240);
bgneal@312 12653 s.resizable = true;
bgneal@312 12654 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
bgneal@312 12655 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
bgneal@312 12656 p.inline = false;
bgneal@312 12657 p.mce_width = s.width;
bgneal@312 12658 p.mce_height = s.height;
bgneal@312 12659 p.mce_auto_focus = s.auto_focus;
bgneal@312 12660
bgneal@312 12661 if (mo) {
bgneal@312 12662 if (isIE) {
bgneal@312 12663 s.center = true;
bgneal@312 12664 s.help = false;
bgneal@312 12665 s.dialogWidth = s.width + 'px';
bgneal@312 12666 s.dialogHeight = s.height + 'px';
bgneal@312 12667 s.scroll = s.scrollbars || false;
bgneal@312 12668 }
bgneal@312 12669 }
bgneal@312 12670
bgneal@312 12671 // Build features string
bgneal@312 12672 each(s, function(v, k) {
bgneal@312 12673 if (tinymce.is(v, 'boolean'))
bgneal@312 12674 v = v ? 'yes' : 'no';
bgneal@312 12675
bgneal@312 12676 if (!/^(name|url)$/.test(k)) {
bgneal@312 12677 if (isIE && mo)
bgneal@312 12678 f += (f ? ';' : '') + k + ':' + v;
bgneal@312 12679 else
bgneal@312 12680 f += (f ? ',' : '') + k + '=' + v;
bgneal@312 12681 }
bgneal@312 12682 });
bgneal@312 12683
bgneal@312 12684 t.features = s;
bgneal@312 12685 t.params = p;
bgneal@312 12686 t.onOpen.dispatch(t, s, p);
bgneal@312 12687
bgneal@312 12688 u = s.url || s.file;
bgneal@312 12689 u = tinymce._addVer(u);
bgneal@312 12690
bgneal@312 12691 try {
bgneal@312 12692 if (isIE && mo) {
bgneal@312 12693 w = 1;
bgneal@312 12694 window.showModalDialog(u, window, f);
bgneal@312 12695 } else
bgneal@312 12696 w = window.open(u, s.name, f);
bgneal@312 12697 } catch (ex) {
bgneal@312 12698 // Ignore
bgneal@312 12699 }
bgneal@312 12700
bgneal@312 12701 if (!w)
bgneal@312 12702 alert(t.editor.getLang('popup_blocked'));
bgneal@312 12703 },
bgneal@312 12704
bgneal@312 12705 close : function(w) {
bgneal@312 12706 w.close();
bgneal@312 12707 this.onClose.dispatch(this);
bgneal@312 12708 },
bgneal@312 12709
bgneal@312 12710 createInstance : function(cl, a, b, c, d, e) {
bgneal@312 12711 var f = tinymce.resolve(cl);
bgneal@312 12712
bgneal@312 12713 return new f(a, b, c, d, e);
bgneal@312 12714 },
bgneal@312 12715
bgneal@312 12716 confirm : function(t, cb, s, w) {
bgneal@312 12717 w = w || window;
bgneal@312 12718
bgneal@312 12719 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
bgneal@312 12720 },
bgneal@312 12721
bgneal@312 12722 alert : function(tx, cb, s, w) {
bgneal@312 12723 var t = this;
bgneal@312 12724
bgneal@312 12725 w = w || window;
bgneal@312 12726 w.alert(t._decode(t.editor.getLang(tx, tx)));
bgneal@312 12727
bgneal@312 12728 if (cb)
bgneal@312 12729 cb.call(s || t);
bgneal@312 12730 },
bgneal@312 12731
bgneal@312 12732 resizeBy : function(dw, dh, win) {
bgneal@312 12733 win.resizeBy(dw, dh);
bgneal@312 12734 },
bgneal@312 12735
bgneal@312 12736 // Internal functions
bgneal@312 12737
bgneal@312 12738 _decode : function(s) {
bgneal@312 12739 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
bgneal@312 12740 }
bgneal@312 12741 });
bgneal@312 12742 }(tinymce));
bgneal@312 12743 (function(tinymce) {
bgneal@312 12744 function CommandManager() {
bgneal@312 12745 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
bgneal@312 12746
bgneal@312 12747 function add(collection, cmd, func, scope) {
bgneal@312 12748 if (typeof(cmd) == 'string')
bgneal@312 12749 cmd = [cmd];
bgneal@312 12750
bgneal@312 12751 tinymce.each(cmd, function(cmd) {
bgneal@312 12752 collection[cmd.toLowerCase()] = {func : func, scope : scope};
bgneal@312 12753 });
bgneal@312 12754 };
bgneal@312 12755
bgneal@312 12756 tinymce.extend(this, {
bgneal@312 12757 add : function(cmd, func, scope) {
bgneal@312 12758 add(execCommands, cmd, func, scope);
bgneal@312 12759 },
bgneal@312 12760
bgneal@312 12761 addQueryStateHandler : function(cmd, func, scope) {
bgneal@312 12762 add(queryStateCommands, cmd, func, scope);
bgneal@312 12763 },
bgneal@312 12764
bgneal@312 12765 addQueryValueHandler : function(cmd, func, scope) {
bgneal@312 12766 add(queryValueCommands, cmd, func, scope);
bgneal@312 12767 },
bgneal@312 12768
bgneal@312 12769 execCommand : function(scope, cmd, ui, value, args) {
bgneal@312 12770 if (cmd = execCommands[cmd.toLowerCase()]) {
bgneal@312 12771 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
bgneal@312 12772 return true;
bgneal@312 12773 }
bgneal@312 12774 },
bgneal@312 12775
bgneal@312 12776 queryCommandValue : function() {
bgneal@312 12777 if (cmd = queryValueCommands[cmd.toLowerCase()])
bgneal@312 12778 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@312 12779 },
bgneal@312 12780
bgneal@312 12781 queryCommandState : function() {
bgneal@312 12782 if (cmd = queryStateCommands[cmd.toLowerCase()])
bgneal@312 12783 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@312 12784 }
bgneal@312 12785 });
bgneal@312 12786 };
bgneal@312 12787
bgneal@312 12788 tinymce.GlobalCommands = new CommandManager();
bgneal@312 12789 })(tinymce);
bgneal@312 12790 (function(tinymce) {
bgneal@312 12791 tinymce.Formatter = function(ed) {
bgneal@312 12792 var formats = {},
bgneal@312 12793 each = tinymce.each,
bgneal@312 12794 dom = ed.dom,
bgneal@312 12795 selection = ed.selection,
bgneal@312 12796 TreeWalker = tinymce.dom.TreeWalker,
bgneal@312 12797 rangeUtils = new tinymce.dom.RangeUtils(dom),
bgneal@312 12798 isValid = ed.schema.isValid,
bgneal@312 12799 isBlock = dom.isBlock,
bgneal@312 12800 forcedRootBlock = ed.settings.forced_root_block,
bgneal@312 12801 nodeIndex = dom.nodeIndex,
bgneal@312 12802 INVISIBLE_CHAR = '\uFEFF',
bgneal@312 12803 MCE_ATTR_RE = /^(src|href|style)$/,
bgneal@312 12804 FALSE = false,
bgneal@312 12805 TRUE = true,
bgneal@312 12806 undefined,
bgneal@312 12807 pendingFormats = {apply : [], remove : []};
bgneal@312 12808
bgneal@312 12809 function isArray(obj) {
bgneal@312 12810 return obj instanceof Array;
bgneal@312 12811 };
bgneal@312 12812
bgneal@312 12813 function getParents(node, selector) {
bgneal@312 12814 return dom.getParents(node, selector, dom.getRoot());
bgneal@312 12815 };
bgneal@312 12816
bgneal@312 12817 function isCaretNode(node) {
bgneal@312 12818 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
bgneal@312 12819 };
bgneal@312 12820
bgneal@312 12821 // Public functions
bgneal@312 12822
bgneal@312 12823 function get(name) {
bgneal@312 12824 return name ? formats[name] : formats;
bgneal@312 12825 };
bgneal@312 12826
bgneal@312 12827 function register(name, format) {
bgneal@312 12828 if (name) {
bgneal@312 12829 if (typeof(name) !== 'string') {
bgneal@312 12830 each(name, function(format, name) {
bgneal@312 12831 register(name, format);
bgneal@312 12832 });
bgneal@312 12833 } else {
bgneal@312 12834 // Force format into array and add it to internal collection
bgneal@312 12835 format = format.length ? format : [format];
bgneal@312 12836
bgneal@312 12837 each(format, function(format) {
bgneal@312 12838 // Set deep to false by default on selector formats this to avoid removing
bgneal@312 12839 // alignment on images inside paragraphs when alignment is changed on paragraphs
bgneal@312 12840 if (format.deep === undefined)
bgneal@312 12841 format.deep = !format.selector;
bgneal@312 12842
bgneal@312 12843 // Default to true
bgneal@312 12844 if (format.split === undefined)
bgneal@312 12845 format.split = !format.selector || format.inline;
bgneal@312 12846
bgneal@312 12847 // Default to true
bgneal@312 12848 if (format.remove === undefined && format.selector && !format.inline)
bgneal@312 12849 format.remove = 'none';
bgneal@312 12850
bgneal@312 12851 // Mark format as a mixed format inline + block level
bgneal@312 12852 if (format.selector && format.inline) {
bgneal@312 12853 format.mixed = true;
bgneal@312 12854 format.block_expand = true;
bgneal@312 12855 }
bgneal@312 12856
bgneal@312 12857 // Split classes if needed
bgneal@312 12858 if (typeof(format.classes) === 'string')
bgneal@312 12859 format.classes = format.classes.split(/\s+/);
bgneal@312 12860 });
bgneal@312 12861
bgneal@312 12862 formats[name] = format;
bgneal@312 12863 }
bgneal@312 12864 }
bgneal@312 12865 };
bgneal@312 12866
bgneal@312 12867 function apply(name, vars, node) {
bgneal@312 12868 var formatList = get(name), format = formatList[0], bookmark, rng, i;
bgneal@312 12869
bgneal@312 12870 function moveStart(rng) {
bgneal@312 12871 var container = rng.startContainer,
bgneal@312 12872 offset = rng.startOffset,
bgneal@312 12873 walker, node;
bgneal@312 12874
bgneal@312 12875 // Move startContainer/startOffset in to a suitable node
bgneal@312 12876 if (container.nodeType == 1 || container.nodeValue === "") {
bgneal@312 12877 container = container.nodeType == 1 ? container.childNodes[offset] : container;
bgneal@312 12878
bgneal@312 12879 // Might fail if the offset is behind the last element in it's container
bgneal@312 12880 if (container) {
bgneal@312 12881 walker = new TreeWalker(container, container.parentNode);
bgneal@312 12882 for (node = walker.current(); node; node = walker.next()) {
bgneal@312 12883 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@312 12884 rng.setStart(node, 0);
bgneal@312 12885 break;
bgneal@312 12886 }
bgneal@312 12887 }
bgneal@312 12888 }
bgneal@312 12889 }
bgneal@312 12890
bgneal@312 12891 return rng;
bgneal@312 12892 };
bgneal@312 12893
bgneal@312 12894 function setElementFormat(elm, fmt) {
bgneal@312 12895 fmt = fmt || format;
bgneal@312 12896
bgneal@312 12897 if (elm) {
bgneal@312 12898 each(fmt.styles, function(value, name) {
bgneal@312 12899 dom.setStyle(elm, name, replaceVars(value, vars));
bgneal@312 12900 });
bgneal@312 12901
bgneal@312 12902 each(fmt.attributes, function(value, name) {
bgneal@312 12903 dom.setAttrib(elm, name, replaceVars(value, vars));
bgneal@312 12904 });
bgneal@312 12905
bgneal@312 12906 each(fmt.classes, function(value) {
bgneal@312 12907 value = replaceVars(value, vars);
bgneal@312 12908
bgneal@312 12909 if (!dom.hasClass(elm, value))
bgneal@312 12910 dom.addClass(elm, value);
bgneal@312 12911 });
bgneal@312 12912 }
bgneal@312 12913 };
bgneal@312 12914
bgneal@312 12915 function applyRngStyle(rng) {
bgneal@312 12916 var newWrappers = [], wrapName, wrapElm;
bgneal@312 12917
bgneal@312 12918 // Setup wrapper element
bgneal@312 12919 wrapName = format.inline || format.block;
bgneal@312 12920 wrapElm = dom.create(wrapName);
bgneal@312 12921 setElementFormat(wrapElm);
bgneal@312 12922
bgneal@312 12923 rangeUtils.walk(rng, function(nodes) {
bgneal@312 12924 var currentWrapElm;
bgneal@312 12925
bgneal@312 12926 function process(node) {
bgneal@312 12927 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
bgneal@312 12928
bgneal@312 12929 // Stop wrapping on br elements
bgneal@312 12930 if (isEq(nodeName, 'br')) {
bgneal@312 12931 currentWrapElm = 0;
bgneal@312 12932
bgneal@312 12933 // Remove any br elements when we wrap things
bgneal@312 12934 if (format.block)
bgneal@312 12935 dom.remove(node);
bgneal@312 12936
bgneal@312 12937 return;
bgneal@312 12938 }
bgneal@312 12939
bgneal@312 12940 // If node is wrapper type
bgneal@312 12941 if (format.wrapper && matchNode(node, name, vars)) {
bgneal@312 12942 currentWrapElm = 0;
bgneal@312 12943 return;
bgneal@312 12944 }
bgneal@312 12945
bgneal@312 12946 // Can we rename the block
bgneal@312 12947 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
bgneal@312 12948 node = dom.rename(node, wrapName);
bgneal@312 12949 setElementFormat(node);
bgneal@312 12950 newWrappers.push(node);
bgneal@312 12951 currentWrapElm = 0;
bgneal@312 12952 return;
bgneal@312 12953 }
bgneal@312 12954
bgneal@312 12955 // Handle selector patterns
bgneal@312 12956 if (format.selector) {
bgneal@312 12957 // Look for matching formats
bgneal@312 12958 each(formatList, function(format) {
bgneal@312 12959 if (dom.is(node, format.selector) && !isCaretNode(node)) {
bgneal@312 12960 setElementFormat(node, format);
bgneal@312 12961 found = true;
bgneal@312 12962 }
bgneal@312 12963 });
bgneal@312 12964
bgneal@312 12965 // Continue processing if a selector match wasn't found and a inline element is defined
bgneal@312 12966 if (!format.inline || found) {
bgneal@312 12967 currentWrapElm = 0;
bgneal@312 12968 return;
bgneal@312 12969 }
bgneal@312 12970 }
bgneal@312 12971
bgneal@312 12972 // Is it valid to wrap this item
bgneal@312 12973 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
bgneal@312 12974 // Start wrapping
bgneal@312 12975 if (!currentWrapElm) {
bgneal@312 12976 // Wrap the node
bgneal@312 12977 currentWrapElm = wrapElm.cloneNode(FALSE);
bgneal@312 12978 node.parentNode.insertBefore(currentWrapElm, node);
bgneal@312 12979 newWrappers.push(currentWrapElm);
bgneal@312 12980 }
bgneal@312 12981
bgneal@312 12982 currentWrapElm.appendChild(node);
bgneal@312 12983 } else {
bgneal@312 12984 // Start a new wrapper for possible children
bgneal@312 12985 currentWrapElm = 0;
bgneal@312 12986
bgneal@312 12987 each(tinymce.grep(node.childNodes), process);
bgneal@312 12988
bgneal@312 12989 // End the last wrapper
bgneal@312 12990 currentWrapElm = 0;
bgneal@312 12991 }
bgneal@312 12992 };
bgneal@312 12993
bgneal@312 12994 // Process siblings from range
bgneal@312 12995 each(nodes, process);
bgneal@312 12996 });
bgneal@312 12997
bgneal@312 12998 // Cleanup
bgneal@312 12999 each(newWrappers, function(node) {
bgneal@312 13000 var childCount;
bgneal@312 13001
bgneal@312 13002 function getChildCount(node) {
bgneal@312 13003 var count = 0;
bgneal@312 13004
bgneal@312 13005 each(node.childNodes, function(node) {
bgneal@312 13006 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
bgneal@312 13007 count++;
bgneal@312 13008 });
bgneal@312 13009
bgneal@312 13010 return count;
bgneal@312 13011 };
bgneal@312 13012
bgneal@312 13013 function mergeStyles(node) {
bgneal@312 13014 var child, clone;
bgneal@312 13015
bgneal@312 13016 each(node.childNodes, function(node) {
bgneal@312 13017 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
bgneal@312 13018 child = node;
bgneal@312 13019 return FALSE; // break loop
bgneal@312 13020 }
bgneal@312 13021 });
bgneal@312 13022
bgneal@312 13023 // If child was found and of the same type as the current node
bgneal@312 13024 if (child && matchName(child, format)) {
bgneal@312 13025 clone = child.cloneNode(FALSE);
bgneal@312 13026 setElementFormat(clone);
bgneal@312 13027
bgneal@312 13028 dom.replace(clone, node, TRUE);
bgneal@312 13029 dom.remove(child, 1);
bgneal@312 13030 }
bgneal@312 13031
bgneal@312 13032 return clone || node;
bgneal@312 13033 };
bgneal@312 13034
bgneal@312 13035 childCount = getChildCount(node);
bgneal@312 13036
bgneal@312 13037 // Remove empty nodes
bgneal@312 13038 if (childCount === 0) {
bgneal@312 13039 dom.remove(node, 1);
bgneal@312 13040 return;
bgneal@312 13041 }
bgneal@312 13042
bgneal@312 13043 if (format.inline || format.wrapper) {
bgneal@312 13044 // Merges the current node with it's children of similar type to reduce the number of elements
bgneal@312 13045 if (!format.exact && childCount === 1)
bgneal@312 13046 node = mergeStyles(node);
bgneal@312 13047
bgneal@312 13048 // Remove/merge children
bgneal@312 13049 each(formatList, function(format) {
bgneal@312 13050 // Merge all children of similar type will move styles from child to parent
bgneal@312 13051 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
bgneal@312 13052 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
bgneal@312 13053 each(dom.select(format.inline, node), function(child) {
bgneal@312 13054 removeFormat(format, vars, child, format.exact ? child : null);
bgneal@312 13055 });
bgneal@312 13056 });
bgneal@312 13057
bgneal@312 13058 // Remove child if direct parent is of same type
bgneal@312 13059 if (matchNode(node.parentNode, name, vars)) {
bgneal@312 13060 dom.remove(node, 1);
bgneal@312 13061 node = 0;
bgneal@312 13062 return TRUE;
bgneal@312 13063 }
bgneal@312 13064
bgneal@312 13065 // Look for parent with similar style format
bgneal@312 13066 if (format.merge_with_parents) {
bgneal@312 13067 dom.getParent(node.parentNode, function(parent) {
bgneal@312 13068 if (matchNode(parent, name, vars)) {
bgneal@312 13069 dom.remove(node, 1);
bgneal@312 13070 node = 0;
bgneal@312 13071 return TRUE;
bgneal@312 13072 }
bgneal@312 13073 });
bgneal@312 13074 }
bgneal@312 13075
bgneal@312 13076 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
bgneal@312 13077 if (node) {
bgneal@312 13078 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
bgneal@312 13079 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
bgneal@312 13080 }
bgneal@312 13081 }
bgneal@312 13082 });
bgneal@312 13083 };
bgneal@312 13084
bgneal@312 13085 if (format) {
bgneal@312 13086 if (node) {
bgneal@312 13087 rng = dom.createRng();
bgneal@312 13088
bgneal@312 13089 rng.setStartBefore(node);
bgneal@312 13090 rng.setEndAfter(node);
bgneal@312 13091
bgneal@312 13092 applyRngStyle(expandRng(rng, formatList));
bgneal@312 13093 } else {
bgneal@312 13094 if (!selection.isCollapsed() || !format.inline) {
bgneal@312 13095 // Apply formatting to selection
bgneal@312 13096 bookmark = selection.getBookmark();
bgneal@312 13097 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
bgneal@312 13098
bgneal@312 13099 selection.moveToBookmark(bookmark);
bgneal@312 13100 selection.setRng(moveStart(selection.getRng(TRUE)));
bgneal@312 13101 ed.nodeChanged();
bgneal@312 13102 } else
bgneal@312 13103 performCaretAction('apply', name, vars);
bgneal@312 13104 }
bgneal@312 13105 }
bgneal@312 13106 };
bgneal@312 13107
bgneal@312 13108 function remove(name, vars, node) {
bgneal@312 13109 var formatList = get(name), format = formatList[0], bookmark, i, rng;
bgneal@312 13110
bgneal@312 13111 function moveStart(rng) {
bgneal@312 13112 var container = rng.startContainer,
bgneal@312 13113 offset = rng.startOffset,
bgneal@312 13114 walker, node, nodes, tmpNode;
bgneal@312 13115
bgneal@312 13116 // Convert text node into index if possible
bgneal@312 13117 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
bgneal@312 13118 container = container.parentNode;
bgneal@312 13119 offset = nodeIndex(container) + 1;
bgneal@312 13120 }
bgneal@312 13121
bgneal@312 13122 // Move startContainer/startOffset in to a suitable node
bgneal@312 13123 if (container.nodeType == 1) {
bgneal@312 13124 nodes = container.childNodes;
bgneal@312 13125 container = nodes[Math.min(offset, nodes.length - 1)];
bgneal@312 13126 walker = new TreeWalker(container);
bgneal@312 13127
bgneal@312 13128 // If offset is at end of the parent node walk to the next one
bgneal@312 13129 if (offset > nodes.length - 1)
bgneal@312 13130 walker.next();
bgneal@312 13131
bgneal@312 13132 for (node = walker.current(); node; node = walker.next()) {
bgneal@312 13133 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@312 13134 // IE has a "neat" feature where it moves the start node into the closest element
bgneal@312 13135 // we can avoid this by inserting an element before it and then remove it after we set the selection
bgneal@312 13136 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
bgneal@312 13137 node.parentNode.insertBefore(tmpNode, node);
bgneal@312 13138
bgneal@312 13139 // Set selection and remove tmpNode
bgneal@312 13140 rng.setStart(node, 0);
bgneal@312 13141 selection.setRng(rng);
bgneal@312 13142 dom.remove(tmpNode);
bgneal@312 13143
bgneal@312 13144 return;
bgneal@312 13145 }
bgneal@312 13146 }
bgneal@312 13147 }
bgneal@312 13148 };
bgneal@312 13149
bgneal@312 13150 // Merges the styles for each node
bgneal@312 13151 function process(node) {
bgneal@312 13152 var children, i, l;
bgneal@312 13153
bgneal@312 13154 // Grab the children first since the nodelist might be changed
bgneal@312 13155 children = tinymce.grep(node.childNodes);
bgneal@312 13156
bgneal@312 13157 // Process current node
bgneal@312 13158 for (i = 0, l = formatList.length; i < l; i++) {
bgneal@312 13159 if (removeFormat(formatList[i], vars, node, node))
bgneal@312 13160 break;
bgneal@312 13161 }
bgneal@312 13162
bgneal@312 13163 // Process the children
bgneal@312 13164 if (format.deep) {
bgneal@312 13165 for (i = 0, l = children.length; i < l; i++)
bgneal@312 13166 process(children[i]);
bgneal@312 13167 }
bgneal@312 13168 };
bgneal@312 13169
bgneal@312 13170 function findFormatRoot(container) {
bgneal@312 13171 var formatRoot;
bgneal@312 13172
bgneal@312 13173 // Find format root
bgneal@312 13174 each(getParents(container.parentNode).reverse(), function(parent) {
bgneal@312 13175 var format;
bgneal@312 13176
bgneal@312 13177 // Find format root element
bgneal@312 13178 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
bgneal@312 13179 // Is the node matching the format we are looking for
bgneal@312 13180 format = matchNode(parent, name, vars);
bgneal@312 13181 if (format && format.split !== false)
bgneal@312 13182 formatRoot = parent;
bgneal@312 13183 }
bgneal@312 13184 });
bgneal@312 13185
bgneal@312 13186 return formatRoot;
bgneal@312 13187 };
bgneal@312 13188
bgneal@312 13189 function wrapAndSplit(format_root, container, target, split) {
bgneal@312 13190 var parent, clone, lastClone, firstClone, i, formatRootParent;
bgneal@312 13191
bgneal@312 13192 // Format root found then clone formats and split it
bgneal@312 13193 if (format_root) {
bgneal@312 13194 formatRootParent = format_root.parentNode;
bgneal@312 13195
bgneal@312 13196 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
bgneal@312 13197 clone = parent.cloneNode(FALSE);
bgneal@312 13198
bgneal@312 13199 for (i = 0; i < formatList.length; i++) {
bgneal@312 13200 if (removeFormat(formatList[i], vars, clone, clone)) {
bgneal@312 13201 clone = 0;
bgneal@312 13202 break;
bgneal@312 13203 }
bgneal@312 13204 }
bgneal@312 13205
bgneal@312 13206 // Build wrapper node
bgneal@312 13207 if (clone) {
bgneal@312 13208 if (lastClone)
bgneal@312 13209 clone.appendChild(lastClone);
bgneal@312 13210
bgneal@312 13211 if (!firstClone)
bgneal@312 13212 firstClone = clone;
bgneal@312 13213
bgneal@312 13214 lastClone = clone;
bgneal@312 13215 }
bgneal@312 13216 }
bgneal@312 13217
bgneal@312 13218 // Never split block elements if the format is mixed
bgneal@312 13219 if (split && (!format.mixed || !isBlock(format_root)))
bgneal@312 13220 container = dom.split(format_root, container);
bgneal@312 13221
bgneal@312 13222 // Wrap container in cloned formats
bgneal@312 13223 if (lastClone) {
bgneal@312 13224 target.parentNode.insertBefore(lastClone, target);
bgneal@312 13225 firstClone.appendChild(target);
bgneal@312 13226 }
bgneal@312 13227 }
bgneal@312 13228
bgneal@312 13229 return container;
bgneal@312 13230 };
bgneal@312 13231
bgneal@312 13232 function splitToFormatRoot(container) {
bgneal@312 13233 return wrapAndSplit(findFormatRoot(container), container, container, true);
bgneal@312 13234 };
bgneal@312 13235
bgneal@312 13236 function unwrap(start) {
bgneal@312 13237 var node = dom.get(start ? '_start' : '_end'),
bgneal@312 13238 out = node[start ? 'firstChild' : 'lastChild'];
bgneal@312 13239
bgneal@312 13240 // If the end is placed within the start the result will be removed
bgneal@312 13241 // So this checks if the out node is a bookmark node if it is it
bgneal@312 13242 // checks for another more suitable node
bgneal@312 13243 if (isBookmarkNode(out))
bgneal@312 13244 out = out[start ? 'firstChild' : 'lastChild'];
bgneal@312 13245
bgneal@312 13246 dom.remove(node, true);
bgneal@312 13247
bgneal@312 13248 return out;
bgneal@312 13249 };
bgneal@312 13250
bgneal@312 13251 function removeRngStyle(rng) {
bgneal@312 13252 var startContainer, endContainer;
bgneal@312 13253
bgneal@312 13254 rng = expandRng(rng, formatList, TRUE);
bgneal@312 13255
bgneal@312 13256 if (format.split) {
bgneal@312 13257 startContainer = getContainer(rng, TRUE);
bgneal@312 13258 endContainer = getContainer(rng);
bgneal@312 13259
bgneal@312 13260 if (startContainer != endContainer) {
bgneal@312 13261 // Wrap start/end nodes in span element since these might be cloned/moved
bgneal@312 13262 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
bgneal@312 13263 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
bgneal@312 13264
bgneal@312 13265 // Split start/end
bgneal@312 13266 splitToFormatRoot(startContainer);
bgneal@312 13267 splitToFormatRoot(endContainer);
bgneal@312 13268
bgneal@312 13269 // Unwrap start/end to get real elements again
bgneal@312 13270 startContainer = unwrap(TRUE);
bgneal@312 13271 endContainer = unwrap();
bgneal@312 13272 } else
bgneal@312 13273 startContainer = endContainer = splitToFormatRoot(startContainer);
bgneal@312 13274
bgneal@312 13275 // Update range positions since they might have changed after the split operations
bgneal@312 13276 rng.startContainer = startContainer.parentNode;
bgneal@312 13277 rng.startOffset = nodeIndex(startContainer);
bgneal@312 13278 rng.endContainer = endContainer.parentNode;
bgneal@312 13279 rng.endOffset = nodeIndex(endContainer) + 1;
bgneal@312 13280 }
bgneal@312 13281
bgneal@312 13282 // Remove items between start/end
bgneal@312 13283 rangeUtils.walk(rng, function(nodes) {
bgneal@312 13284 each(nodes, function(node) {
bgneal@312 13285 process(node);
bgneal@312 13286 });
bgneal@312 13287 });
bgneal@312 13288 };
bgneal@312 13289
bgneal@312 13290 // Handle node
bgneal@312 13291 if (node) {
bgneal@312 13292 rng = dom.createRng();
bgneal@312 13293 rng.setStartBefore(node);
bgneal@312 13294 rng.setEndAfter(node);
bgneal@312 13295 removeRngStyle(rng);
bgneal@312 13296 return;
bgneal@312 13297 }
bgneal@312 13298
bgneal@312 13299 if (!selection.isCollapsed() || !format.inline) {
bgneal@312 13300 bookmark = selection.getBookmark();
bgneal@312 13301 removeRngStyle(selection.getRng(TRUE));
bgneal@312 13302 selection.moveToBookmark(bookmark);
bgneal@312 13303
bgneal@312 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@312 13305 if (match(name, vars, selection.getStart())) {
bgneal@312 13306 moveStart(selection.getRng(true));
bgneal@312 13307 }
bgneal@312 13308
bgneal@312 13309 ed.nodeChanged();
bgneal@312 13310 } else
bgneal@312 13311 performCaretAction('remove', name, vars);
bgneal@312 13312 };
bgneal@312 13313
bgneal@312 13314 function toggle(name, vars, node) {
bgneal@312 13315 if (match(name, vars, node))
bgneal@312 13316 remove(name, vars, node);
bgneal@312 13317 else
bgneal@312 13318 apply(name, vars, node);
bgneal@312 13319 };
bgneal@312 13320
bgneal@312 13321 function matchNode(node, name, vars, similar) {
bgneal@312 13322 var formatList = get(name), format, i, classes;
bgneal@312 13323
bgneal@312 13324 function matchItems(node, format, item_name) {
bgneal@312 13325 var key, value, items = format[item_name], i;
bgneal@312 13326
bgneal@312 13327 // Check all items
bgneal@312 13328 if (items) {
bgneal@312 13329 // Non indexed object
bgneal@312 13330 if (items.length === undefined) {
bgneal@312 13331 for (key in items) {
bgneal@312 13332 if (items.hasOwnProperty(key)) {
bgneal@312 13333 if (item_name === 'attributes')
bgneal@312 13334 value = dom.getAttrib(node, key);
bgneal@312 13335 else
bgneal@312 13336 value = getStyle(node, key);
bgneal@312 13337
bgneal@312 13338 if (similar && !value && !format.exact)
bgneal@312 13339 return;
bgneal@312 13340
bgneal@312 13341 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
bgneal@312 13342 return;
bgneal@312 13343 }
bgneal@312 13344 }
bgneal@312 13345 } else {
bgneal@312 13346 // Only one match needed for indexed arrays
bgneal@312 13347 for (i = 0; i < items.length; i++) {
bgneal@312 13348 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
bgneal@312 13349 return format;
bgneal@312 13350 }
bgneal@312 13351 }
bgneal@312 13352 }
bgneal@312 13353
bgneal@312 13354 return format;
bgneal@312 13355 };
bgneal@312 13356
bgneal@312 13357 if (formatList && node) {
bgneal@312 13358 // Check each format in list
bgneal@312 13359 for (i = 0; i < formatList.length; i++) {
bgneal@312 13360 format = formatList[i];
bgneal@312 13361
bgneal@312 13362 // Name name, attributes, styles and classes
bgneal@312 13363 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
bgneal@312 13364 // Match classes
bgneal@312 13365 if (classes = format.classes) {
bgneal@312 13366 for (i = 0; i < classes.length; i++) {
bgneal@312 13367 if (!dom.hasClass(node, classes[i]))
bgneal@312 13368 return;
bgneal@312 13369 }
bgneal@312 13370 }
bgneal@312 13371
bgneal@312 13372 return format;
bgneal@312 13373 }
bgneal@312 13374 }
bgneal@312 13375 }
bgneal@312 13376 };
bgneal@312 13377
bgneal@312 13378 function match(name, vars, node) {
bgneal@312 13379 var startNode, i;
bgneal@312 13380
bgneal@312 13381 function matchParents(node) {
bgneal@312 13382 // Find first node with similar format settings
bgneal@312 13383 node = dom.getParent(node, function(node) {
bgneal@312 13384 return !!matchNode(node, name, vars, true);
bgneal@312 13385 });
bgneal@312 13386
bgneal@312 13387 // Do an exact check on the similar format element
bgneal@312 13388 return matchNode(node, name, vars);
bgneal@312 13389 };
bgneal@312 13390
bgneal@312 13391 // Check specified node
bgneal@312 13392 if (node)
bgneal@312 13393 return matchParents(node);
bgneal@312 13394
bgneal@312 13395 // Check pending formats
bgneal@312 13396 if (selection.isCollapsed()) {
bgneal@312 13397 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@312 13398 if (pendingFormats.apply[i].name == name)
bgneal@312 13399 return true;
bgneal@312 13400 }
bgneal@312 13401
bgneal@312 13402 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@312 13403 if (pendingFormats.remove[i].name == name)
bgneal@312 13404 return false;
bgneal@312 13405 }
bgneal@312 13406
bgneal@312 13407 return matchParents(selection.getNode());
bgneal@312 13408 }
bgneal@312 13409
bgneal@312 13410 // Check selected node
bgneal@312 13411 node = selection.getNode();
bgneal@312 13412 if (matchParents(node))
bgneal@312 13413 return TRUE;
bgneal@312 13414
bgneal@312 13415 // Check start node if it's different
bgneal@312 13416 startNode = selection.getStart();
bgneal@312 13417 if (startNode != node) {
bgneal@312 13418 if (matchParents(startNode))
bgneal@312 13419 return TRUE;
bgneal@312 13420 }
bgneal@312 13421
bgneal@312 13422 return FALSE;
bgneal@312 13423 };
bgneal@312 13424
bgneal@312 13425 function matchAll(names, vars) {
bgneal@312 13426 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
bgneal@312 13427
bgneal@312 13428 // If the selection is collapsed then check pending formats
bgneal@312 13429 if (selection.isCollapsed()) {
bgneal@312 13430 for (ni = 0; ni < names.length; ni++) {
bgneal@312 13431 // If the name is to be removed, then stop it from being added
bgneal@312 13432 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@312 13433 name = names[ni];
bgneal@312 13434
bgneal@312 13435 if (pendingFormats.remove[i].name == name) {
bgneal@312 13436 checkedMap[name] = true;
bgneal@312 13437 break;
bgneal@312 13438 }
bgneal@312 13439 }
bgneal@312 13440 }
bgneal@312 13441
bgneal@312 13442 // If the format is to be applied
bgneal@312 13443 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@312 13444 for (ni = 0; ni < names.length; ni++) {
bgneal@312 13445 name = names[ni];
bgneal@312 13446
bgneal@312 13447 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
bgneal@312 13448 checkedMap[name] = true;
bgneal@312 13449 matchedFormatNames.push(name);
bgneal@312 13450 }
bgneal@312 13451 }
bgneal@312 13452 }
bgneal@312 13453 }
bgneal@312 13454
bgneal@312 13455 // Check start of selection for formats
bgneal@312 13456 startElement = selection.getStart();
bgneal@312 13457 dom.getParent(startElement, function(node) {
bgneal@312 13458 var i, name;
bgneal@312 13459
bgneal@312 13460 for (i = 0; i < names.length; i++) {
bgneal@312 13461 name = names[i];
bgneal@312 13462
bgneal@312 13463 if (!checkedMap[name] && matchNode(node, name, vars)) {
bgneal@312 13464 checkedMap[name] = true;
bgneal@312 13465 matchedFormatNames.push(name);
bgneal@312 13466 }
bgneal@312 13467 }
bgneal@312 13468 });
bgneal@312 13469
bgneal@312 13470 return matchedFormatNames;
bgneal@312 13471 };
bgneal@312 13472
bgneal@312 13473 function canApply(name) {
bgneal@312 13474 var formatList = get(name), startNode, parents, i, x, selector;
bgneal@312 13475
bgneal@312 13476 if (formatList) {
bgneal@312 13477 startNode = selection.getStart();
bgneal@312 13478 parents = getParents(startNode);
bgneal@312 13479
bgneal@312 13480 for (x = formatList.length - 1; x >= 0; x--) {
bgneal@312 13481 selector = formatList[x].selector;
bgneal@312 13482
bgneal@312 13483 // Format is not selector based, then always return TRUE
bgneal@312 13484 if (!selector)
bgneal@312 13485 return TRUE;
bgneal@312 13486
bgneal@312 13487 for (i = parents.length - 1; i >= 0; i--) {
bgneal@312 13488 if (dom.is(parents[i], selector))
bgneal@312 13489 return TRUE;
bgneal@312 13490 }
bgneal@312 13491 }
bgneal@312 13492 }
bgneal@312 13493
bgneal@312 13494 return FALSE;
bgneal@312 13495 };
bgneal@312 13496
bgneal@312 13497 // Expose to public
bgneal@312 13498 tinymce.extend(this, {
bgneal@312 13499 get : get,
bgneal@312 13500 register : register,
bgneal@312 13501 apply : apply,
bgneal@312 13502 remove : remove,
bgneal@312 13503 toggle : toggle,
bgneal@312 13504 match : match,
bgneal@312 13505 matchAll : matchAll,
bgneal@312 13506 matchNode : matchNode,
bgneal@312 13507 canApply : canApply
bgneal@312 13508 });
bgneal@312 13509
bgneal@312 13510 // Private functions
bgneal@312 13511
bgneal@312 13512 function matchName(node, format) {
bgneal@312 13513 // Check for inline match
bgneal@312 13514 if (isEq(node, format.inline))
bgneal@312 13515 return TRUE;
bgneal@312 13516
bgneal@312 13517 // Check for block match
bgneal@312 13518 if (isEq(node, format.block))
bgneal@312 13519 return TRUE;
bgneal@312 13520
bgneal@312 13521 // Check for selector match
bgneal@312 13522 if (format.selector)
bgneal@312 13523 return dom.is(node, format.selector);
bgneal@312 13524 };
bgneal@312 13525
bgneal@312 13526 function isEq(str1, str2) {
bgneal@312 13527 str1 = str1 || '';
bgneal@312 13528 str2 = str2 || '';
bgneal@312 13529
bgneal@312 13530 str1 = '' + (str1.nodeName || str1);
bgneal@312 13531 str2 = '' + (str2.nodeName || str2);
bgneal@312 13532
bgneal@312 13533 return str1.toLowerCase() == str2.toLowerCase();
bgneal@312 13534 };
bgneal@312 13535
bgneal@312 13536 function getStyle(node, name) {
bgneal@312 13537 var styleVal = dom.getStyle(node, name);
bgneal@312 13538
bgneal@312 13539 // Force the format to hex
bgneal@312 13540 if (name == 'color' || name == 'backgroundColor')
bgneal@312 13541 styleVal = dom.toHex(styleVal);
bgneal@312 13542
bgneal@312 13543 // Opera will return bold as 700
bgneal@312 13544 if (name == 'fontWeight' && styleVal == 700)
bgneal@312 13545 styleVal = 'bold';
bgneal@312 13546
bgneal@312 13547 return '' + styleVal;
bgneal@312 13548 };
bgneal@312 13549
bgneal@312 13550 function replaceVars(value, vars) {
bgneal@312 13551 if (typeof(value) != "string")
bgneal@312 13552 value = value(vars);
bgneal@312 13553 else if (vars) {
bgneal@312 13554 value = value.replace(/%(\w+)/g, function(str, name) {
bgneal@312 13555 return vars[name] || str;
bgneal@312 13556 });
bgneal@312 13557 }
bgneal@312 13558
bgneal@312 13559 return value;
bgneal@312 13560 };
bgneal@312 13561
bgneal@312 13562 function isWhiteSpaceNode(node) {
bgneal@312 13563 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
bgneal@312 13564 };
bgneal@312 13565
bgneal@312 13566 function wrap(node, name, attrs) {
bgneal@312 13567 var wrapper = dom.create(name, attrs);
bgneal@312 13568
bgneal@312 13569 node.parentNode.insertBefore(wrapper, node);
bgneal@312 13570 wrapper.appendChild(node);
bgneal@312 13571
bgneal@312 13572 return wrapper;
bgneal@312 13573 };
bgneal@312 13574
bgneal@312 13575 function expandRng(rng, format, remove) {
bgneal@312 13576 var startContainer = rng.startContainer,
bgneal@312 13577 startOffset = rng.startOffset,
bgneal@312 13578 endContainer = rng.endContainer,
bgneal@312 13579 endOffset = rng.endOffset, sibling, lastIdx;
bgneal@312 13580
bgneal@312 13581 // This function walks up the tree if there is no siblings before/after the node
bgneal@312 13582 function findParentContainer(container, child_name, sibling_name, root) {
bgneal@312 13583 var parent, child;
bgneal@312 13584
bgneal@312 13585 root = root || dom.getRoot();
bgneal@312 13586
bgneal@312 13587 for (;;) {
bgneal@312 13588 // Check if we can move up are we at root level or body level
bgneal@312 13589 parent = container.parentNode;
bgneal@312 13590
bgneal@312 13591 // Stop expanding on block elements or root depending on format
bgneal@312 13592 if (parent == root || (!format[0].block_expand && isBlock(parent)))
bgneal@312 13593 return container;
bgneal@312 13594
bgneal@312 13595 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
bgneal@312 13596 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@312 13597 return container;
bgneal@312 13598
bgneal@312 13599 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@312 13600 return container;
bgneal@312 13601 }
bgneal@312 13602
bgneal@312 13603 container = container.parentNode;
bgneal@312 13604 }
bgneal@312 13605
bgneal@312 13606 return container;
bgneal@312 13607 };
bgneal@312 13608
bgneal@312 13609 // If index based start position then resolve it
bgneal@312 13610 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
bgneal@312 13611 lastIdx = startContainer.childNodes.length - 1;
bgneal@312 13612 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
bgneal@312 13613
bgneal@312 13614 if (startContainer.nodeType == 3)
bgneal@312 13615 startOffset = 0;
bgneal@312 13616 }
bgneal@312 13617
bgneal@312 13618 // If index based end position then resolve it
bgneal@312 13619 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
bgneal@312 13620 lastIdx = endContainer.childNodes.length - 1;
bgneal@312 13621 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
bgneal@312 13622
bgneal@312 13623 if (endContainer.nodeType == 3)
bgneal@312 13624 endOffset = endContainer.nodeValue.length;
bgneal@312 13625 }
bgneal@312 13626
bgneal@312 13627 // Exclude bookmark nodes if possible
bgneal@312 13628 if (isBookmarkNode(startContainer.parentNode))
bgneal@312 13629 startContainer = startContainer.parentNode;
bgneal@312 13630
bgneal@312 13631 if (isBookmarkNode(startContainer))
bgneal@312 13632 startContainer = startContainer.nextSibling || startContainer;
bgneal@312 13633
bgneal@312 13634 if (isBookmarkNode(endContainer.parentNode))
bgneal@312 13635 endContainer = endContainer.parentNode;
bgneal@312 13636
bgneal@312 13637 if (isBookmarkNode(endContainer))
bgneal@312 13638 endContainer = endContainer.previousSibling || endContainer;
bgneal@312 13639
bgneal@312 13640 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
bgneal@312 13641 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
bgneal@312 13642 // This will reduce the number of wrapper elements that needs to be created
bgneal@312 13643 // Move start point up the tree
bgneal@312 13644 if (format[0].inline || format[0].block_expand) {
bgneal@312 13645 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@312 13646 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@312 13647 }
bgneal@312 13648
bgneal@312 13649 // Expand start/end container to matching selector
bgneal@312 13650 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
bgneal@312 13651 function findSelectorEndPoint(container, sibling_name) {
bgneal@312 13652 var parents, i, y;
bgneal@312 13653
bgneal@312 13654 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
bgneal@312 13655 container = container[sibling_name];
bgneal@312 13656
bgneal@312 13657 parents = getParents(container);
bgneal@312 13658 for (i = 0; i < parents.length; i++) {
bgneal@312 13659 for (y = 0; y < format.length; y++) {
bgneal@312 13660 if (dom.is(parents[i], format[y].selector))
bgneal@312 13661 return parents[i];
bgneal@312 13662 }
bgneal@312 13663 }
bgneal@312 13664
bgneal@312 13665 return container;
bgneal@312 13666 };
bgneal@312 13667
bgneal@312 13668 // Find new startContainer/endContainer if there is better one
bgneal@312 13669 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
bgneal@312 13670 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
bgneal@312 13671 }
bgneal@312 13672
bgneal@312 13673 // Expand start/end container to matching block element or text node
bgneal@312 13674 if (format[0].block || format[0].selector) {
bgneal@312 13675 function findBlockEndPoint(container, sibling_name, sibling_name2) {
bgneal@312 13676 var node;
bgneal@312 13677
bgneal@312 13678 // Expand to block of similar type
bgneal@312 13679 if (!format[0].wrapper)
bgneal@312 13680 node = dom.getParent(container, format[0].block);
bgneal@312 13681
bgneal@312 13682 // Expand to first wrappable block element or any block element
bgneal@312 13683 if (!node)
bgneal@312 13684 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
bgneal@312 13685
bgneal@312 13686 // Exclude inner lists from wrapping
bgneal@312 13687 if (node && format[0].wrapper)
bgneal@312 13688 node = getParents(node, 'ul,ol').reverse()[0] || node;
bgneal@312 13689
bgneal@312 13690 // Didn't find a block element look for first/last wrappable element
bgneal@312 13691 if (!node) {
bgneal@312 13692 node = container;
bgneal@312 13693
bgneal@312 13694 while (node[sibling_name] && !isBlock(node[sibling_name])) {
bgneal@312 13695 node = node[sibling_name];
bgneal@312 13696
bgneal@312 13697 // Break on BR but include it will be removed later on
bgneal@312 13698 // we can't remove it now since we need to check if it can be wrapped
bgneal@312 13699 if (isEq(node, 'br'))
bgneal@312 13700 break;
bgneal@312 13701 }
bgneal@312 13702 }
bgneal@312 13703
bgneal@312 13704 return node || container;
bgneal@312 13705 };
bgneal@312 13706
bgneal@312 13707 // Find new startContainer/endContainer if there is better one
bgneal@312 13708 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
bgneal@312 13709 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
bgneal@312 13710
bgneal@312 13711 // Non block element then try to expand up the leaf
bgneal@312 13712 if (format[0].block) {
bgneal@312 13713 if (!isBlock(startContainer))
bgneal@312 13714 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@312 13715
bgneal@312 13716 if (!isBlock(endContainer))
bgneal@312 13717 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@312 13718 }
bgneal@312 13719 }
bgneal@312 13720
bgneal@312 13721 // Setup index for startContainer
bgneal@312 13722 if (startContainer.nodeType == 1) {
bgneal@312 13723 startOffset = nodeIndex(startContainer);
bgneal@312 13724 startContainer = startContainer.parentNode;
bgneal@312 13725 }
bgneal@312 13726
bgneal@312 13727 // Setup index for endContainer
bgneal@312 13728 if (endContainer.nodeType == 1) {
bgneal@312 13729 endOffset = nodeIndex(endContainer) + 1;
bgneal@312 13730 endContainer = endContainer.parentNode;
bgneal@312 13731 }
bgneal@312 13732
bgneal@312 13733 // Return new range like object
bgneal@312 13734 return {
bgneal@312 13735 startContainer : startContainer,
bgneal@312 13736 startOffset : startOffset,
bgneal@312 13737 endContainer : endContainer,
bgneal@312 13738 endOffset : endOffset
bgneal@312 13739 };
bgneal@312 13740 }
bgneal@312 13741
bgneal@312 13742 function removeFormat(format, vars, node, compare_node) {
bgneal@312 13743 var i, attrs, stylesModified;
bgneal@312 13744
bgneal@312 13745 // Check if node matches format
bgneal@312 13746 if (!matchName(node, format))
bgneal@312 13747 return FALSE;
bgneal@312 13748
bgneal@312 13749 // Should we compare with format attribs and styles
bgneal@312 13750 if (format.remove != 'all') {
bgneal@312 13751 // Remove styles
bgneal@312 13752 each(format.styles, function(value, name) {
bgneal@312 13753 value = replaceVars(value, vars);
bgneal@312 13754
bgneal@312 13755 // Indexed array
bgneal@312 13756 if (typeof(name) === 'number') {
bgneal@312 13757 name = value;
bgneal@312 13758 compare_node = 0;
bgneal@312 13759 }
bgneal@312 13760
bgneal@312 13761 if (!compare_node || isEq(getStyle(compare_node, name), value))
bgneal@312 13762 dom.setStyle(node, name, '');
bgneal@312 13763
bgneal@312 13764 stylesModified = 1;
bgneal@312 13765 });
bgneal@312 13766
bgneal@312 13767 // Remove style attribute if it's empty
bgneal@312 13768 if (stylesModified && dom.getAttrib(node, 'style') == '') {
bgneal@312 13769 node.removeAttribute('style');
bgneal@312 13770 node.removeAttribute('_mce_style');
bgneal@312 13771 }
bgneal@312 13772
bgneal@312 13773 // Remove attributes
bgneal@312 13774 each(format.attributes, function(value, name) {
bgneal@312 13775 var valueOut;
bgneal@312 13776
bgneal@312 13777 value = replaceVars(value, vars);
bgneal@312 13778
bgneal@312 13779 // Indexed array
bgneal@312 13780 if (typeof(name) === 'number') {
bgneal@312 13781 name = value;
bgneal@312 13782 compare_node = 0;
bgneal@312 13783 }
bgneal@312 13784
bgneal@312 13785 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
bgneal@312 13786 // Keep internal classes
bgneal@312 13787 if (name == 'class') {
bgneal@312 13788 value = dom.getAttrib(node, name);
bgneal@312 13789 if (value) {
bgneal@312 13790 // Build new class value where everything is removed except the internal prefixed classes
bgneal@312 13791 valueOut = '';
bgneal@312 13792 each(value.split(/\s+/), function(cls) {
bgneal@312 13793 if (/mce\w+/.test(cls))
bgneal@312 13794 valueOut += (valueOut ? ' ' : '') + cls;
bgneal@312 13795 });
bgneal@312 13796
bgneal@312 13797 // We got some internal classes left
bgneal@312 13798 if (valueOut) {
bgneal@312 13799 dom.setAttrib(node, name, valueOut);
bgneal@312 13800 return;
bgneal@312 13801 }
bgneal@312 13802 }
bgneal@312 13803 }
bgneal@312 13804
bgneal@312 13805 // IE6 has a bug where the attribute doesn't get removed correctly
bgneal@312 13806 if (name == "class")
bgneal@312 13807 node.removeAttribute('className');
bgneal@312 13808
bgneal@312 13809 // Remove mce prefixed attributes
bgneal@312 13810 if (MCE_ATTR_RE.test(name))
bgneal@312 13811 node.removeAttribute('_mce_' + name);
bgneal@312 13812
bgneal@312 13813 node.removeAttribute(name);
bgneal@312 13814 }
bgneal@312 13815 });
bgneal@312 13816
bgneal@312 13817 // Remove classes
bgneal@312 13818 each(format.classes, function(value) {
bgneal@312 13819 value = replaceVars(value, vars);
bgneal@312 13820
bgneal@312 13821 if (!compare_node || dom.hasClass(compare_node, value))
bgneal@312 13822 dom.removeClass(node, value);
bgneal@312 13823 });
bgneal@312 13824
bgneal@312 13825 // Check for non internal attributes
bgneal@312 13826 attrs = dom.getAttribs(node);
bgneal@312 13827 for (i = 0; i < attrs.length; i++) {
bgneal@312 13828 if (attrs[i].nodeName.indexOf('_') !== 0)
bgneal@312 13829 return FALSE;
bgneal@312 13830 }
bgneal@312 13831 }
bgneal@312 13832
bgneal@312 13833 // Remove the inline child if it's empty for example <b> or <span>
bgneal@312 13834 if (format.remove != 'none') {
bgneal@312 13835 removeNode(node, format);
bgneal@312 13836 return TRUE;
bgneal@312 13837 }
bgneal@312 13838 };
bgneal@312 13839
bgneal@312 13840 function removeNode(node, format) {
bgneal@312 13841 var parentNode = node.parentNode, rootBlockElm;
bgneal@312 13842
bgneal@312 13843 if (format.block) {
bgneal@312 13844 if (!forcedRootBlock) {
bgneal@312 13845 function find(node, next, inc) {
bgneal@312 13846 node = getNonWhiteSpaceSibling(node, next, inc);
bgneal@312 13847
bgneal@312 13848 return !node || (node.nodeName == 'BR' || isBlock(node));
bgneal@312 13849 };
bgneal@312 13850
bgneal@312 13851 // Append BR elements if needed before we remove the block
bgneal@312 13852 if (isBlock(node) && !isBlock(parentNode)) {
bgneal@312 13853 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
bgneal@312 13854 node.insertBefore(dom.create('br'), node.firstChild);
bgneal@312 13855
bgneal@312 13856 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
bgneal@312 13857 node.appendChild(dom.create('br'));
bgneal@312 13858 }
bgneal@312 13859 } else {
bgneal@312 13860 // Wrap the block in a forcedRootBlock if we are at the root of document
bgneal@312 13861 if (parentNode == dom.getRoot()) {
bgneal@312 13862 if (!format.list_block || !isEq(node, format.list_block)) {
bgneal@312 13863 each(tinymce.grep(node.childNodes), function(node) {
bgneal@312 13864 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
bgneal@312 13865 if (!rootBlockElm)
bgneal@312 13866 rootBlockElm = wrap(node, forcedRootBlock);
bgneal@312 13867 else
bgneal@312 13868 rootBlockElm.appendChild(node);
bgneal@312 13869 } else
bgneal@312 13870 rootBlockElm = 0;
bgneal@312 13871 });
bgneal@312 13872 }
bgneal@312 13873 }
bgneal@312 13874 }
bgneal@312 13875 }
bgneal@312 13876
bgneal@312 13877 // Never remove nodes that isn't the specified inline element if a selector is specified too
bgneal@312 13878 if (format.selector && format.inline && !isEq(format.inline, node))
bgneal@312 13879 return;
bgneal@312 13880
bgneal@312 13881 dom.remove(node, 1);
bgneal@312 13882 };
bgneal@312 13883
bgneal@312 13884 function getNonWhiteSpaceSibling(node, next, inc) {
bgneal@312 13885 if (node) {
bgneal@312 13886 next = next ? 'nextSibling' : 'previousSibling';
bgneal@312 13887
bgneal@312 13888 for (node = inc ? node : node[next]; node; node = node[next]) {
bgneal@312 13889 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
bgneal@312 13890 return node;
bgneal@312 13891 }
bgneal@312 13892 }
bgneal@312 13893 };
bgneal@312 13894
bgneal@312 13895 function isBookmarkNode(node) {
bgneal@312 13896 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
bgneal@312 13897 };
bgneal@312 13898
bgneal@312 13899 function mergeSiblings(prev, next) {
bgneal@312 13900 var marker, sibling, tmpSibling;
bgneal@312 13901
bgneal@312 13902 function compareElements(node1, node2) {
bgneal@312 13903 // Not the same name
bgneal@312 13904 if (node1.nodeName != node2.nodeName)
bgneal@312 13905 return FALSE;
bgneal@312 13906
bgneal@312 13907 function getAttribs(node) {
bgneal@312 13908 var attribs = {};
bgneal@312 13909
bgneal@312 13910 each(dom.getAttribs(node), function(attr) {
bgneal@312 13911 var name = attr.nodeName.toLowerCase();
bgneal@312 13912
bgneal@312 13913 // Don't compare internal attributes or style
bgneal@312 13914 if (name.indexOf('_') !== 0 && name !== 'style')
bgneal@312 13915 attribs[name] = dom.getAttrib(node, name);
bgneal@312 13916 });
bgneal@312 13917
bgneal@312 13918 return attribs;
bgneal@312 13919 };
bgneal@312 13920
bgneal@312 13921 function compareObjects(obj1, obj2) {
bgneal@312 13922 var value, name;
bgneal@312 13923
bgneal@312 13924 for (name in obj1) {
bgneal@312 13925 // Obj1 has item obj2 doesn't have
bgneal@312 13926 if (obj1.hasOwnProperty(name)) {
bgneal@312 13927 value = obj2[name];
bgneal@312 13928
bgneal@312 13929 // Obj2 doesn't have obj1 item
bgneal@312 13930 if (value === undefined)
bgneal@312 13931 return FALSE;
bgneal@312 13932
bgneal@312 13933 // Obj2 item has a different value
bgneal@312 13934 if (obj1[name] != value)
bgneal@312 13935 return FALSE;
bgneal@312 13936
bgneal@312 13937 // Delete similar value
bgneal@312 13938 delete obj2[name];
bgneal@312 13939 }
bgneal@312 13940 }
bgneal@312 13941
bgneal@312 13942 // Check if obj 2 has something obj 1 doesn't have
bgneal@312 13943 for (name in obj2) {
bgneal@312 13944 // Obj2 has item obj1 doesn't have
bgneal@312 13945 if (obj2.hasOwnProperty(name))
bgneal@312 13946 return FALSE;
bgneal@312 13947 }
bgneal@312 13948
bgneal@312 13949 return TRUE;
bgneal@312 13950 };
bgneal@312 13951
bgneal@312 13952 // Attribs are not the same
bgneal@312 13953 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
bgneal@312 13954 return FALSE;
bgneal@312 13955
bgneal@312 13956 // Styles are not the same
bgneal@312 13957 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
bgneal@312 13958 return FALSE;
bgneal@312 13959
bgneal@312 13960 return TRUE;
bgneal@312 13961 };
bgneal@312 13962
bgneal@312 13963 // Check if next/prev exists and that they are elements
bgneal@312 13964 if (prev && next) {
bgneal@312 13965 function findElementSibling(node, sibling_name) {
bgneal@312 13966 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
bgneal@312 13967 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@312 13968 return node;
bgneal@312 13969
bgneal@312 13970 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@312 13971 return sibling;
bgneal@312 13972 }
bgneal@312 13973
bgneal@312 13974 return node;
bgneal@312 13975 };
bgneal@312 13976
bgneal@312 13977 // If previous sibling is empty then jump over it
bgneal@312 13978 prev = findElementSibling(prev, 'previousSibling');
bgneal@312 13979 next = findElementSibling(next, 'nextSibling');
bgneal@312 13980
bgneal@312 13981 // Compare next and previous nodes
bgneal@312 13982 if (compareElements(prev, next)) {
bgneal@312 13983 // Append nodes between
bgneal@312 13984 for (sibling = prev.nextSibling; sibling && sibling != next;) {
bgneal@312 13985 tmpSibling = sibling;
bgneal@312 13986 sibling = sibling.nextSibling;
bgneal@312 13987 prev.appendChild(tmpSibling);
bgneal@312 13988 }
bgneal@312 13989
bgneal@312 13990 // Remove next node
bgneal@312 13991 dom.remove(next);
bgneal@312 13992
bgneal@312 13993 // Move children into prev node
bgneal@312 13994 each(tinymce.grep(next.childNodes), function(node) {
bgneal@312 13995 prev.appendChild(node);
bgneal@312 13996 });
bgneal@312 13997
bgneal@312 13998 return prev;
bgneal@312 13999 }
bgneal@312 14000 }
bgneal@312 14001
bgneal@312 14002 return next;
bgneal@312 14003 };
bgneal@312 14004
bgneal@312 14005 function isTextBlock(name) {
bgneal@312 14006 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
bgneal@312 14007 };
bgneal@312 14008
bgneal@312 14009 function getContainer(rng, start) {
bgneal@312 14010 var container, offset, lastIdx;
bgneal@312 14011
bgneal@312 14012 container = rng[start ? 'startContainer' : 'endContainer'];
bgneal@312 14013 offset = rng[start ? 'startOffset' : 'endOffset'];
bgneal@312 14014
bgneal@312 14015 if (container.nodeType == 1) {
bgneal@312 14016 lastIdx = container.childNodes.length - 1;
bgneal@312 14017
bgneal@312 14018 if (!start && offset)
bgneal@312 14019 offset--;
bgneal@312 14020
bgneal@312 14021 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
bgneal@312 14022 }
bgneal@312 14023
bgneal@312 14024 return container;
bgneal@312 14025 };
bgneal@312 14026
bgneal@312 14027 function performCaretAction(type, name, vars) {
bgneal@312 14028 var i, currentPendingFormats = pendingFormats[type],
bgneal@312 14029 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
bgneal@312 14030
bgneal@312 14031 function hasPending() {
bgneal@312 14032 return pendingFormats.apply.length || pendingFormats.remove.length;
bgneal@312 14033 };
bgneal@312 14034
bgneal@312 14035 function resetPending() {
bgneal@312 14036 pendingFormats.apply = [];
bgneal@312 14037 pendingFormats.remove = [];
bgneal@312 14038 };
bgneal@312 14039
bgneal@312 14040 function perform(caret_node) {
bgneal@312 14041 // Apply pending formats
bgneal@312 14042 each(pendingFormats.apply.reverse(), function(item) {
bgneal@312 14043 apply(item.name, item.vars, caret_node);
bgneal@312 14044 });
bgneal@312 14045
bgneal@312 14046 // Remove pending formats
bgneal@312 14047 each(pendingFormats.remove.reverse(), function(item) {
bgneal@312 14048 remove(item.name, item.vars, caret_node);
bgneal@312 14049 });
bgneal@312 14050
bgneal@312 14051 dom.remove(caret_node, 1);
bgneal@312 14052 resetPending();
bgneal@312 14053 };
bgneal@312 14054
bgneal@312 14055 // Check if it already exists then ignore it
bgneal@312 14056 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
bgneal@312 14057 if (currentPendingFormats[i].name == name)
bgneal@312 14058 return;
bgneal@312 14059 }
bgneal@312 14060
bgneal@312 14061 currentPendingFormats.push({name : name, vars : vars});
bgneal@312 14062
bgneal@312 14063 // Check if it's in the other type, then remove it
bgneal@312 14064 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
bgneal@312 14065 if (otherPendingFormats[i].name == name)
bgneal@312 14066 otherPendingFormats.splice(i, 1);
bgneal@312 14067 }
bgneal@312 14068
bgneal@312 14069 // Pending apply or remove formats
bgneal@312 14070 if (hasPending()) {
bgneal@312 14071 ed.getDoc().execCommand('FontName', false, 'mceinline');
bgneal@312 14072 pendingFormats.lastRng = selection.getRng();
bgneal@312 14073
bgneal@312 14074 // IE will convert the current word
bgneal@312 14075 each(dom.select('font,span'), function(node) {
bgneal@312 14076 var bookmark;
bgneal@312 14077
bgneal@312 14078 if (isCaretNode(node)) {
bgneal@312 14079 bookmark = selection.getBookmark();
bgneal@312 14080 perform(node);
bgneal@312 14081 selection.moveToBookmark(bookmark);
bgneal@312 14082 ed.nodeChanged();
bgneal@312 14083 }
bgneal@312 14084 });
bgneal@312 14085
bgneal@312 14086 // Only register listeners once if we need to
bgneal@312 14087 if (!pendingFormats.isListening && hasPending()) {
bgneal@312 14088 pendingFormats.isListening = true;
bgneal@312 14089
bgneal@312 14090 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
bgneal@312 14091 ed[event].addToTop(function(ed, e) {
bgneal@312 14092 // Do we have pending formats and is the selection moved has moved
bgneal@312 14093 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
bgneal@312 14094 each(dom.select('font,span'), function(node) {
bgneal@312 14095 var textNode, rng;
bgneal@312 14096
bgneal@312 14097 // Look for marker
bgneal@312 14098 if (isCaretNode(node)) {
bgneal@312 14099 textNode = node.firstChild;
bgneal@312 14100
bgneal@312 14101 if (textNode) {
bgneal@312 14102 perform(node);
bgneal@312 14103
bgneal@312 14104 rng = dom.createRng();
bgneal@312 14105 rng.setStart(textNode, textNode.nodeValue.length);
bgneal@312 14106 rng.setEnd(textNode, textNode.nodeValue.length);
bgneal@312 14107 selection.setRng(rng);
bgneal@312 14108 ed.nodeChanged();
bgneal@312 14109 } else
bgneal@312 14110 dom.remove(node);
bgneal@312 14111 }
bgneal@312 14112 });
bgneal@312 14113
bgneal@312 14114 // Always unbind and clear pending styles on keyup
bgneal@312 14115 if (e.type == 'keyup' || e.type == 'mouseup')
bgneal@312 14116 resetPending();
bgneal@312 14117 }
bgneal@312 14118 });
bgneal@312 14119 });
bgneal@312 14120 }
bgneal@312 14121 }
bgneal@312 14122 };
bgneal@312 14123 };
bgneal@312 14124 })(tinymce);
bgneal@312 14125
bgneal@312 14126 tinymce.onAddEditor.add(function(tinymce, ed) {
bgneal@312 14127 var filters, fontSizes, dom, settings = ed.settings;
bgneal@312 14128
bgneal@312 14129 if (settings.inline_styles) {
bgneal@312 14130 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@312 14131
bgneal@312 14132 function replaceWithSpan(node, styles) {
bgneal@312 14133 tinymce.each(styles, function(value, name) {
bgneal@312 14134 if (value)
bgneal@312 14135 dom.setStyle(node, name, value);
bgneal@312 14136 });
bgneal@312 14137
bgneal@312 14138 dom.rename(node, 'span');
bgneal@312 14139 };
bgneal@312 14140
bgneal@312 14141 filters = {
bgneal@312 14142 font : function(dom, node) {
bgneal@312 14143 replaceWithSpan(node, {
bgneal@312 14144 backgroundColor : node.style.backgroundColor,
bgneal@312 14145 color : node.color,
bgneal@312 14146 fontFamily : node.face,
bgneal@312 14147 fontSize : fontSizes[parseInt(node.size) - 1]
bgneal@312 14148 });
bgneal@312 14149 },
bgneal@312 14150
bgneal@312 14151 u : function(dom, node) {
bgneal@312 14152 replaceWithSpan(node, {
bgneal@312 14153 textDecoration : 'underline'
bgneal@312 14154 });
bgneal@312 14155 },
bgneal@312 14156
bgneal@312 14157 strike : function(dom, node) {
bgneal@312 14158 replaceWithSpan(node, {
bgneal@312 14159 textDecoration : 'line-through'
bgneal@312 14160 });
bgneal@312 14161 }
bgneal@312 14162 };
bgneal@312 14163
bgneal@312 14164 function convert(editor, params) {
bgneal@312 14165 dom = editor.dom;
bgneal@312 14166
bgneal@312 14167 if (settings.convert_fonts_to_spans) {
bgneal@312 14168 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
bgneal@312 14169 filters[node.nodeName.toLowerCase()](ed.dom, node);
bgneal@312 14170 });
bgneal@312 14171 }
bgneal@312 14172 };
bgneal@312 14173
bgneal@312 14174 ed.onPreProcess.add(convert);
bgneal@312 14175
bgneal@312 14176 ed.onInit.add(function() {
bgneal@312 14177 ed.selection.onSetContent.add(convert);
bgneal@312 14178 });
bgneal@312 14179 }
bgneal@312 14180 });
bgneal@312 14181