annotate media/js/tiny_mce/tiny_mce_src.js @ 197:2baadae33f2e

Got autocomplete working for the member search. Updated django and ran into a bug where url tags with comma separated kwargs starting consuming tons of CPU throughput. The work-around is to cut over to using spaces between arguments. This is now allowed to be consistent with other tags. Did some query optimization for the news app.
author Brian Neal <bgneal@gmail.com>
date Sat, 10 Apr 2010 04:32:24 +0000
parents 149c3567fec1
children 237710206167
rev   line source
bgneal@183 1 (function(win) {
bgneal@183 2 var whiteSpaceRe = /^\s*|\s*$/g,
bgneal@183 3 undefined;
bgneal@183 4
bgneal@183 5 var tinymce = {
bgneal@183 6 majorVersion : '3',
bgneal@183 7
bgneal@183 8 minorVersion : '3.2',
bgneal@183 9
bgneal@183 10 releaseDate : '2010-03-25',
bgneal@183 11
bgneal@183 12 _init : function() {
bgneal@183 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
bgneal@183 14
bgneal@183 15 t.isOpera = win.opera && opera.buildNumber;
bgneal@183 16
bgneal@183 17 t.isWebKit = /WebKit/.test(ua);
bgneal@183 18
bgneal@183 19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
bgneal@183 20
bgneal@183 21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
bgneal@183 22
bgneal@183 23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
bgneal@183 24
bgneal@183 25 t.isMac = ua.indexOf('Mac') != -1;
bgneal@183 26
bgneal@183 27 t.isAir = /adobeair/i.test(ua);
bgneal@183 28
bgneal@183 29 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
bgneal@183 30 if (win.tinyMCEPreInit) {
bgneal@183 31 t.suffix = tinyMCEPreInit.suffix;
bgneal@183 32 t.baseURL = tinyMCEPreInit.base;
bgneal@183 33 t.query = tinyMCEPreInit.query;
bgneal@45 34 return;
bgneal@183 35 }
bgneal@183 36
bgneal@183 37 // Get suffix and base
bgneal@183 38 t.suffix = '';
bgneal@183 39
bgneal@183 40 // If base element found, add that infront of baseURL
bgneal@183 41 nl = d.getElementsByTagName('base');
bgneal@183 42 for (i=0; i<nl.length; i++) {
bgneal@183 43 if (v = nl[i].href) {
bgneal@183 44 // Host only value like http://site.com or http://site.com:8008
bgneal@183 45 if (/^https?:\/\/[^\/]+$/.test(v))
bgneal@183 46 v += '/';
bgneal@183 47
bgneal@183 48 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
bgneal@183 49 }
bgneal@183 50 }
bgneal@183 51
bgneal@183 52 function getBase(n) {
bgneal@183 53 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
bgneal@183 54 if (/_(src|dev)\.js/g.test(n.src))
bgneal@183 55 t.suffix = '_src';
bgneal@183 56
bgneal@183 57 if ((p = n.src.indexOf('?')) != -1)
bgneal@183 58 t.query = n.src.substring(p + 1);
bgneal@183 59
bgneal@183 60 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
bgneal@183 61
bgneal@183 62 // If path to script is relative and a base href was found add that one infront
bgneal@183 63 // the src property will always be an absolute one on non IE browsers and IE 8
bgneal@183 64 // so this logic will basically only be executed on older IE versions
bgneal@183 65 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
bgneal@183 66 t.baseURL = base + t.baseURL;
bgneal@183 67
bgneal@183 68 return t.baseURL;
bgneal@183 69 }
bgneal@183 70
bgneal@183 71 return null;
bgneal@183 72 };
bgneal@183 73
bgneal@183 74 // Check document
bgneal@183 75 nl = d.getElementsByTagName('script');
bgneal@45 76 for (i=0; i<nl.length; i++) {
bgneal@45 77 if (getBase(nl[i]))
bgneal@45 78 return;
bgneal@45 79 }
bgneal@183 80
bgneal@183 81 // Check head
bgneal@183 82 n = d.getElementsByTagName('head')[0];
bgneal@183 83 if (n) {
bgneal@183 84 nl = n.getElementsByTagName('script');
bgneal@183 85 for (i=0; i<nl.length; i++) {
bgneal@183 86 if (getBase(nl[i]))
bgneal@183 87 return;
bgneal@183 88 }
bgneal@183 89 }
bgneal@183 90
bgneal@183 91 return;
bgneal@183 92 },
bgneal@183 93
bgneal@183 94 is : function(o, t) {
bgneal@183 95 if (!t)
bgneal@183 96 return o !== undefined;
bgneal@183 97
bgneal@183 98 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
bgneal@183 99 return true;
bgneal@183 100
bgneal@183 101 return typeof(o) == t;
bgneal@183 102 },
bgneal@183 103
bgneal@183 104 each : function(o, cb, s) {
bgneal@183 105 var n, l;
bgneal@183 106
bgneal@183 107 if (!o)
bgneal@183 108 return 0;
bgneal@183 109
bgneal@183 110 s = s || o;
bgneal@183 111
bgneal@183 112 if (o.length !== undefined) {
bgneal@183 113 // Indexed arrays, needed for Safari
bgneal@183 114 for (n=0, l = o.length; n < l; n++) {
bgneal@45 115 if (cb.call(s, o[n], n, o) === false)
bgneal@45 116 return 0;
bgneal@45 117 }
bgneal@45 118 } else {
bgneal@183 119 // Hashtables
bgneal@183 120 for (n in o) {
bgneal@183 121 if (o.hasOwnProperty(n)) {
bgneal@183 122 if (cb.call(s, o[n], n, o) === false)
bgneal@183 123 return 0;
bgneal@183 124 }
bgneal@183 125 }
bgneal@183 126 }
bgneal@183 127
bgneal@183 128 return 1;
bgneal@183 129 },
bgneal@183 130
bgneal@183 131
bgneal@183 132 map : function(a, f) {
bgneal@183 133 var o = [];
bgneal@183 134
bgneal@183 135 tinymce.each(a, function(v) {
bgneal@183 136 o.push(f(v));
bgneal@183 137 });
bgneal@183 138
bgneal@183 139 return o;
bgneal@183 140 },
bgneal@183 141
bgneal@183 142 grep : function(a, f) {
bgneal@183 143 var o = [];
bgneal@183 144
bgneal@183 145 tinymce.each(a, function(v) {
bgneal@183 146 if (!f || f(v))
bgneal@183 147 o.push(v);
bgneal@183 148 });
bgneal@183 149
bgneal@183 150 return o;
bgneal@183 151 },
bgneal@183 152
bgneal@183 153 inArray : function(a, v) {
bgneal@183 154 var i, l;
bgneal@183 155
bgneal@183 156 if (a) {
bgneal@183 157 for (i = 0, l = a.length; i < l; i++) {
bgneal@183 158 if (a[i] === v)
bgneal@183 159 return i;
bgneal@183 160 }
bgneal@183 161 }
bgneal@183 162
bgneal@183 163 return -1;
bgneal@183 164 },
bgneal@183 165
bgneal@183 166 extend : function(o, e) {
bgneal@183 167 var i, l, a = arguments;
bgneal@183 168
bgneal@183 169 for (i = 1, l = a.length; i < l; i++) {
bgneal@183 170 e = a[i];
bgneal@183 171
bgneal@183 172 tinymce.each(e, function(v, n) {
bgneal@183 173 if (v !== undefined)
bgneal@183 174 o[n] = v;
bgneal@183 175 });
bgneal@183 176 }
bgneal@183 177
bgneal@183 178 return o;
bgneal@183 179 },
bgneal@183 180
bgneal@183 181
bgneal@183 182 trim : function(s) {
bgneal@183 183 return (s ? '' + s : '').replace(whiteSpaceRe, '');
bgneal@183 184 },
bgneal@183 185
bgneal@183 186 create : function(s, p) {
bgneal@183 187 var t = this, sp, ns, cn, scn, c, de = 0;
bgneal@183 188
bgneal@183 189 // Parse : <prefix> <class>:<super class>
bgneal@183 190 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
bgneal@183 191 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
bgneal@183 192
bgneal@183 193 // Create namespace for new class
bgneal@183 194 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
bgneal@183 195
bgneal@183 196 // Class already exists
bgneal@183 197 if (ns[cn])
bgneal@183 198 return;
bgneal@183 199
bgneal@183 200 // Make pure static class
bgneal@183 201 if (s[2] == 'static') {
bgneal@183 202 ns[cn] = p;
bgneal@183 203
bgneal@183 204 if (this.onCreate)
bgneal@183 205 this.onCreate(s[2], s[3], ns[cn]);
bgneal@183 206
bgneal@183 207 return;
bgneal@183 208 }
bgneal@183 209
bgneal@183 210 // Create default constructor
bgneal@183 211 if (!p[cn]) {
bgneal@183 212 p[cn] = function() {};
bgneal@183 213 de = 1;
bgneal@183 214 }
bgneal@183 215
bgneal@183 216 // Add constructor and methods
bgneal@183 217 ns[cn] = p[cn];
bgneal@183 218 t.extend(ns[cn].prototype, p);
bgneal@183 219
bgneal@183 220 // Extend
bgneal@183 221 if (s[5]) {
bgneal@183 222 sp = t.resolve(s[5]).prototype;
bgneal@183 223 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
bgneal@183 224
bgneal@183 225 // Extend constructor
bgneal@183 226 c = ns[cn];
bgneal@183 227 if (de) {
bgneal@183 228 // Add passthrough constructor
bgneal@183 229 ns[cn] = function() {
bgneal@183 230 return sp[scn].apply(this, arguments);
bgneal@45 231 };
bgneal@45 232 } else {
bgneal@183 233 // Add inherit constructor
bgneal@183 234 ns[cn] = function() {
bgneal@183 235 this.parent = sp[scn];
bgneal@183 236 return c.apply(this, arguments);
bgneal@183 237 };
bgneal@183 238 }
bgneal@183 239 ns[cn].prototype[cn] = ns[cn];
bgneal@183 240
bgneal@183 241 // Add super methods
bgneal@183 242 t.each(sp, function(f, n) {
bgneal@183 243 ns[cn].prototype[n] = sp[n];
bgneal@183 244 });
bgneal@183 245
bgneal@183 246 // Add overridden methods
bgneal@183 247 t.each(p, function(f, n) {
bgneal@183 248 // Extend methods if needed
bgneal@183 249 if (sp[n]) {
bgneal@183 250 ns[cn].prototype[n] = function() {
bgneal@183 251 this.parent = sp[n];
bgneal@183 252 return f.apply(this, arguments);
bgneal@183 253 };
bgneal@183 254 } else {
bgneal@183 255 if (n != cn)
bgneal@183 256 ns[cn].prototype[n] = f;
bgneal@183 257 }
bgneal@183 258 });
bgneal@183 259 }
bgneal@183 260
bgneal@183 261 // Add static methods
bgneal@183 262 t.each(p['static'], function(f, n) {
bgneal@183 263 ns[cn][n] = f;
bgneal@183 264 });
bgneal@183 265
bgneal@183 266 if (this.onCreate)
bgneal@183 267 this.onCreate(s[2], s[3], ns[cn].prototype);
bgneal@183 268 },
bgneal@183 269
bgneal@183 270 walk : function(o, f, n, s) {
bgneal@183 271 s = s || this;
bgneal@183 272
bgneal@183 273 if (o) {
bgneal@183 274 if (n)
bgneal@183 275 o = o[n];
bgneal@183 276
bgneal@183 277 tinymce.each(o, function(o, i) {
bgneal@183 278 if (f.call(s, o, i, n) === false)
bgneal@183 279 return false;
bgneal@183 280
bgneal@183 281 tinymce.walk(o, f, n, s);
bgneal@183 282 });
bgneal@183 283 }
bgneal@183 284 },
bgneal@183 285
bgneal@183 286 createNS : function(n, o) {
bgneal@183 287 var i, v;
bgneal@183 288
bgneal@183 289 o = o || win;
bgneal@183 290
bgneal@183 291 n = n.split('.');
bgneal@183 292 for (i=0; i<n.length; i++) {
bgneal@183 293 v = n[i];
bgneal@183 294
bgneal@183 295 if (!o[v])
bgneal@183 296 o[v] = {};
bgneal@183 297
bgneal@183 298 o = o[v];
bgneal@183 299 }
bgneal@183 300
bgneal@183 301 return o;
bgneal@183 302 },
bgneal@183 303
bgneal@183 304 resolve : function(n, o) {
bgneal@183 305 var i, l;
bgneal@183 306
bgneal@183 307 o = o || win;
bgneal@183 308
bgneal@183 309 n = n.split('.');
bgneal@183 310 for (i = 0, l = n.length; i < l; i++) {
bgneal@183 311 o = o[n[i]];
bgneal@183 312
bgneal@183 313 if (!o)
bgneal@183 314 break;
bgneal@183 315 }
bgneal@183 316
bgneal@183 317 return o;
bgneal@183 318 },
bgneal@183 319
bgneal@183 320 addUnload : function(f, s) {
bgneal@183 321 var t = this;
bgneal@183 322
bgneal@183 323 f = {func : f, scope : s || this};
bgneal@183 324
bgneal@183 325 if (!t.unloads) {
bgneal@183 326 function unload() {
bgneal@183 327 var li = t.unloads, o, n;
bgneal@183 328
bgneal@183 329 if (li) {
bgneal@183 330 // Call unload handlers
bgneal@183 331 for (n in li) {
bgneal@183 332 o = li[n];
bgneal@183 333
bgneal@183 334 if (o && o.func)
bgneal@183 335 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
bgneal@183 336 }
bgneal@183 337
bgneal@183 338 // Detach unload function
bgneal@183 339 if (win.detachEvent) {
bgneal@183 340 win.detachEvent('onbeforeunload', fakeUnload);
bgneal@183 341 win.detachEvent('onunload', unload);
bgneal@183 342 } else if (win.removeEventListener)
bgneal@183 343 win.removeEventListener('unload', unload, false);
bgneal@183 344
bgneal@183 345 // Destroy references
bgneal@183 346 t.unloads = o = li = w = unload = 0;
bgneal@183 347
bgneal@183 348 // Run garbarge collector on IE
bgneal@183 349 if (win.CollectGarbage)
bgneal@183 350 CollectGarbage();
bgneal@183 351 }
bgneal@183 352 };
bgneal@183 353
bgneal@183 354 function fakeUnload() {
bgneal@183 355 var d = document;
bgneal@183 356
bgneal@183 357 // Is there things still loading, then do some magic
bgneal@183 358 if (d.readyState == 'interactive') {
bgneal@183 359 function stop() {
bgneal@183 360 // Prevent memory leak
bgneal@183 361 d.detachEvent('onstop', stop);
bgneal@183 362
bgneal@183 363 // Call unload handler
bgneal@183 364 if (unload)
bgneal@183 365 unload();
bgneal@183 366
bgneal@183 367 d = 0;
bgneal@183 368 };
bgneal@183 369
bgneal@183 370 // Fire unload when the currently loading page is stopped
bgneal@183 371 if (d)
bgneal@183 372 d.attachEvent('onstop', stop);
bgneal@183 373
bgneal@183 374 // Remove onstop listener after a while to prevent the unload function
bgneal@183 375 // to execute if the user presses cancel in an onbeforeunload
bgneal@183 376 // confirm dialog and then presses the browser stop button
bgneal@183 377 win.setTimeout(function() {
bgneal@183 378 if (d)
bgneal@183 379 d.detachEvent('onstop', stop);
bgneal@183 380 }, 0);
bgneal@183 381 }
bgneal@183 382 };
bgneal@183 383
bgneal@183 384 // Attach unload handler
bgneal@183 385 if (win.attachEvent) {
bgneal@183 386 win.attachEvent('onunload', unload);
bgneal@183 387 win.attachEvent('onbeforeunload', fakeUnload);
bgneal@183 388 } else if (win.addEventListener)
bgneal@183 389 win.addEventListener('unload', unload, false);
bgneal@183 390
bgneal@183 391 // Setup initial unload handler array
bgneal@183 392 t.unloads = [f];
bgneal@183 393 } else
bgneal@183 394 t.unloads.push(f);
bgneal@183 395
bgneal@183 396 return f;
bgneal@183 397 },
bgneal@183 398
bgneal@183 399 removeUnload : function(f) {
bgneal@183 400 var u = this.unloads, r = null;
bgneal@183 401
bgneal@183 402 tinymce.each(u, function(o, i) {
bgneal@183 403 if (o && o.func == f) {
bgneal@183 404 u.splice(i, 1);
bgneal@183 405 r = f;
bgneal@183 406 return false;
bgneal@183 407 }
bgneal@183 408 });
bgneal@183 409
bgneal@183 410 return r;
bgneal@183 411 },
bgneal@183 412
bgneal@183 413 explode : function(s, d) {
bgneal@183 414 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
bgneal@183 415 },
bgneal@183 416
bgneal@183 417 _addVer : function(u) {
bgneal@183 418 var v;
bgneal@183 419
bgneal@183 420 if (!this.query)
bgneal@183 421 return u;
bgneal@183 422
bgneal@183 423 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
bgneal@183 424
bgneal@183 425 if (u.indexOf('#') == -1)
bgneal@183 426 return u + v;
bgneal@183 427
bgneal@183 428 return u.replace('#', v + '#');
bgneal@45 429 }
bgneal@45 430
bgneal@183 431 };
bgneal@183 432
bgneal@183 433 // Initialize the API
bgneal@183 434 tinymce._init();
bgneal@183 435
bgneal@183 436 // Expose tinymce namespace to the global namespace (window)
bgneal@183 437 win.tinymce = win.tinyMCE = tinymce;
bgneal@183 438 })(window);
bgneal@183 439
bgneal@183 440
bgneal@45 441 tinymce.create('tinymce.util.Dispatcher', {
bgneal@45 442 scope : null,
bgneal@45 443 listeners : null,
bgneal@45 444
bgneal@45 445 Dispatcher : function(s) {
bgneal@45 446 this.scope = s || this;
bgneal@45 447 this.listeners = [];
bgneal@45 448 },
bgneal@45 449
bgneal@45 450 add : function(cb, s) {
bgneal@45 451 this.listeners.push({cb : cb, scope : s || this.scope});
bgneal@45 452
bgneal@45 453 return cb;
bgneal@45 454 },
bgneal@45 455
bgneal@45 456 addToTop : function(cb, s) {
bgneal@45 457 this.listeners.unshift({cb : cb, scope : s || this.scope});
bgneal@45 458
bgneal@45 459 return cb;
bgneal@45 460 },
bgneal@45 461
bgneal@45 462 remove : function(cb) {
bgneal@45 463 var l = this.listeners, o = null;
bgneal@45 464
bgneal@45 465 tinymce.each(l, function(c, i) {
bgneal@45 466 if (cb == c.cb) {
bgneal@45 467 o = cb;
bgneal@45 468 l.splice(i, 1);
bgneal@45 469 return false;
bgneal@45 470 }
bgneal@45 471 });
bgneal@45 472
bgneal@45 473 return o;
bgneal@45 474 },
bgneal@45 475
bgneal@45 476 dispatch : function() {
bgneal@45 477 var s, a = arguments, i, li = this.listeners, c;
bgneal@45 478
bgneal@45 479 // Needs to be a real loop since the listener count might change while looping
bgneal@45 480 // And this is also more efficient
bgneal@45 481 for (i = 0; i<li.length; i++) {
bgneal@45 482 c = li[i];
bgneal@45 483 s = c.cb.apply(c.scope, a);
bgneal@45 484
bgneal@45 485 if (s === false)
bgneal@45 486 break;
bgneal@45 487 }
bgneal@45 488
bgneal@45 489 return s;
bgneal@45 490 }
bgneal@45 491
bgneal@45 492 });
bgneal@183 493
bgneal@45 494 (function() {
bgneal@45 495 var each = tinymce.each;
bgneal@45 496
bgneal@45 497 tinymce.create('tinymce.util.URI', {
bgneal@45 498 URI : function(u, s) {
bgneal@45 499 var t = this, o, a, b;
bgneal@45 500
bgneal@183 501 // Trim whitespace
bgneal@183 502 u = tinymce.trim(u);
bgneal@183 503
bgneal@45 504 // Default settings
bgneal@45 505 s = t.settings = s || {};
bgneal@45 506
bgneal@45 507 // Strange app protocol or local anchor
bgneal@183 508 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
bgneal@45 509 t.source = u;
bgneal@45 510 return;
bgneal@45 511 }
bgneal@45 512
bgneal@45 513 // Absolute path with no host, fake host and protocol
bgneal@45 514 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
bgneal@45 515 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
bgneal@45 516
bgneal@183 517 // Relative path http:// or protocol relative //path
bgneal@183 518 if (!/^\w*:?\/\//.test(u))
bgneal@45 519 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
bgneal@45 520
bgneal@45 521 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
bgneal@45 522 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
bgneal@45 523 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
bgneal@45 524 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
bgneal@45 525 var s = u[i];
bgneal@45 526
bgneal@45 527 // Zope 3 workaround, they use @@something
bgneal@45 528 if (s)
bgneal@45 529 s = s.replace(/\(mce_at\)/g, '@@');
bgneal@45 530
bgneal@45 531 t[v] = s;
bgneal@45 532 });
bgneal@45 533
bgneal@45 534 if (b = s.base_uri) {
bgneal@45 535 if (!t.protocol)
bgneal@45 536 t.protocol = b.protocol;
bgneal@45 537
bgneal@45 538 if (!t.userInfo)
bgneal@45 539 t.userInfo = b.userInfo;
bgneal@45 540
bgneal@45 541 if (!t.port && t.host == 'mce_host')
bgneal@45 542 t.port = b.port;
bgneal@45 543
bgneal@45 544 if (!t.host || t.host == 'mce_host')
bgneal@45 545 t.host = b.host;
bgneal@45 546
bgneal@45 547 t.source = '';
bgneal@45 548 }
bgneal@45 549
bgneal@45 550 //t.path = t.path || '/';
bgneal@45 551 },
bgneal@45 552
bgneal@45 553 setPath : function(p) {
bgneal@45 554 var t = this;
bgneal@45 555
bgneal@45 556 p = /^(.*?)\/?(\w+)?$/.exec(p);
bgneal@45 557
bgneal@45 558 // Update path parts
bgneal@45 559 t.path = p[0];
bgneal@45 560 t.directory = p[1];
bgneal@45 561 t.file = p[2];
bgneal@45 562
bgneal@45 563 // Rebuild source
bgneal@45 564 t.source = '';
bgneal@45 565 t.getURI();
bgneal@45 566 },
bgneal@45 567
bgneal@45 568 toRelative : function(u) {
bgneal@45 569 var t = this, o;
bgneal@45 570
bgneal@45 571 if (u === "./")
bgneal@45 572 return u;
bgneal@45 573
bgneal@45 574 u = new tinymce.util.URI(u, {base_uri : t});
bgneal@45 575
bgneal@45 576 // Not on same domain/port or protocol
bgneal@45 577 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
bgneal@45 578 return u.getURI();
bgneal@45 579
bgneal@45 580 o = t.toRelPath(t.path, u.path);
bgneal@45 581
bgneal@45 582 // Add query
bgneal@45 583 if (u.query)
bgneal@45 584 o += '?' + u.query;
bgneal@45 585
bgneal@45 586 // Add anchor
bgneal@45 587 if (u.anchor)
bgneal@45 588 o += '#' + u.anchor;
bgneal@45 589
bgneal@45 590 return o;
bgneal@45 591 },
bgneal@45 592
bgneal@45 593 toAbsolute : function(u, nh) {
bgneal@45 594 var u = new tinymce.util.URI(u, {base_uri : this});
bgneal@45 595
bgneal@183 596 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
bgneal@45 597 },
bgneal@45 598
bgneal@45 599 toRelPath : function(base, path) {
bgneal@45 600 var items, bp = 0, out = '', i, l;
bgneal@45 601
bgneal@45 602 // Split the paths
bgneal@45 603 base = base.substring(0, base.lastIndexOf('/'));
bgneal@45 604 base = base.split('/');
bgneal@45 605 items = path.split('/');
bgneal@45 606
bgneal@45 607 if (base.length >= items.length) {
bgneal@45 608 for (i = 0, l = base.length; i < l; i++) {
bgneal@45 609 if (i >= items.length || base[i] != items[i]) {
bgneal@45 610 bp = i + 1;
bgneal@45 611 break;
bgneal@45 612 }
bgneal@45 613 }
bgneal@45 614 }
bgneal@45 615
bgneal@45 616 if (base.length < items.length) {
bgneal@45 617 for (i = 0, l = items.length; i < l; i++) {
bgneal@45 618 if (i >= base.length || base[i] != items[i]) {
bgneal@45 619 bp = i + 1;
bgneal@45 620 break;
bgneal@45 621 }
bgneal@45 622 }
bgneal@45 623 }
bgneal@45 624
bgneal@45 625 if (bp == 1)
bgneal@45 626 return path;
bgneal@45 627
bgneal@45 628 for (i = 0, l = base.length - (bp - 1); i < l; i++)
bgneal@45 629 out += "../";
bgneal@45 630
bgneal@45 631 for (i = bp - 1, l = items.length; i < l; i++) {
bgneal@45 632 if (i != bp - 1)
bgneal@45 633 out += "/" + items[i];
bgneal@45 634 else
bgneal@45 635 out += items[i];
bgneal@45 636 }
bgneal@45 637
bgneal@45 638 return out;
bgneal@45 639 },
bgneal@45 640
bgneal@45 641 toAbsPath : function(base, path) {
bgneal@183 642 var i, nb = 0, o = [], tr, outPath;
bgneal@45 643
bgneal@45 644 // Split paths
bgneal@45 645 tr = /\/$/.test(path) ? '/' : '';
bgneal@45 646 base = base.split('/');
bgneal@45 647 path = path.split('/');
bgneal@45 648
bgneal@45 649 // Remove empty chunks
bgneal@45 650 each(base, function(k) {
bgneal@45 651 if (k)
bgneal@45 652 o.push(k);
bgneal@45 653 });
bgneal@45 654
bgneal@45 655 base = o;
bgneal@45 656
bgneal@45 657 // Merge relURLParts chunks
bgneal@45 658 for (i = path.length - 1, o = []; i >= 0; i--) {
bgneal@45 659 // Ignore empty or .
bgneal@45 660 if (path[i].length == 0 || path[i] == ".")
bgneal@45 661 continue;
bgneal@45 662
bgneal@45 663 // Is parent
bgneal@45 664 if (path[i] == '..') {
bgneal@45 665 nb++;
bgneal@45 666 continue;
bgneal@45 667 }
bgneal@45 668
bgneal@45 669 // Move up
bgneal@45 670 if (nb > 0) {
bgneal@45 671 nb--;
bgneal@45 672 continue;
bgneal@45 673 }
bgneal@45 674
bgneal@45 675 o.push(path[i]);
bgneal@45 676 }
bgneal@45 677
bgneal@45 678 i = base.length - nb;
bgneal@45 679
bgneal@45 680 // If /a/b/c or /
bgneal@45 681 if (i <= 0)
bgneal@183 682 outPath = o.reverse().join('/');
bgneal@183 683 else
bgneal@183 684 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
bgneal@183 685
bgneal@183 686 // Add front / if it's needed
bgneal@183 687 if (outPath.indexOf('/') !== 0)
bgneal@183 688 outPath = '/' + outPath;
bgneal@183 689
bgneal@183 690 // Add traling / if it's needed
bgneal@183 691 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
bgneal@183 692 outPath += tr;
bgneal@183 693
bgneal@183 694 return outPath;
bgneal@45 695 },
bgneal@45 696
bgneal@45 697 getURI : function(nh) {
bgneal@45 698 var s, t = this;
bgneal@45 699
bgneal@45 700 // Rebuild source
bgneal@45 701 if (!t.source || nh) {
bgneal@45 702 s = '';
bgneal@45 703
bgneal@45 704 if (!nh) {
bgneal@45 705 if (t.protocol)
bgneal@45 706 s += t.protocol + '://';
bgneal@45 707
bgneal@45 708 if (t.userInfo)
bgneal@45 709 s += t.userInfo + '@';
bgneal@45 710
bgneal@45 711 if (t.host)
bgneal@45 712 s += t.host;
bgneal@45 713
bgneal@45 714 if (t.port)
bgneal@45 715 s += ':' + t.port;
bgneal@45 716 }
bgneal@45 717
bgneal@45 718 if (t.path)
bgneal@45 719 s += t.path;
bgneal@45 720
bgneal@45 721 if (t.query)
bgneal@45 722 s += '?' + t.query;
bgneal@45 723
bgneal@45 724 if (t.anchor)
bgneal@45 725 s += '#' + t.anchor;
bgneal@45 726
bgneal@45 727 t.source = s;
bgneal@45 728 }
bgneal@45 729
bgneal@45 730 return t.source;
bgneal@45 731 }
bgneal@183 732 });
bgneal@45 733 })();
bgneal@183 734
bgneal@45 735 (function() {
bgneal@45 736 var each = tinymce.each;
bgneal@45 737
bgneal@45 738 tinymce.create('static tinymce.util.Cookie', {
bgneal@45 739 getHash : function(n) {
bgneal@45 740 var v = this.get(n), h;
bgneal@45 741
bgneal@45 742 if (v) {
bgneal@45 743 each(v.split('&'), function(v) {
bgneal@45 744 v = v.split('=');
bgneal@45 745 h = h || {};
bgneal@45 746 h[unescape(v[0])] = unescape(v[1]);
bgneal@45 747 });
bgneal@45 748 }
bgneal@45 749
bgneal@45 750 return h;
bgneal@45 751 },
bgneal@45 752
bgneal@45 753 setHash : function(n, v, e, p, d, s) {
bgneal@45 754 var o = '';
bgneal@45 755
bgneal@45 756 each(v, function(v, k) {
bgneal@45 757 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
bgneal@45 758 });
bgneal@45 759
bgneal@45 760 this.set(n, o, e, p, d, s);
bgneal@45 761 },
bgneal@45 762
bgneal@45 763 get : function(n) {
bgneal@45 764 var c = document.cookie, e, p = n + "=", b;
bgneal@45 765
bgneal@45 766 // Strict mode
bgneal@45 767 if (!c)
bgneal@45 768 return;
bgneal@45 769
bgneal@45 770 b = c.indexOf("; " + p);
bgneal@45 771
bgneal@45 772 if (b == -1) {
bgneal@45 773 b = c.indexOf(p);
bgneal@45 774
bgneal@45 775 if (b != 0)
bgneal@45 776 return null;
bgneal@45 777 } else
bgneal@45 778 b += 2;
bgneal@45 779
bgneal@45 780 e = c.indexOf(";", b);
bgneal@45 781
bgneal@45 782 if (e == -1)
bgneal@45 783 e = c.length;
bgneal@45 784
bgneal@45 785 return unescape(c.substring(b + p.length, e));
bgneal@45 786 },
bgneal@45 787
bgneal@45 788 set : function(n, v, e, p, d, s) {
bgneal@45 789 document.cookie = n + "=" + escape(v) +
bgneal@45 790 ((e) ? "; expires=" + e.toGMTString() : "") +
bgneal@45 791 ((p) ? "; path=" + escape(p) : "") +
bgneal@45 792 ((d) ? "; domain=" + d : "") +
bgneal@45 793 ((s) ? "; secure" : "");
bgneal@45 794 },
bgneal@45 795
bgneal@45 796 remove : function(n, p) {
bgneal@45 797 var d = new Date();
bgneal@45 798
bgneal@45 799 d.setTime(d.getTime() - 1000);
bgneal@45 800
bgneal@45 801 this.set(n, '', d, p, d);
bgneal@45 802 }
bgneal@183 803 });
bgneal@45 804 })();
bgneal@183 805
bgneal@45 806 tinymce.create('static tinymce.util.JSON', {
bgneal@45 807 serialize : function(o) {
bgneal@45 808 var i, v, s = tinymce.util.JSON.serialize, t;
bgneal@45 809
bgneal@45 810 if (o == null)
bgneal@45 811 return 'null';
bgneal@45 812
bgneal@45 813 t = typeof o;
bgneal@45 814
bgneal@45 815 if (t == 'string') {
bgneal@45 816 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
bgneal@45 817
bgneal@45 818 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
bgneal@45 819 i = v.indexOf(b);
bgneal@45 820
bgneal@45 821 if (i + 1)
bgneal@45 822 return '\\' + v.charAt(i + 1);
bgneal@45 823
bgneal@45 824 a = b.charCodeAt().toString(16);
bgneal@45 825
bgneal@45 826 return '\\u' + '0000'.substring(a.length) + a;
bgneal@45 827 }) + '"';
bgneal@45 828 }
bgneal@45 829
bgneal@45 830 if (t == 'object') {
bgneal@45 831 if (o.hasOwnProperty && o instanceof Array) {
bgneal@45 832 for (i=0, v = '['; i<o.length; i++)
bgneal@45 833 v += (i > 0 ? ',' : '') + s(o[i]);
bgneal@45 834
bgneal@45 835 return v + ']';
bgneal@45 836 }
bgneal@45 837
bgneal@45 838 v = '{';
bgneal@45 839
bgneal@45 840 for (i in o)
bgneal@45 841 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
bgneal@45 842
bgneal@45 843 return v + '}';
bgneal@45 844 }
bgneal@45 845
bgneal@45 846 return '' + o;
bgneal@45 847 },
bgneal@45 848
bgneal@45 849 parse : function(s) {
bgneal@45 850 try {
bgneal@45 851 return eval('(' + s + ')');
bgneal@45 852 } catch (ex) {
bgneal@45 853 // Ignore
bgneal@45 854 }
bgneal@45 855 }
bgneal@45 856
bgneal@45 857 });
bgneal@183 858
bgneal@45 859 tinymce.create('static tinymce.util.XHR', {
bgneal@45 860 send : function(o) {
bgneal@45 861 var x, t, w = window, c = 0;
bgneal@45 862
bgneal@45 863 // Default settings
bgneal@45 864 o.scope = o.scope || this;
bgneal@45 865 o.success_scope = o.success_scope || o.scope;
bgneal@45 866 o.error_scope = o.error_scope || o.scope;
bgneal@45 867 o.async = o.async === false ? false : true;
bgneal@45 868 o.data = o.data || '';
bgneal@45 869
bgneal@45 870 function get(s) {
bgneal@45 871 x = 0;
bgneal@45 872
bgneal@45 873 try {
bgneal@45 874 x = new ActiveXObject(s);
bgneal@45 875 } catch (ex) {
bgneal@45 876 }
bgneal@45 877
bgneal@45 878 return x;
bgneal@45 879 };
bgneal@45 880
bgneal@45 881 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
bgneal@45 882
bgneal@45 883 if (x) {
bgneal@45 884 if (x.overrideMimeType)
bgneal@45 885 x.overrideMimeType(o.content_type);
bgneal@45 886
bgneal@45 887 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
bgneal@45 888
bgneal@45 889 if (o.content_type)
bgneal@45 890 x.setRequestHeader('Content-Type', o.content_type);
bgneal@45 891
bgneal@183 892 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bgneal@183 893
bgneal@45 894 x.send(o.data);
bgneal@45 895
bgneal@45 896 function ready() {
bgneal@45 897 if (!o.async || x.readyState == 4 || c++ > 10000) {
bgneal@45 898 if (o.success && c < 10000 && x.status == 200)
bgneal@45 899 o.success.call(o.success_scope, '' + x.responseText, x, o);
bgneal@45 900 else if (o.error)
bgneal@45 901 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
bgneal@45 902
bgneal@45 903 x = null;
bgneal@45 904 } else
bgneal@45 905 w.setTimeout(ready, 10);
bgneal@45 906 };
bgneal@45 907
bgneal@45 908 // Syncronous request
bgneal@45 909 if (!o.async)
bgneal@45 910 return ready();
bgneal@45 911
bgneal@45 912 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
bgneal@45 913 t = w.setTimeout(ready, 10);
bgneal@45 914 }
bgneal@183 915 }
bgneal@45 916 });
bgneal@183 917
bgneal@45 918 (function() {
bgneal@45 919 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
bgneal@45 920
bgneal@45 921 tinymce.create('tinymce.util.JSONRequest', {
bgneal@45 922 JSONRequest : function(s) {
bgneal@45 923 this.settings = extend({
bgneal@45 924 }, s);
bgneal@45 925 this.count = 0;
bgneal@45 926 },
bgneal@45 927
bgneal@45 928 send : function(o) {
bgneal@45 929 var ecb = o.error, scb = o.success;
bgneal@45 930
bgneal@45 931 o = extend(this.settings, o);
bgneal@45 932
bgneal@45 933 o.success = function(c, x) {
bgneal@45 934 c = JSON.parse(c);
bgneal@45 935
bgneal@45 936 if (typeof(c) == 'undefined') {
bgneal@45 937 c = {
bgneal@45 938 error : 'JSON Parse error.'
bgneal@45 939 };
bgneal@45 940 }
bgneal@45 941
bgneal@45 942 if (c.error)
bgneal@45 943 ecb.call(o.error_scope || o.scope, c.error, x);
bgneal@45 944 else
bgneal@45 945 scb.call(o.success_scope || o.scope, c.result);
bgneal@45 946 };
bgneal@45 947
bgneal@45 948 o.error = function(ty, x) {
bgneal@45 949 ecb.call(o.error_scope || o.scope, ty, x);
bgneal@45 950 };
bgneal@45 951
bgneal@45 952 o.data = JSON.serialize({
bgneal@45 953 id : o.id || 'c' + (this.count++),
bgneal@45 954 method : o.method,
bgneal@45 955 params : o.params
bgneal@45 956 });
bgneal@45 957
bgneal@45 958 // JSON content type for Ruby on rails. Bug: #1883287
bgneal@45 959 o.content_type = 'application/json';
bgneal@45 960
bgneal@45 961 XHR.send(o);
bgneal@45 962 },
bgneal@45 963
bgneal@45 964 'static' : {
bgneal@45 965 sendRPC : function(o) {
bgneal@45 966 return new tinymce.util.JSONRequest().send(o);
bgneal@45 967 }
bgneal@45 968 }
bgneal@183 969 });
bgneal@183 970 }());
bgneal@183 971 (function(tinymce) {
bgneal@45 972 // Shorten names
bgneal@183 973 var each = tinymce.each,
bgneal@183 974 is = tinymce.is,
bgneal@183 975 isWebKit = tinymce.isWebKit,
bgneal@183 976 isIE = tinymce.isIE,
bgneal@183 977 blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,
bgneal@183 978 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
bgneal@183 979 mceAttribs = makeMap('src,href,style,coords,shape'),
bgneal@183 980 encodedChars = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'},
bgneal@183 981 encodeCharsRe = /[<>&\"]/g,
bgneal@183 982 simpleSelectorRe = /^([a-z0-9],?)+$/i,
bgneal@183 983 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
bgneal@183 984 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
bgneal@183 985
bgneal@183 986 function makeMap(str) {
bgneal@183 987 var map = {}, i;
bgneal@183 988
bgneal@183 989 str = str.split(',');
bgneal@183 990 for (i = str.length; i >= 0; i--)
bgneal@183 991 map[str[i]] = 1;
bgneal@183 992
bgneal@183 993 return map;
bgneal@183 994 };
bgneal@45 995
bgneal@45 996 tinymce.create('tinymce.dom.DOMUtils', {
bgneal@45 997 doc : null,
bgneal@45 998 root : null,
bgneal@45 999 files : null,
bgneal@45 1000 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
bgneal@45 1001 props : {
bgneal@45 1002 "for" : "htmlFor",
bgneal@45 1003 "class" : "className",
bgneal@45 1004 className : "className",
bgneal@45 1005 checked : "checked",
bgneal@45 1006 disabled : "disabled",
bgneal@45 1007 maxlength : "maxLength",
bgneal@45 1008 readonly : "readOnly",
bgneal@45 1009 selected : "selected",
bgneal@45 1010 value : "value",
bgneal@45 1011 id : "id",
bgneal@45 1012 name : "name",
bgneal@45 1013 type : "type"
bgneal@45 1014 },
bgneal@45 1015
bgneal@45 1016 DOMUtils : function(d, s) {
bgneal@183 1017 var t = this, globalStyle;
bgneal@45 1018
bgneal@45 1019 t.doc = d;
bgneal@45 1020 t.win = window;
bgneal@45 1021 t.files = {};
bgneal@45 1022 t.cssFlicker = false;
bgneal@45 1023 t.counter = 0;
bgneal@45 1024 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
bgneal@45 1025 t.stdMode = d.documentMode === 8;
bgneal@45 1026
bgneal@183 1027 t.settings = s = tinymce.extend({
bgneal@45 1028 keep_values : false,
bgneal@45 1029 hex_colors : 1,
bgneal@45 1030 process_html : 1
bgneal@45 1031 }, s);
bgneal@45 1032
bgneal@45 1033 // Fix IE6SP2 flicker and check it failed for pre SP2
bgneal@45 1034 if (tinymce.isIE6) {
bgneal@45 1035 try {
bgneal@45 1036 d.execCommand('BackgroundImageCache', false, true);
bgneal@45 1037 } catch (e) {
bgneal@45 1038 t.cssFlicker = true;
bgneal@45 1039 }
bgneal@45 1040 }
bgneal@45 1041
bgneal@183 1042 // Build styles list
bgneal@183 1043 if (s.valid_styles) {
bgneal@183 1044 t._styles = {};
bgneal@183 1045
bgneal@183 1046 // Convert styles into a rule list
bgneal@183 1047 each(s.valid_styles, function(value, key) {
bgneal@183 1048 t._styles[key] = tinymce.explode(value);
bgneal@183 1049 });
bgneal@183 1050 }
bgneal@183 1051
bgneal@45 1052 tinymce.addUnload(t.destroy, t);
bgneal@45 1053 },
bgneal@45 1054
bgneal@45 1055 getRoot : function() {
bgneal@45 1056 var t = this, s = t.settings;
bgneal@45 1057
bgneal@45 1058 return (s && t.get(s.root_element)) || t.doc.body;
bgneal@45 1059 },
bgneal@45 1060
bgneal@45 1061 getViewPort : function(w) {
bgneal@45 1062 var d, b;
bgneal@45 1063
bgneal@45 1064 w = !w ? this.win : w;
bgneal@45 1065 d = w.document;
bgneal@45 1066 b = this.boxModel ? d.documentElement : d.body;
bgneal@45 1067
bgneal@45 1068 // Returns viewport size excluding scrollbars
bgneal@45 1069 return {
bgneal@45 1070 x : w.pageXOffset || b.scrollLeft,
bgneal@45 1071 y : w.pageYOffset || b.scrollTop,
bgneal@45 1072 w : w.innerWidth || b.clientWidth,
bgneal@45 1073 h : w.innerHeight || b.clientHeight
bgneal@45 1074 };
bgneal@45 1075 },
bgneal@45 1076
bgneal@45 1077 getRect : function(e) {
bgneal@45 1078 var p, t = this, sr;
bgneal@45 1079
bgneal@45 1080 e = t.get(e);
bgneal@45 1081 p = t.getPos(e);
bgneal@45 1082 sr = t.getSize(e);
bgneal@45 1083
bgneal@45 1084 return {
bgneal@45 1085 x : p.x,
bgneal@45 1086 y : p.y,
bgneal@45 1087 w : sr.w,
bgneal@45 1088 h : sr.h
bgneal@45 1089 };
bgneal@45 1090 },
bgneal@45 1091
bgneal@45 1092 getSize : function(e) {
bgneal@45 1093 var t = this, w, h;
bgneal@45 1094
bgneal@45 1095 e = t.get(e);
bgneal@45 1096 w = t.getStyle(e, 'width');
bgneal@45 1097 h = t.getStyle(e, 'height');
bgneal@45 1098
bgneal@45 1099 // Non pixel value, then force offset/clientWidth
bgneal@45 1100 if (w.indexOf('px') === -1)
bgneal@45 1101 w = 0;
bgneal@45 1102
bgneal@45 1103 // Non pixel value, then force offset/clientWidth
bgneal@45 1104 if (h.indexOf('px') === -1)
bgneal@45 1105 h = 0;
bgneal@45 1106
bgneal@45 1107 return {
bgneal@45 1108 w : parseInt(w) || e.offsetWidth || e.clientWidth,
bgneal@45 1109 h : parseInt(h) || e.offsetHeight || e.clientHeight
bgneal@45 1110 };
bgneal@45 1111 },
bgneal@45 1112
bgneal@45 1113 getParent : function(n, f, r) {
bgneal@45 1114 return this.getParents(n, f, r, false);
bgneal@45 1115 },
bgneal@45 1116
bgneal@45 1117 getParents : function(n, f, r, c) {
bgneal@45 1118 var t = this, na, se = t.settings, o = [];
bgneal@45 1119
bgneal@45 1120 n = t.get(n);
bgneal@45 1121 c = c === undefined;
bgneal@45 1122
bgneal@45 1123 if (se.strict_root)
bgneal@45 1124 r = r || t.getRoot();
bgneal@45 1125
bgneal@45 1126 // Wrap node name as func
bgneal@45 1127 if (is(f, 'string')) {
bgneal@45 1128 na = f;
bgneal@45 1129
bgneal@45 1130 if (f === '*') {
bgneal@45 1131 f = function(n) {return n.nodeType == 1;};
bgneal@45 1132 } else {
bgneal@45 1133 f = function(n) {
bgneal@45 1134 return t.is(n, na);
bgneal@45 1135 };
bgneal@45 1136 }
bgneal@45 1137 }
bgneal@45 1138
bgneal@45 1139 while (n) {
bgneal@183 1140 if (n == r || !n.nodeType || n.nodeType === 9)
bgneal@45 1141 break;
bgneal@45 1142
bgneal@45 1143 if (!f || f(n)) {
bgneal@45 1144 if (c)
bgneal@45 1145 o.push(n);
bgneal@45 1146 else
bgneal@45 1147 return n;
bgneal@45 1148 }
bgneal@45 1149
bgneal@45 1150 n = n.parentNode;
bgneal@45 1151 }
bgneal@45 1152
bgneal@45 1153 return c ? o : null;
bgneal@45 1154 },
bgneal@45 1155
bgneal@45 1156 get : function(e) {
bgneal@45 1157 var n;
bgneal@45 1158
bgneal@45 1159 if (e && this.doc && typeof(e) == 'string') {
bgneal@45 1160 n = e;
bgneal@45 1161 e = this.doc.getElementById(e);
bgneal@45 1162
bgneal@45 1163 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
bgneal@45 1164 if (e && e.id !== n)
bgneal@45 1165 return this.doc.getElementsByName(n)[1];
bgneal@45 1166 }
bgneal@45 1167
bgneal@45 1168 return e;
bgneal@45 1169 },
bgneal@45 1170
bgneal@183 1171 getNext : function(node, selector) {
bgneal@183 1172 return this._findSib(node, selector, 'nextSibling');
bgneal@183 1173 },
bgneal@183 1174
bgneal@183 1175 getPrev : function(node, selector) {
bgneal@183 1176 return this._findSib(node, selector, 'previousSibling');
bgneal@183 1177 },
bgneal@183 1178
bgneal@45 1179
bgneal@45 1180 select : function(pa, s) {
bgneal@45 1181 var t = this;
bgneal@45 1182
bgneal@45 1183 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
bgneal@45 1184 },
bgneal@45 1185
bgneal@183 1186 is : function(n, selector) {
bgneal@183 1187 var i;
bgneal@183 1188
bgneal@183 1189 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
bgneal@183 1190 if (n.length === undefined) {
bgneal@183 1191 // Simple all selector
bgneal@183 1192 if (selector === '*')
bgneal@183 1193 return n.nodeType == 1;
bgneal@183 1194
bgneal@183 1195 // Simple selector just elements
bgneal@183 1196 if (simpleSelectorRe.test(selector)) {
bgneal@183 1197 selector = selector.toLowerCase().split(/,/);
bgneal@183 1198 n = n.nodeName.toLowerCase();
bgneal@183 1199
bgneal@183 1200 for (i = selector.length - 1; i >= 0; i--) {
bgneal@183 1201 if (selector[i] == n)
bgneal@183 1202 return true;
bgneal@183 1203 }
bgneal@183 1204
bgneal@183 1205 return false;
bgneal@183 1206 }
bgneal@183 1207 }
bgneal@183 1208
bgneal@183 1209 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
bgneal@183 1210 },
bgneal@183 1211
bgneal@45 1212
bgneal@45 1213 add : function(p, n, a, h, c) {
bgneal@45 1214 var t = this;
bgneal@45 1215
bgneal@45 1216 return this.run(p, function(p) {
bgneal@45 1217 var e, k;
bgneal@45 1218
bgneal@45 1219 e = is(n, 'string') ? t.doc.createElement(n) : n;
bgneal@45 1220 t.setAttribs(e, a);
bgneal@45 1221
bgneal@45 1222 if (h) {
bgneal@45 1223 if (h.nodeType)
bgneal@45 1224 e.appendChild(h);
bgneal@45 1225 else
bgneal@45 1226 t.setHTML(e, h);
bgneal@45 1227 }
bgneal@45 1228
bgneal@45 1229 return !c ? p.appendChild(e) : e;
bgneal@45 1230 });
bgneal@45 1231 },
bgneal@45 1232
bgneal@45 1233 create : function(n, a, h) {
bgneal@45 1234 return this.add(this.doc.createElement(n), n, a, h, 1);
bgneal@45 1235 },
bgneal@45 1236
bgneal@45 1237 createHTML : function(n, a, h) {
bgneal@45 1238 var o = '', t = this, k;
bgneal@45 1239
bgneal@45 1240 o += '<' + n;
bgneal@45 1241
bgneal@45 1242 for (k in a) {
bgneal@45 1243 if (a.hasOwnProperty(k))
bgneal@45 1244 o += ' ' + k + '="' + t.encode(a[k]) + '"';
bgneal@45 1245 }
bgneal@45 1246
bgneal@45 1247 if (tinymce.is(h))
bgneal@45 1248 return o + '>' + h + '</' + n + '>';
bgneal@45 1249
bgneal@45 1250 return o + ' />';
bgneal@45 1251 },
bgneal@45 1252
bgneal@183 1253 remove : function(node, keep_children) {
bgneal@183 1254 return this.run(node, function(node) {
bgneal@183 1255 var parent, child;
bgneal@183 1256
bgneal@183 1257 parent = node.parentNode;
bgneal@183 1258
bgneal@183 1259 if (!parent)
bgneal@45 1260 return null;
bgneal@45 1261
bgneal@183 1262 if (keep_children) {
bgneal@183 1263 while (child = node.firstChild) {
bgneal@183 1264 // IE 8 will crash if you don't remove completely empty text nodes
bgneal@183 1265 if (child.nodeType !== 3 || child.nodeValue)
bgneal@183 1266 parent.insertBefore(child, node);
bgneal@183 1267 else
bgneal@183 1268 node.removeChild(child);
bgneal@183 1269 }
bgneal@183 1270 }
bgneal@183 1271
bgneal@183 1272 return parent.removeChild(node);
bgneal@183 1273 });
bgneal@183 1274 },
bgneal@45 1275
bgneal@45 1276 setStyle : function(n, na, v) {
bgneal@45 1277 var t = this;
bgneal@45 1278
bgneal@45 1279 return t.run(n, function(e) {
bgneal@45 1280 var s, i;
bgneal@45 1281
bgneal@45 1282 s = e.style;
bgneal@45 1283
bgneal@45 1284 // Camelcase it, if needed
bgneal@45 1285 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 1286 return b.toUpperCase();
bgneal@45 1287 });
bgneal@45 1288
bgneal@45 1289 // Default px suffix on these
bgneal@45 1290 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
bgneal@45 1291 v += 'px';
bgneal@45 1292
bgneal@45 1293 switch (na) {
bgneal@45 1294 case 'opacity':
bgneal@45 1295 // IE specific opacity
bgneal@45 1296 if (isIE) {
bgneal@45 1297 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
bgneal@45 1298
bgneal@45 1299 if (!n.currentStyle || !n.currentStyle.hasLayout)
bgneal@45 1300 s.display = 'inline-block';
bgneal@45 1301 }
bgneal@45 1302
bgneal@45 1303 // Fix for older browsers
bgneal@45 1304 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
bgneal@45 1305 break;
bgneal@45 1306
bgneal@45 1307 case 'float':
bgneal@45 1308 isIE ? s.styleFloat = v : s.cssFloat = v;
bgneal@45 1309 break;
bgneal@45 1310
bgneal@45 1311 default:
bgneal@45 1312 s[na] = v || '';
bgneal@45 1313 }
bgneal@45 1314
bgneal@45 1315 // Force update of the style data
bgneal@45 1316 if (t.settings.update_styles)
bgneal@183 1317 t.setAttrib(e, '_mce_style');
bgneal@45 1318 });
bgneal@45 1319 },
bgneal@45 1320
bgneal@45 1321 getStyle : function(n, na, c) {
bgneal@45 1322 n = this.get(n);
bgneal@45 1323
bgneal@45 1324 if (!n)
bgneal@45 1325 return false;
bgneal@45 1326
bgneal@45 1327 // Gecko
bgneal@45 1328 if (this.doc.defaultView && c) {
bgneal@45 1329 // Remove camelcase
bgneal@45 1330 na = na.replace(/[A-Z]/g, function(a){
bgneal@45 1331 return '-' + a;
bgneal@45 1332 });
bgneal@45 1333
bgneal@45 1334 try {
bgneal@45 1335 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
bgneal@45 1336 } catch (ex) {
bgneal@45 1337 // Old safari might fail
bgneal@45 1338 return null;
bgneal@45 1339 }
bgneal@45 1340 }
bgneal@45 1341
bgneal@45 1342 // Camelcase it, if needed
bgneal@45 1343 na = na.replace(/-(\D)/g, function(a, b){
bgneal@45 1344 return b.toUpperCase();
bgneal@45 1345 });
bgneal@45 1346
bgneal@45 1347 if (na == 'float')
bgneal@45 1348 na = isIE ? 'styleFloat' : 'cssFloat';
bgneal@45 1349
bgneal@45 1350 // IE & Opera
bgneal@45 1351 if (n.currentStyle && c)
bgneal@45 1352 return n.currentStyle[na];
bgneal@45 1353
bgneal@45 1354 return n.style[na];
bgneal@45 1355 },
bgneal@45 1356
bgneal@45 1357 setStyles : function(e, o) {
bgneal@45 1358 var t = this, s = t.settings, ol;
bgneal@45 1359
bgneal@45 1360 ol = s.update_styles;
bgneal@45 1361 s.update_styles = 0;
bgneal@45 1362
bgneal@45 1363 each(o, function(v, n) {
bgneal@45 1364 t.setStyle(e, n, v);
bgneal@45 1365 });
bgneal@45 1366
bgneal@45 1367 // Update style info
bgneal@45 1368 s.update_styles = ol;
bgneal@45 1369 if (s.update_styles)
bgneal@45 1370 t.setAttrib(e, s.cssText);
bgneal@45 1371 },
bgneal@45 1372
bgneal@45 1373 setAttrib : function(e, n, v) {
bgneal@45 1374 var t = this;
bgneal@45 1375
bgneal@45 1376 // Whats the point
bgneal@45 1377 if (!e || !n)
bgneal@45 1378 return;
bgneal@45 1379
bgneal@45 1380 // Strict XML mode
bgneal@45 1381 if (t.settings.strict)
bgneal@45 1382 n = n.toLowerCase();
bgneal@45 1383
bgneal@45 1384 return this.run(e, function(e) {
bgneal@45 1385 var s = t.settings;
bgneal@45 1386
bgneal@45 1387 switch (n) {
bgneal@45 1388 case "style":
bgneal@45 1389 if (!is(v, 'string')) {
bgneal@45 1390 each(v, function(v, n) {
bgneal@45 1391 t.setStyle(e, n, v);
bgneal@45 1392 });
bgneal@45 1393
bgneal@45 1394 return;
bgneal@45 1395 }
bgneal@45 1396
bgneal@45 1397 // No mce_style for elements with these since they might get resized by the user
bgneal@45 1398 if (s.keep_values) {
bgneal@45 1399 if (v && !t._isRes(v))
bgneal@183 1400 e.setAttribute('_mce_style', v, 2);
bgneal@45 1401 else
bgneal@183 1402 e.removeAttribute('_mce_style', 2);
bgneal@45 1403 }
bgneal@45 1404
bgneal@45 1405 e.style.cssText = v;
bgneal@45 1406 break;
bgneal@45 1407
bgneal@45 1408 case "class":
bgneal@45 1409 e.className = v || ''; // Fix IE null bug
bgneal@45 1410 break;
bgneal@45 1411
bgneal@45 1412 case "src":
bgneal@45 1413 case "href":
bgneal@45 1414 if (s.keep_values) {
bgneal@45 1415 if (s.url_converter)
bgneal@45 1416 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
bgneal@45 1417
bgneal@183 1418 t.setAttrib(e, '_mce_' + n, v, 2);
bgneal@45 1419 }
bgneal@45 1420
bgneal@45 1421 break;
bgneal@45 1422
bgneal@45 1423 case "shape":
bgneal@183 1424 e.setAttribute('_mce_style', v);
bgneal@45 1425 break;
bgneal@45 1426 }
bgneal@45 1427
bgneal@45 1428 if (is(v) && v !== null && v.length !== 0)
bgneal@45 1429 e.setAttribute(n, '' + v, 2);
bgneal@45 1430 else
bgneal@45 1431 e.removeAttribute(n, 2);
bgneal@45 1432 });
bgneal@45 1433 },
bgneal@45 1434
bgneal@45 1435 setAttribs : function(e, o) {
bgneal@45 1436 var t = this;
bgneal@45 1437
bgneal@45 1438 return this.run(e, function(e) {
bgneal@45 1439 each(o, function(v, n) {
bgneal@45 1440 t.setAttrib(e, n, v);
bgneal@45 1441 });
bgneal@45 1442 });
bgneal@45 1443 },
bgneal@45 1444
bgneal@45 1445 getAttrib : function(e, n, dv) {
bgneal@45 1446 var v, t = this;
bgneal@45 1447
bgneal@45 1448 e = t.get(e);
bgneal@45 1449
bgneal@45 1450 if (!e || e.nodeType !== 1)
bgneal@45 1451 return false;
bgneal@45 1452
bgneal@45 1453 if (!is(dv))
bgneal@45 1454 dv = '';
bgneal@45 1455
bgneal@45 1456 // Try the mce variant for these
bgneal@45 1457 if (/^(src|href|style|coords|shape)$/.test(n)) {
bgneal@183 1458 v = e.getAttribute("_mce_" + n);
bgneal@45 1459
bgneal@45 1460 if (v)
bgneal@45 1461 return v;
bgneal@45 1462 }
bgneal@45 1463
bgneal@45 1464 if (isIE && t.props[n]) {
bgneal@45 1465 v = e[t.props[n]];
bgneal@45 1466 v = v && v.nodeValue ? v.nodeValue : v;
bgneal@45 1467 }
bgneal@45 1468
bgneal@45 1469 if (!v)
bgneal@45 1470 v = e.getAttribute(n, 2);
bgneal@45 1471
bgneal@183 1472 // Check boolean attribs
bgneal@183 1473 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
bgneal@183 1474 if (e[t.props[n]] === true && v === '')
bgneal@183 1475 return n;
bgneal@183 1476
bgneal@183 1477 return v ? n : '';
bgneal@183 1478 }
bgneal@183 1479
bgneal@183 1480 // Inner input elements will override attributes on form elements
bgneal@183 1481 if (e.nodeName === "FORM" && e.getAttributeNode(n))
bgneal@183 1482 return e.getAttributeNode(n).nodeValue;
bgneal@183 1483
bgneal@45 1484 if (n === 'style') {
bgneal@45 1485 v = v || e.style.cssText;
bgneal@45 1486
bgneal@45 1487 if (v) {
bgneal@183 1488 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
bgneal@45 1489
bgneal@45 1490 if (t.settings.keep_values && !t._isRes(v))
bgneal@183 1491 e.setAttribute('_mce_style', v);
bgneal@45 1492 }
bgneal@45 1493 }
bgneal@45 1494
bgneal@45 1495 // Remove Apple and WebKit stuff
bgneal@45 1496 if (isWebKit && n === "class" && v)
bgneal@45 1497 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
bgneal@45 1498
bgneal@45 1499 // Handle IE issues
bgneal@45 1500 if (isIE) {
bgneal@45 1501 switch (n) {
bgneal@45 1502 case 'rowspan':
bgneal@45 1503 case 'colspan':
bgneal@45 1504 // IE returns 1 as default value
bgneal@45 1505 if (v === 1)
bgneal@45 1506 v = '';
bgneal@45 1507
bgneal@45 1508 break;
bgneal@45 1509
bgneal@45 1510 case 'size':
bgneal@45 1511 // IE returns +0 as default value for size
bgneal@45 1512 if (v === '+0' || v === 20 || v === 0)
bgneal@45 1513 v = '';
bgneal@45 1514
bgneal@45 1515 break;
bgneal@45 1516
bgneal@45 1517 case 'width':
bgneal@45 1518 case 'height':
bgneal@45 1519 case 'vspace':
bgneal@45 1520 case 'checked':
bgneal@45 1521 case 'disabled':
bgneal@45 1522 case 'readonly':
bgneal@45 1523 if (v === 0)
bgneal@45 1524 v = '';
bgneal@45 1525
bgneal@45 1526 break;
bgneal@45 1527
bgneal@45 1528 case 'hspace':
bgneal@45 1529 // IE returns -1 as default value
bgneal@45 1530 if (v === -1)
bgneal@45 1531 v = '';
bgneal@45 1532
bgneal@45 1533 break;
bgneal@45 1534
bgneal@45 1535 case 'maxlength':
bgneal@45 1536 case 'tabindex':
bgneal@45 1537 // IE returns default value
bgneal@45 1538 if (v === 32768 || v === 2147483647 || v === '32768')
bgneal@45 1539 v = '';
bgneal@45 1540
bgneal@45 1541 break;
bgneal@45 1542
bgneal@45 1543 case 'multiple':
bgneal@45 1544 case 'compact':
bgneal@45 1545 case 'noshade':
bgneal@45 1546 case 'nowrap':
bgneal@45 1547 if (v === 65535)
bgneal@45 1548 return n;
bgneal@45 1549
bgneal@45 1550 return dv;
bgneal@45 1551
bgneal@45 1552 case 'shape':
bgneal@45 1553 v = v.toLowerCase();
bgneal@45 1554 break;
bgneal@45 1555
bgneal@45 1556 default:
bgneal@45 1557 // IE has odd anonymous function for event attributes
bgneal@45 1558 if (n.indexOf('on') === 0 && v)
bgneal@183 1559 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
bgneal@45 1560 }
bgneal@45 1561 }
bgneal@45 1562
bgneal@45 1563 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
bgneal@45 1564 },
bgneal@45 1565
bgneal@183 1566 getPos : function(n, ro) {
bgneal@45 1567 var t = this, x = 0, y = 0, e, d = t.doc, r;
bgneal@45 1568
bgneal@45 1569 n = t.get(n);
bgneal@183 1570 ro = ro || d.body;
bgneal@183 1571
bgneal@183 1572 if (n) {
bgneal@183 1573 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
bgneal@183 1574 if (isIE && !t.stdMode) {
bgneal@183 1575 n = n.getBoundingClientRect();
bgneal@183 1576 e = t.boxModel ? d.documentElement : d.body;
bgneal@183 1577 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
bgneal@183 1578 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
bgneal@183 1579 n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
bgneal@183 1580
bgneal@183 1581 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
bgneal@183 1582 }
bgneal@183 1583
bgneal@183 1584 r = n;
bgneal@183 1585 while (r && r != ro && r.nodeType) {
bgneal@183 1586 x += r.offsetLeft || 0;
bgneal@183 1587 y += r.offsetTop || 0;
bgneal@183 1588 r = r.offsetParent;
bgneal@183 1589 }
bgneal@183 1590
bgneal@183 1591 r = n.parentNode;
bgneal@183 1592 while (r && r != ro && r.nodeType) {
bgneal@45 1593 x -= r.scrollLeft || 0;
bgneal@45 1594 y -= r.scrollTop || 0;
bgneal@183 1595 r = r.parentNode;
bgneal@183 1596 }
bgneal@45 1597 }
bgneal@45 1598
bgneal@45 1599 return {x : x, y : y};
bgneal@45 1600 },
bgneal@45 1601
bgneal@45 1602 parseStyle : function(st) {
bgneal@45 1603 var t = this, s = t.settings, o = {};
bgneal@45 1604
bgneal@45 1605 if (!st)
bgneal@45 1606 return o;
bgneal@45 1607
bgneal@45 1608 function compress(p, s, ot) {
bgneal@45 1609 var t, r, b, l;
bgneal@45 1610
bgneal@45 1611 // Get values and check it it needs compressing
bgneal@45 1612 t = o[p + '-top' + s];
bgneal@45 1613 if (!t)
bgneal@45 1614 return;
bgneal@45 1615
bgneal@45 1616 r = o[p + '-right' + s];
bgneal@45 1617 if (t != r)
bgneal@45 1618 return;
bgneal@45 1619
bgneal@45 1620 b = o[p + '-bottom' + s];
bgneal@45 1621 if (r != b)
bgneal@45 1622 return;
bgneal@45 1623
bgneal@45 1624 l = o[p + '-left' + s];
bgneal@45 1625 if (b != l)
bgneal@45 1626 return;
bgneal@45 1627
bgneal@45 1628 // Compress
bgneal@45 1629 o[ot] = l;
bgneal@45 1630 delete o[p + '-top' + s];
bgneal@45 1631 delete o[p + '-right' + s];
bgneal@45 1632 delete o[p + '-bottom' + s];
bgneal@45 1633 delete o[p + '-left' + s];
bgneal@45 1634 };
bgneal@45 1635
bgneal@45 1636 function compress2(ta, a, b, c) {
bgneal@45 1637 var t;
bgneal@45 1638
bgneal@45 1639 t = o[a];
bgneal@45 1640 if (!t)
bgneal@45 1641 return;
bgneal@45 1642
bgneal@45 1643 t = o[b];
bgneal@45 1644 if (!t)
bgneal@45 1645 return;
bgneal@45 1646
bgneal@45 1647 t = o[c];
bgneal@45 1648 if (!t)
bgneal@45 1649 return;
bgneal@45 1650
bgneal@45 1651 // Compress
bgneal@45 1652 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
bgneal@45 1653 delete o[a];
bgneal@45 1654 delete o[b];
bgneal@45 1655 delete o[c];
bgneal@45 1656 };
bgneal@45 1657
bgneal@45 1658 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
bgneal@45 1659
bgneal@45 1660 each(st.split(';'), function(v) {
bgneal@45 1661 var sv, ur = [];
bgneal@45 1662
bgneal@45 1663 if (v) {
bgneal@45 1664 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
bgneal@45 1665 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
bgneal@45 1666 v = v.split(':');
bgneal@45 1667 sv = tinymce.trim(v[1]);
bgneal@45 1668 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
bgneal@45 1669
bgneal@45 1670 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
bgneal@45 1671 return t.toHex(v);
bgneal@45 1672 });
bgneal@45 1673
bgneal@45 1674 if (s.url_converter) {
bgneal@45 1675 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
bgneal@45 1676 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
bgneal@45 1677 });
bgneal@45 1678 }
bgneal@45 1679
bgneal@45 1680 o[tinymce.trim(v[0]).toLowerCase()] = sv;
bgneal@45 1681 }
bgneal@45 1682 });
bgneal@45 1683
bgneal@45 1684 compress("border", "", "border");
bgneal@45 1685 compress("border", "-width", "border-width");
bgneal@45 1686 compress("border", "-color", "border-color");
bgneal@45 1687 compress("border", "-style", "border-style");
bgneal@45 1688 compress("padding", "", "padding");
bgneal@45 1689 compress("margin", "", "margin");
bgneal@45 1690 compress2('border', 'border-width', 'border-style', 'border-color');
bgneal@45 1691
bgneal@45 1692 if (isIE) {
bgneal@45 1693 // Remove pointless border
bgneal@45 1694 if (o.border == 'medium none')
bgneal@45 1695 o.border = '';
bgneal@45 1696 }
bgneal@45 1697
bgneal@45 1698 return o;
bgneal@45 1699 },
bgneal@45 1700
bgneal@183 1701 serializeStyle : function(o, name) {
bgneal@183 1702 var t = this, s = '';
bgneal@183 1703
bgneal@183 1704 function add(v, k) {
bgneal@45 1705 if (k && v) {
bgneal@183 1706 // Remove browser specific styles like -moz- or -webkit-
bgneal@183 1707 if (k.indexOf('-') === 0)
bgneal@45 1708 return;
bgneal@45 1709
bgneal@45 1710 switch (k) {
bgneal@183 1711 case 'font-weight':
bgneal@183 1712 // Opera will output bold as 700
bgneal@183 1713 if (v == 700)
bgneal@183 1714 v = 'bold';
bgneal@183 1715
bgneal@183 1716 break;
bgneal@183 1717
bgneal@45 1718 case 'color':
bgneal@45 1719 case 'background-color':
bgneal@45 1720 v = v.toLowerCase();
bgneal@45 1721 break;
bgneal@45 1722 }
bgneal@45 1723
bgneal@45 1724 s += (s ? ' ' : '') + k + ': ' + v + ';';
bgneal@45 1725 }
bgneal@183 1726 };
bgneal@183 1727
bgneal@183 1728 // Validate style output
bgneal@183 1729 if (name && t._styles) {
bgneal@183 1730 each(t._styles['*'], function(name) {
bgneal@183 1731 add(o[name], name);
bgneal@183 1732 });
bgneal@183 1733
bgneal@183 1734 each(t._styles[name.toLowerCase()], function(name) {
bgneal@183 1735 add(o[name], name);
bgneal@183 1736 });
bgneal@183 1737 } else
bgneal@183 1738 each(o, add);
bgneal@45 1739
bgneal@45 1740 return s;
bgneal@45 1741 },
bgneal@45 1742
bgneal@45 1743 loadCSS : function(u) {
bgneal@183 1744 var t = this, d = t.doc, head;
bgneal@45 1745
bgneal@45 1746 if (!u)
bgneal@45 1747 u = '';
bgneal@45 1748
bgneal@183 1749 head = t.select('head')[0];
bgneal@183 1750
bgneal@45 1751 each(u.split(','), function(u) {
bgneal@183 1752 var link;
bgneal@183 1753
bgneal@45 1754 if (t.files[u])
bgneal@45 1755 return;
bgneal@45 1756
bgneal@45 1757 t.files[u] = true;
bgneal@183 1758 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
bgneal@183 1759
bgneal@183 1760 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
bgneal@183 1761 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
bgneal@183 1762 // It's ugly but it seems to work fine.
bgneal@183 1763 if (isIE && d.documentMode) {
bgneal@183 1764 link.onload = function() {
bgneal@183 1765 d.recalc();
bgneal@183 1766 link.onload = null;
bgneal@183 1767 };
bgneal@183 1768 }
bgneal@183 1769
bgneal@183 1770 head.appendChild(link);
bgneal@183 1771 });
bgneal@183 1772 },
bgneal@45 1773
bgneal@45 1774 addClass : function(e, c) {
bgneal@45 1775 return this.run(e, function(e) {
bgneal@45 1776 var o;
bgneal@45 1777
bgneal@45 1778 if (!c)
bgneal@45 1779 return 0;
bgneal@45 1780
bgneal@45 1781 if (this.hasClass(e, c))
bgneal@45 1782 return e.className;
bgneal@45 1783
bgneal@45 1784 o = this.removeClass(e, c);
bgneal@45 1785
bgneal@45 1786 return e.className = (o != '' ? (o + ' ') : '') + c;
bgneal@45 1787 });
bgneal@45 1788 },
bgneal@45 1789
bgneal@45 1790 removeClass : function(e, c) {
bgneal@45 1791 var t = this, re;
bgneal@45 1792
bgneal@45 1793 return t.run(e, function(e) {
bgneal@45 1794 var v;
bgneal@45 1795
bgneal@45 1796 if (t.hasClass(e, c)) {
bgneal@45 1797 if (!re)
bgneal@45 1798 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
bgneal@45 1799
bgneal@45 1800 v = e.className.replace(re, ' ');
bgneal@183 1801 v = tinymce.trim(v != ' ' ? v : '');
bgneal@183 1802
bgneal@183 1803 e.className = v;
bgneal@183 1804
bgneal@183 1805 // Empty class attr
bgneal@183 1806 if (!v) {
bgneal@183 1807 e.removeAttribute('class');
bgneal@183 1808 e.removeAttribute('className');
bgneal@183 1809 }
bgneal@183 1810
bgneal@183 1811 return v;
bgneal@45 1812 }
bgneal@45 1813
bgneal@45 1814 return e.className;
bgneal@45 1815 });
bgneal@45 1816 },
bgneal@45 1817
bgneal@45 1818 hasClass : function(n, c) {
bgneal@45 1819 n = this.get(n);
bgneal@45 1820
bgneal@45 1821 if (!n || !c)
bgneal@45 1822 return false;
bgneal@45 1823
bgneal@45 1824 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
bgneal@45 1825 },
bgneal@45 1826
bgneal@45 1827 show : function(e) {
bgneal@45 1828 return this.setStyle(e, 'display', 'block');
bgneal@45 1829 },
bgneal@45 1830
bgneal@45 1831 hide : function(e) {
bgneal@45 1832 return this.setStyle(e, 'display', 'none');
bgneal@45 1833 },
bgneal@45 1834
bgneal@45 1835 isHidden : function(e) {
bgneal@45 1836 e = this.get(e);
bgneal@45 1837
bgneal@45 1838 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
bgneal@45 1839 },
bgneal@45 1840
bgneal@45 1841 uniqueId : function(p) {
bgneal@45 1842 return (!p ? 'mce_' : p) + (this.counter++);
bgneal@45 1843 },
bgneal@45 1844
bgneal@45 1845 setHTML : function(e, h) {
bgneal@45 1846 var t = this;
bgneal@45 1847
bgneal@45 1848 return this.run(e, function(e) {
bgneal@45 1849 var x, i, nl, n, p, x;
bgneal@45 1850
bgneal@45 1851 h = t.processHTML(h);
bgneal@45 1852
bgneal@45 1853 if (isIE) {
bgneal@45 1854 function set() {
bgneal@183 1855 // Remove all child nodes
bgneal@183 1856 while (e.firstChild)
bgneal@183 1857 e.firstChild.removeNode();
bgneal@183 1858
bgneal@45 1859 try {
bgneal@45 1860 // IE will remove comments from the beginning
bgneal@45 1861 // unless you padd the contents with something
bgneal@45 1862 e.innerHTML = '<br />' + h;
bgneal@45 1863 e.removeChild(e.firstChild);
bgneal@45 1864 } catch (ex) {
bgneal@45 1865 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
bgneal@45 1866 // This seems to fix this problem
bgneal@45 1867
bgneal@45 1868 // Create new div with HTML contents and a BR infront to keep comments
bgneal@45 1869 x = t.create('div');
bgneal@45 1870 x.innerHTML = '<br />' + h;
bgneal@45 1871
bgneal@45 1872 // Add all children from div to target
bgneal@45 1873 each (x.childNodes, function(n, i) {
bgneal@45 1874 // Skip br element
bgneal@45 1875 if (i)
bgneal@45 1876 e.appendChild(n);
bgneal@45 1877 });
bgneal@45 1878 }
bgneal@45 1879 };
bgneal@45 1880
bgneal@45 1881 // IE has a serious bug when it comes to paragraphs it can produce an invalid
bgneal@45 1882 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
bgneal@45 1883 // It seems to be that IE doesn't like a root block element placed inside another root block element
bgneal@45 1884 if (t.settings.fix_ie_paragraphs)
bgneal@183 1885 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true">&nbsp;</p>');
bgneal@45 1886
bgneal@45 1887 set();
bgneal@45 1888
bgneal@45 1889 if (t.settings.fix_ie_paragraphs) {
bgneal@45 1890 // Check for odd paragraphs this is a sign of a broken DOM
bgneal@45 1891 nl = e.getElementsByTagName("p");
bgneal@45 1892 for (i = nl.length - 1, x = 0; i >= 0; i--) {
bgneal@45 1893 n = nl[i];
bgneal@45 1894
bgneal@45 1895 if (!n.hasChildNodes()) {
bgneal@183 1896 if (!n._mce_keep) {
bgneal@45 1897 x = 1; // Is broken
bgneal@45 1898 break;
bgneal@45 1899 }
bgneal@45 1900
bgneal@183 1901 n.removeAttribute('_mce_keep');
bgneal@45 1902 }
bgneal@45 1903 }
bgneal@45 1904 }
bgneal@45 1905
bgneal@45 1906 // Time to fix the madness IE left us
bgneal@45 1907 if (x) {
bgneal@45 1908 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
bgneal@45 1909 // after we use innerHTML we can fix the DOM tree
bgneal@183 1910 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
bgneal@45 1911 h = h.replace(/<\/p>/g, '</div>');
bgneal@45 1912
bgneal@45 1913 // Set the new HTML with DIVs
bgneal@45 1914 set();
bgneal@45 1915
bgneal@183 1916 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
bgneal@45 1917 // This is needed since IE has a annoying bug see above for details
bgneal@45 1918 // This is a slow process but it has to be done. :(
bgneal@45 1919 if (t.settings.fix_ie_paragraphs) {
bgneal@45 1920 nl = e.getElementsByTagName("DIV");
bgneal@45 1921 for (i = nl.length - 1; i >= 0; i--) {
bgneal@45 1922 n = nl[i];
bgneal@45 1923
bgneal@45 1924 // Is it a temp div
bgneal@183 1925 if (n._mce_tmp) {
bgneal@45 1926 // Create new paragraph
bgneal@45 1927 p = t.doc.createElement('p');
bgneal@45 1928
bgneal@45 1929 // Copy all attributes
bgneal@45 1930 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
bgneal@45 1931 var v;
bgneal@45 1932
bgneal@183 1933 if (b !== '_mce_tmp') {
bgneal@45 1934 v = n.getAttribute(b);
bgneal@45 1935
bgneal@45 1936 if (!v && b === 'class')
bgneal@45 1937 v = n.className;
bgneal@45 1938
bgneal@45 1939 p.setAttribute(b, v);
bgneal@45 1940 }
bgneal@45 1941 });
bgneal@45 1942
bgneal@45 1943 // Append all children to new paragraph
bgneal@45 1944 for (x = 0; x<n.childNodes.length; x++)
bgneal@45 1945 p.appendChild(n.childNodes[x].cloneNode(true));
bgneal@45 1946
bgneal@45 1947 // Replace div with new paragraph
bgneal@45 1948 n.swapNode(p);
bgneal@45 1949 }
bgneal@45 1950 }
bgneal@45 1951 }
bgneal@45 1952 }
bgneal@45 1953 } else
bgneal@45 1954 e.innerHTML = h;
bgneal@45 1955
bgneal@45 1956 return h;
bgneal@45 1957 });
bgneal@45 1958 },
bgneal@45 1959
bgneal@45 1960 processHTML : function(h) {
bgneal@183 1961 var t = this, s = t.settings, codeBlocks = [];
bgneal@45 1962
bgneal@45 1963 if (!s.process_html)
bgneal@45 1964 return h;
bgneal@45 1965
bgneal@183 1966 if (isIE) {
bgneal@45 1967 h = h.replace(/&apos;/g, '&#39;'); // IE can't handle apos
bgneal@45 1968 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
bgneal@45 1969 }
bgneal@45 1970
bgneal@45 1971 // Fix some issues
bgneal@45 1972 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
bgneal@45 1973
bgneal@183 1974 // Store away src and href in _mce_src and mce_href since browsers mess them up
bgneal@45 1975 if (s.keep_values) {
bgneal@45 1976 // Wrap scripts and styles in comments for serialization purposes
bgneal@183 1977 if (/<script|noscript|style/i.test(h)) {
bgneal@45 1978 function trim(s) {
bgneal@45 1979 // Remove prefix and suffix code for element
bgneal@45 1980 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
bgneal@45 1981 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
bgneal@45 1982 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
bgneal@45 1983 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
bgneal@45 1984
bgneal@45 1985 return s;
bgneal@45 1986 };
bgneal@45 1987
bgneal@183 1988 // Wrap the script contents in CDATA and keep them from executing
bgneal@183 1989 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
bgneal@45 1990 // Force type attribute
bgneal@183 1991 if (!attribs)
bgneal@183 1992 attribs = ' type="text/javascript"';
bgneal@183 1993
bgneal@183 1994 // Convert the src attribute of the scripts
bgneal@183 1995 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
bgneal@183 1996 if (s.url_converter)
bgneal@183 1997 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
bgneal@183 1998
bgneal@183 1999 return '_mce_src="' + url + '"';
bgneal@183 2000 });
bgneal@183 2001
bgneal@183 2002 // Wrap text contents
bgneal@183 2003 if (tinymce.trim(text)) {
bgneal@183 2004 codeBlocks.push(trim(text));
bgneal@183 2005 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
bgneal@183 2006 }
bgneal@183 2007
bgneal@183 2008 return '<mce:script' + attribs + '>' + text + '</mce:script>';
bgneal@45 2009 });
bgneal@45 2010
bgneal@183 2011 // Wrap style elements
bgneal@183 2012 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
bgneal@183 2013 // Wrap text contents
bgneal@183 2014 if (text) {
bgneal@183 2015 codeBlocks.push(trim(text));
bgneal@183 2016 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
bgneal@183 2017 }
bgneal@183 2018
bgneal@183 2019 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
bgneal@45 2020 });
bgneal@183 2021
bgneal@183 2022 // Wrap noscript elements
bgneal@183 2023 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@183 2024 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '&#45;&#45;') + '--></mce:noscript>';
bgneal@183 2025 });
bgneal@45 2026 }
bgneal@45 2027
bgneal@45 2028 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
bgneal@45 2029
bgneal@183 2030 // This function processes the attributes in the HTML string to force boolean
bgneal@183 2031 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
bgneal@183 2032 function processTags(html) {
bgneal@183 2033 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
bgneal@183 2034 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
bgneal@183 2035 var mceValue;
bgneal@183 2036
bgneal@183 2037 name = name.toLowerCase();
bgneal@183 2038 value = value || val2 || val3 || "";
bgneal@183 2039
bgneal@183 2040 // Treat boolean attributes
bgneal@183 2041 if (boolAttrs[name]) {
bgneal@183 2042 // false or 0 is treated as a missing attribute
bgneal@183 2043 if (value === 'false' || value === '0')
bgneal@183 2044 return;
bgneal@183 2045
bgneal@183 2046 return name + '="' + name + '"';
bgneal@45 2047 }
bgneal@45 2048
bgneal@183 2049 // Is attribute one that needs special treatment
bgneal@183 2050 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
bgneal@183 2051 mceValue = t.decode(value);
bgneal@183 2052
bgneal@183 2053 // Convert URLs to relative/absolute ones
bgneal@183 2054 if (s.url_converter && (name == "src" || name == "href"))
bgneal@183 2055 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
bgneal@183 2056
bgneal@183 2057 // Process styles lowercases them and compresses them
bgneal@183 2058 if (name == 'style')
bgneal@183 2059 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
bgneal@183 2060
bgneal@183 2061 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
bgneal@45 2062 }
bgneal@183 2063
bgneal@183 2064 return match;
bgneal@183 2065 }) + end + '>';
bgneal@183 2066 });
bgneal@183 2067 };
bgneal@183 2068
bgneal@183 2069 h = processTags(h);
bgneal@183 2070
bgneal@183 2071 // Restore script blocks
bgneal@183 2072 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
bgneal@183 2073 return codeBlocks[idx];
bgneal@45 2074 });
bgneal@45 2075 }
bgneal@45 2076
bgneal@45 2077 return h;
bgneal@45 2078 },
bgneal@45 2079
bgneal@45 2080 getOuterHTML : function(e) {
bgneal@45 2081 var d;
bgneal@45 2082
bgneal@45 2083 e = this.get(e);
bgneal@45 2084
bgneal@45 2085 if (!e)
bgneal@45 2086 return null;
bgneal@45 2087
bgneal@45 2088 if (e.outerHTML !== undefined)
bgneal@45 2089 return e.outerHTML;
bgneal@45 2090
bgneal@45 2091 d = (e.ownerDocument || this.doc).createElement("body");
bgneal@45 2092 d.appendChild(e.cloneNode(true));
bgneal@45 2093
bgneal@45 2094 return d.innerHTML;
bgneal@45 2095 },
bgneal@45 2096
bgneal@45 2097 setOuterHTML : function(e, h, d) {
bgneal@45 2098 var t = this;
bgneal@45 2099
bgneal@183 2100 function setHTML(e, h, d) {
bgneal@183 2101 var n, tp;
bgneal@183 2102
bgneal@183 2103 tp = d.createElement("body");
bgneal@183 2104 tp.innerHTML = h;
bgneal@183 2105
bgneal@183 2106 n = tp.lastChild;
bgneal@183 2107 while (n) {
bgneal@183 2108 t.insertAfter(n.cloneNode(true), e);
bgneal@183 2109 n = n.previousSibling;
bgneal@183 2110 }
bgneal@183 2111
bgneal@183 2112 t.remove(e);
bgneal@183 2113 };
bgneal@183 2114
bgneal@45 2115 return this.run(e, function(e) {
bgneal@45 2116 e = t.get(e);
bgneal@183 2117
bgneal@183 2118 // Only set HTML on elements
bgneal@183 2119 if (e.nodeType == 1) {
bgneal@183 2120 d = d || e.ownerDocument || t.doc;
bgneal@183 2121
bgneal@183 2122 if (isIE) {
bgneal@183 2123 try {
bgneal@183 2124 // Try outerHTML for IE it sometimes produces an unknown runtime error
bgneal@183 2125 if (isIE && e.nodeType == 1)
bgneal@183 2126 e.outerHTML = h;
bgneal@183 2127 else
bgneal@183 2128 setHTML(e, h, d);
bgneal@183 2129 } catch (ex) {
bgneal@183 2130 // Fix for unknown runtime error
bgneal@183 2131 setHTML(e, h, d);
bgneal@183 2132 }
bgneal@183 2133 } else
bgneal@183 2134 setHTML(e, h, d);
bgneal@45 2135 }
bgneal@45 2136 });
bgneal@45 2137 },
bgneal@45 2138
bgneal@45 2139 decode : function(s) {
bgneal@45 2140 var e, n, v;
bgneal@45 2141
bgneal@45 2142 // Look for entities to decode
bgneal@183 2143 if (/&[\w#]+;/.test(s)) {
bgneal@45 2144 // Decode the entities using a div element not super efficient but less code
bgneal@45 2145 e = this.doc.createElement("div");
bgneal@45 2146 e.innerHTML = s;
bgneal@45 2147 n = e.firstChild;
bgneal@45 2148 v = '';
bgneal@45 2149
bgneal@45 2150 if (n) {
bgneal@45 2151 do {
bgneal@45 2152 v += n.nodeValue;
bgneal@183 2153 } while (n = n.nextSibling);
bgneal@45 2154 }
bgneal@45 2155
bgneal@45 2156 return v || s;
bgneal@45 2157 }
bgneal@45 2158
bgneal@45 2159 return s;
bgneal@45 2160 },
bgneal@45 2161
bgneal@183 2162 encode : function(str) {
bgneal@183 2163 return ('' + str).replace(encodeCharsRe, function(chr) {
bgneal@183 2164 return encodedChars[chr];
bgneal@183 2165 });
bgneal@183 2166 },
bgneal@183 2167
bgneal@183 2168 insertAfter : function(node, reference_node) {
bgneal@183 2169 reference_node = this.get(reference_node);
bgneal@183 2170
bgneal@183 2171 return this.run(node, function(node) {
bgneal@183 2172 var parent, nextSibling;
bgneal@183 2173
bgneal@183 2174 parent = reference_node.parentNode;
bgneal@183 2175 nextSibling = reference_node.nextSibling;
bgneal@183 2176
bgneal@183 2177 if (nextSibling)
bgneal@183 2178 parent.insertBefore(node, nextSibling);
bgneal@45 2179 else
bgneal@183 2180 parent.appendChild(node);
bgneal@183 2181
bgneal@183 2182 return node;
bgneal@183 2183 });
bgneal@183 2184 },
bgneal@45 2185
bgneal@45 2186 isBlock : function(n) {
bgneal@45 2187 if (n.nodeType && n.nodeType !== 1)
bgneal@45 2188 return false;
bgneal@45 2189
bgneal@45 2190 n = n.nodeName || n;
bgneal@45 2191
bgneal@183 2192 return blockRe.test(n);
bgneal@183 2193 },
bgneal@45 2194
bgneal@45 2195 replace : function(n, o, k) {
bgneal@45 2196 var t = this;
bgneal@45 2197
bgneal@45 2198 if (is(o, 'array'))
bgneal@45 2199 n = n.cloneNode(true);
bgneal@45 2200
bgneal@45 2201 return t.run(o, function(o) {
bgneal@45 2202 if (k) {
bgneal@183 2203 each(tinymce.grep(o.childNodes), function(c) {
bgneal@183 2204 n.appendChild(c);
bgneal@45 2205 });
bgneal@45 2206 }
bgneal@45 2207
bgneal@45 2208 return o.parentNode.replaceChild(n, o);
bgneal@45 2209 });
bgneal@45 2210 },
bgneal@45 2211
bgneal@183 2212 rename : function(elm, name) {
bgneal@183 2213 var t = this, newElm;
bgneal@183 2214
bgneal@183 2215 if (elm.nodeName != name.toUpperCase()) {
bgneal@183 2216 // Rename block element
bgneal@183 2217 newElm = t.create(name);
bgneal@183 2218
bgneal@183 2219 // Copy attribs to new block
bgneal@183 2220 each(t.getAttribs(elm), function(attr_node) {
bgneal@183 2221 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
bgneal@183 2222 });
bgneal@183 2223
bgneal@183 2224 // Replace block
bgneal@183 2225 t.replace(newElm, elm, 1);
bgneal@183 2226 }
bgneal@183 2227
bgneal@183 2228 return newElm || elm;
bgneal@183 2229 },
bgneal@45 2230
bgneal@45 2231 findCommonAncestor : function(a, b) {
bgneal@45 2232 var ps = a, pe;
bgneal@45 2233
bgneal@45 2234 while (ps) {
bgneal@45 2235 pe = b;
bgneal@45 2236
bgneal@45 2237 while (pe && ps != pe)
bgneal@45 2238 pe = pe.parentNode;
bgneal@45 2239
bgneal@45 2240 if (ps == pe)
bgneal@45 2241 break;
bgneal@45 2242
bgneal@45 2243 ps = ps.parentNode;
bgneal@45 2244 }
bgneal@45 2245
bgneal@45 2246 if (!ps && a.ownerDocument)
bgneal@45 2247 return a.ownerDocument.documentElement;
bgneal@45 2248
bgneal@45 2249 return ps;
bgneal@45 2250 },
bgneal@45 2251
bgneal@45 2252 toHex : function(s) {
bgneal@45 2253 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
bgneal@45 2254
bgneal@45 2255 function hex(s) {
bgneal@45 2256 s = parseInt(s).toString(16);
bgneal@45 2257
bgneal@45 2258 return s.length > 1 ? s : '0' + s; // 0 -> 00
bgneal@45 2259 };
bgneal@45 2260
bgneal@45 2261 if (c) {
bgneal@45 2262 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
bgneal@45 2263
bgneal@45 2264 return s;
bgneal@45 2265 }
bgneal@45 2266
bgneal@45 2267 return s;
bgneal@45 2268 },
bgneal@45 2269
bgneal@45 2270 getClasses : function() {
bgneal@45 2271 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
bgneal@45 2272
bgneal@45 2273 if (t.classes)
bgneal@45 2274 return t.classes;
bgneal@45 2275
bgneal@45 2276 function addClasses(s) {
bgneal@45 2277 // IE style imports
bgneal@45 2278 each(s.imports, function(r) {
bgneal@45 2279 addClasses(r);
bgneal@45 2280 });
bgneal@45 2281
bgneal@45 2282 each(s.cssRules || s.rules, function(r) {
bgneal@45 2283 // Real type or fake it on IE
bgneal@45 2284 switch (r.type || 1) {
bgneal@45 2285 // Rule
bgneal@45 2286 case 1:
bgneal@45 2287 if (r.selectorText) {
bgneal@45 2288 each(r.selectorText.split(','), function(v) {
bgneal@45 2289 v = v.replace(/^\s*|\s*$|^\s\./g, "");
bgneal@45 2290
bgneal@45 2291 // Is internal or it doesn't contain a class
bgneal@45 2292 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
bgneal@45 2293 return;
bgneal@45 2294
bgneal@45 2295 // Remove everything but class name
bgneal@45 2296 ov = v;
bgneal@45 2297 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
bgneal@45 2298
bgneal@45 2299 // Filter classes
bgneal@45 2300 if (f && !(v = f(v, ov)))
bgneal@45 2301 return;
bgneal@45 2302
bgneal@45 2303 if (!lo[v]) {
bgneal@45 2304 cl.push({'class' : v});
bgneal@45 2305 lo[v] = 1;
bgneal@45 2306 }
bgneal@45 2307 });
bgneal@45 2308 }
bgneal@45 2309 break;
bgneal@45 2310
bgneal@45 2311 // Import
bgneal@45 2312 case 3:
bgneal@45 2313 addClasses(r.styleSheet);
bgneal@45 2314 break;
bgneal@45 2315 }
bgneal@45 2316 });
bgneal@45 2317 };
bgneal@45 2318
bgneal@45 2319 try {
bgneal@45 2320 each(t.doc.styleSheets, addClasses);
bgneal@45 2321 } catch (ex) {
bgneal@45 2322 // Ignore
bgneal@45 2323 }
bgneal@45 2324
bgneal@45 2325 if (cl.length > 0)
bgneal@45 2326 t.classes = cl;
bgneal@45 2327
bgneal@45 2328 return cl;
bgneal@45 2329 },
bgneal@45 2330
bgneal@45 2331 run : function(e, f, s) {
bgneal@45 2332 var t = this, o;
bgneal@45 2333
bgneal@45 2334 if (t.doc && typeof(e) === 'string')
bgneal@45 2335 e = t.get(e);
bgneal@45 2336
bgneal@45 2337 if (!e)
bgneal@45 2338 return false;
bgneal@45 2339
bgneal@45 2340 s = s || this;
bgneal@45 2341 if (!e.nodeType && (e.length || e.length === 0)) {
bgneal@45 2342 o = [];
bgneal@45 2343
bgneal@45 2344 each(e, function(e, i) {
bgneal@45 2345 if (e) {
bgneal@45 2346 if (typeof(e) == 'string')
bgneal@45 2347 e = t.doc.getElementById(e);
bgneal@45 2348
bgneal@45 2349 o.push(f.call(s, e, i));
bgneal@45 2350 }
bgneal@45 2351 });
bgneal@45 2352
bgneal@45 2353 return o;
bgneal@45 2354 }
bgneal@45 2355
bgneal@45 2356 return f.call(s, e);
bgneal@45 2357 },
bgneal@45 2358
bgneal@45 2359 getAttribs : function(n) {
bgneal@45 2360 var o;
bgneal@45 2361
bgneal@45 2362 n = this.get(n);
bgneal@45 2363
bgneal@45 2364 if (!n)
bgneal@45 2365 return [];
bgneal@45 2366
bgneal@45 2367 if (isIE) {
bgneal@45 2368 o = [];
bgneal@45 2369
bgneal@45 2370 // Object will throw exception in IE
bgneal@45 2371 if (n.nodeName == 'OBJECT')
bgneal@45 2372 return n.attributes;
bgneal@45 2373
bgneal@183 2374 // IE doesn't keep the selected attribute if you clone option elements
bgneal@183 2375 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
bgneal@183 2376 o.push({specified : 1, nodeName : 'selected'});
bgneal@183 2377
bgneal@45 2378 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
bgneal@183 2379 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
bgneal@183 2380 o.push({specified : 1, nodeName : a});
bgneal@45 2381 });
bgneal@45 2382
bgneal@45 2383 return o;
bgneal@45 2384 }
bgneal@45 2385
bgneal@45 2386 return n.attributes;
bgneal@45 2387 },
bgneal@45 2388
bgneal@45 2389 destroy : function(s) {
bgneal@45 2390 var t = this;
bgneal@45 2391
bgneal@183 2392 if (t.events)
bgneal@183 2393 t.events.destroy();
bgneal@183 2394
bgneal@183 2395 t.win = t.doc = t.root = t.events = null;
bgneal@45 2396
bgneal@45 2397 // Manual destroy then remove unload handler
bgneal@45 2398 if (!s)
bgneal@45 2399 tinymce.removeUnload(t.destroy);
bgneal@45 2400 },
bgneal@45 2401
bgneal@45 2402 createRng : function() {
bgneal@45 2403 var d = this.doc;
bgneal@45 2404
bgneal@45 2405 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
bgneal@45 2406 },
bgneal@45 2407
bgneal@183 2408 nodeIndex : function(node, normalized) {
bgneal@183 2409 var idx = 0, lastNodeType, lastNode, nodeType;
bgneal@183 2410
bgneal@183 2411 if (node) {
bgneal@183 2412 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
bgneal@183 2413 nodeType = node.nodeType;
bgneal@183 2414
bgneal@183 2415 // Handle normalization of text nodes
bgneal@183 2416 if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length))
bgneal@183 2417 idx++;
bgneal@183 2418
bgneal@183 2419 lastNodeType = nodeType;
bgneal@183 2420 }
bgneal@183 2421 }
bgneal@183 2422
bgneal@183 2423 return idx;
bgneal@183 2424 },
bgneal@183 2425
bgneal@45 2426 split : function(pe, e, re) {
bgneal@45 2427 var t = this, r = t.createRng(), bef, aft, pa;
bgneal@45 2428
bgneal@183 2429 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
bgneal@183 2430 // but we don't want that in our code since it serves no purpose for the end user
bgneal@45 2431 // For example if this is chopped:
bgneal@45 2432 // <p>text 1<span><b>CHOP</b></span>text 2</p>
bgneal@45 2433 // would produce:
bgneal@45 2434 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
bgneal@45 2435 // this function will then trim of empty edges and produce:
bgneal@45 2436 // <p>text 1</p><b>CHOP</b><p>text 2</p>
bgneal@183 2437 function trim(node) {
bgneal@183 2438 var i, children = node.childNodes;
bgneal@183 2439
bgneal@183 2440 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
bgneal@183 2441 return;
bgneal@183 2442
bgneal@183 2443 for (i = children.length - 1; i >= 0; i--)
bgneal@183 2444 trim(children[i]);
bgneal@183 2445
bgneal@183 2446 if (node.nodeType != 9) {
bgneal@183 2447 // Keep non whitespace text nodes
bgneal@183 2448 if (node.nodeType == 3 && node.nodeValue.length > 0)
bgneal@183 2449 return;
bgneal@183 2450
bgneal@183 2451 if (node.nodeType == 1) {
bgneal@183 2452 // If the only child is a bookmark then move it up
bgneal@183 2453 children = node.childNodes;
bgneal@183 2454 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
bgneal@183 2455 node.parentNode.insertBefore(children[0], node);
bgneal@183 2456
bgneal@183 2457 // Keep non empty elements or img, hr etc
bgneal@183 2458 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
bgneal@183 2459 return;
bgneal@183 2460 }
bgneal@183 2461
bgneal@183 2462 t.remove(node);
bgneal@183 2463 }
bgneal@183 2464
bgneal@183 2465 return node;
bgneal@45 2466 };
bgneal@45 2467
bgneal@45 2468 if (pe && e) {
bgneal@45 2469 // Get before chunk
bgneal@183 2470 r.setStart(pe.parentNode, t.nodeIndex(pe));
bgneal@183 2471 r.setEnd(e.parentNode, t.nodeIndex(e));
bgneal@45 2472 bef = r.extractContents();
bgneal@45 2473
bgneal@45 2474 // Get after chunk
bgneal@45 2475 r = t.createRng();
bgneal@183 2476 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
bgneal@183 2477 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
bgneal@45 2478 aft = r.extractContents();
bgneal@45 2479
bgneal@183 2480 // Insert before chunk
bgneal@45 2481 pa = pe.parentNode;
bgneal@183 2482 pa.insertBefore(trim(bef), pe);
bgneal@183 2483
bgneal@183 2484 // Insert middle chunk
bgneal@45 2485 if (re)
bgneal@45 2486 pa.replaceChild(re, e);
bgneal@45 2487 else
bgneal@45 2488 pa.insertBefore(e, pe);
bgneal@45 2489
bgneal@183 2490 // Insert after chunk
bgneal@183 2491 pa.insertBefore(trim(aft), pe);
bgneal@45 2492 t.remove(pe);
bgneal@45 2493
bgneal@45 2494 return re || e;
bgneal@45 2495 }
bgneal@45 2496 },
bgneal@45 2497
bgneal@183 2498 bind : function(target, name, func, scope) {
bgneal@183 2499 var t = this;
bgneal@183 2500
bgneal@183 2501 if (!t.events)
bgneal@183 2502 t.events = new tinymce.dom.EventUtils();
bgneal@183 2503
bgneal@183 2504 return t.events.add(target, name, func, scope || this);
bgneal@183 2505 },
bgneal@183 2506
bgneal@183 2507 unbind : function(target, name, func) {
bgneal@183 2508 var t = this;
bgneal@183 2509
bgneal@183 2510 if (!t.events)
bgneal@183 2511 t.events = new tinymce.dom.EventUtils();
bgneal@183 2512
bgneal@183 2513 return t.events.remove(target, name, func);
bgneal@183 2514 },
bgneal@183 2515
bgneal@183 2516
bgneal@183 2517 _findSib : function(node, selector, name) {
bgneal@183 2518 var t = this, f = selector;
bgneal@183 2519
bgneal@183 2520 if (node) {
bgneal@183 2521 // If expression make a function of it using is
bgneal@183 2522 if (is(f, 'string')) {
bgneal@183 2523 f = function(node) {
bgneal@183 2524 return t.is(node, selector);
bgneal@183 2525 };
bgneal@183 2526 }
bgneal@183 2527
bgneal@183 2528 // Loop all siblings
bgneal@183 2529 for (node = node[name]; node; node = node[name]) {
bgneal@183 2530 if (f(node))
bgneal@183 2531 return node;
bgneal@183 2532 }
bgneal@183 2533 }
bgneal@183 2534
bgneal@183 2535 return null;
bgneal@183 2536 },
bgneal@183 2537
bgneal@45 2538 _isRes : function(c) {
bgneal@45 2539 // Is live resizble element
bgneal@45 2540 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
bgneal@45 2541 }
bgneal@45 2542
bgneal@45 2543 /*
bgneal@45 2544 walk : function(n, f, s) {
bgneal@45 2545 var d = this.doc, w;
bgneal@45 2546
bgneal@45 2547 if (d.createTreeWalker) {
bgneal@45 2548 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
bgneal@45 2549
bgneal@45 2550 while ((n = w.nextNode()) != null)
bgneal@45 2551 f.call(s || this, n);
bgneal@45 2552 } else
bgneal@45 2553 tinymce.walk(n, f, 'childNodes', s);
bgneal@45 2554 }
bgneal@45 2555 */
bgneal@45 2556
bgneal@45 2557 /*
bgneal@45 2558 toRGB : function(s) {
bgneal@45 2559 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
bgneal@45 2560
bgneal@45 2561 if (c) {
bgneal@45 2562 // #FFF -> #FFFFFF
bgneal@45 2563 if (!is(c[3]))
bgneal@45 2564 c[3] = c[2] = c[1];
bgneal@45 2565
bgneal@45 2566 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
bgneal@45 2567 }
bgneal@45 2568
bgneal@45 2569 return s;
bgneal@45 2570 }
bgneal@45 2571 */
bgneal@183 2572 });
bgneal@183 2573
bgneal@45 2574 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
bgneal@45 2575 })(tinymce);
bgneal@183 2576
bgneal@45 2577 (function(ns) {
bgneal@45 2578 // Range constructor
bgneal@45 2579 function Range(dom) {
bgneal@183 2580 var t = this,
bgneal@183 2581 doc = dom.doc,
bgneal@183 2582 EXTRACT = 0,
bgneal@183 2583 CLONE = 1,
bgneal@183 2584 DELETE = 2,
bgneal@183 2585 TRUE = true,
bgneal@183 2586 FALSE = false,
bgneal@183 2587 START_OFFSET = 'startOffset',
bgneal@183 2588 START_CONTAINER = 'startContainer',
bgneal@183 2589 END_CONTAINER = 'endContainer',
bgneal@183 2590 END_OFFSET = 'endOffset',
bgneal@183 2591 extend = tinymce.extend,
bgneal@183 2592 nodeIndex = dom.nodeIndex;
bgneal@183 2593
bgneal@183 2594 extend(t, {
bgneal@45 2595 // Inital states
bgneal@183 2596 startContainer : doc,
bgneal@45 2597 startOffset : 0,
bgneal@183 2598 endContainer : doc,
bgneal@45 2599 endOffset : 0,
bgneal@183 2600 collapsed : TRUE,
bgneal@183 2601 commonAncestorContainer : doc,
bgneal@45 2602
bgneal@45 2603 // Range constants
bgneal@45 2604 START_TO_START : 0,
bgneal@45 2605 START_TO_END : 1,
bgneal@45 2606 END_TO_END : 2,
bgneal@183 2607 END_TO_START : 3,
bgneal@183 2608
bgneal@183 2609 // Public methods
bgneal@183 2610 setStart : setStart,
bgneal@183 2611 setEnd : setEnd,
bgneal@183 2612 setStartBefore : setStartBefore,
bgneal@183 2613 setStartAfter : setStartAfter,
bgneal@183 2614 setEndBefore : setEndBefore,
bgneal@183 2615 setEndAfter : setEndAfter,
bgneal@183 2616 collapse : collapse,
bgneal@183 2617 selectNode : selectNode,
bgneal@183 2618 selectNodeContents : selectNodeContents,
bgneal@183 2619 compareBoundaryPoints : compareBoundaryPoints,
bgneal@183 2620 deleteContents : deleteContents,
bgneal@183 2621 extractContents : extractContents,
bgneal@183 2622 cloneContents : cloneContents,
bgneal@183 2623 insertNode : insertNode,
bgneal@183 2624 surroundContents : surroundContents,
bgneal@183 2625 cloneRange : cloneRange
bgneal@45 2626 });
bgneal@183 2627
bgneal@183 2628 function setStart(n, o) {
bgneal@183 2629 _setEndPoint(TRUE, n, o);
bgneal@183 2630 };
bgneal@183 2631
bgneal@183 2632 function setEnd(n, o) {
bgneal@183 2633 _setEndPoint(FALSE, n, o);
bgneal@183 2634 };
bgneal@183 2635
bgneal@183 2636 function setStartBefore(n) {
bgneal@183 2637 setStart(n.parentNode, nodeIndex(n));
bgneal@183 2638 };
bgneal@183 2639
bgneal@183 2640 function setStartAfter(n) {
bgneal@183 2641 setStart(n.parentNode, nodeIndex(n) + 1);
bgneal@183 2642 };
bgneal@183 2643
bgneal@183 2644 function setEndBefore(n) {
bgneal@183 2645 setEnd(n.parentNode, nodeIndex(n));
bgneal@183 2646 };
bgneal@183 2647
bgneal@183 2648 function setEndAfter(n) {
bgneal@183 2649 setEnd(n.parentNode, nodeIndex(n) + 1);
bgneal@183 2650 };
bgneal@183 2651
bgneal@183 2652 function collapse(ts) {
bgneal@45 2653 if (ts) {
bgneal@183 2654 t[END_CONTAINER] = t[START_CONTAINER];
bgneal@183 2655 t[END_OFFSET] = t[START_OFFSET];
bgneal@45 2656 } else {
bgneal@183 2657 t[START_CONTAINER] = t[END_CONTAINER];
bgneal@183 2658 t[START_OFFSET] = t[END_OFFSET];
bgneal@183 2659 }
bgneal@183 2660
bgneal@183 2661 t.collapsed = TRUE;
bgneal@183 2662 };
bgneal@183 2663
bgneal@183 2664 function selectNode(n) {
bgneal@183 2665 setStartBefore(n);
bgneal@183 2666 setEndAfter(n);
bgneal@183 2667 };
bgneal@183 2668
bgneal@183 2669 function selectNodeContents(n) {
bgneal@183 2670 setStart(n, 0);
bgneal@183 2671 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
bgneal@183 2672 };
bgneal@183 2673
bgneal@183 2674 function compareBoundaryPoints(h, r) {
bgneal@183 2675 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
bgneal@45 2676
bgneal@45 2677 // Check START_TO_START
bgneal@45 2678 if (h === 0)
bgneal@183 2679 return _compareBoundaryPoints(sc, so, sc, so);
bgneal@45 2680
bgneal@45 2681 // Check START_TO_END
bgneal@45 2682 if (h === 1)
bgneal@183 2683 return _compareBoundaryPoints(sc, so, ec, eo);
bgneal@45 2684
bgneal@45 2685 // Check END_TO_END
bgneal@45 2686 if (h === 2)
bgneal@183 2687 return _compareBoundaryPoints(ec, eo, ec, eo);
bgneal@45 2688
bgneal@45 2689 // Check END_TO_START
bgneal@45 2690 if (h === 3)
bgneal@183 2691 return _compareBoundaryPoints(ec, eo, sc, so);
bgneal@183 2692 };
bgneal@183 2693
bgneal@183 2694 function deleteContents() {
bgneal@183 2695 _traverse(DELETE);
bgneal@183 2696 };
bgneal@183 2697
bgneal@183 2698 function extractContents() {
bgneal@183 2699 return _traverse(EXTRACT);
bgneal@183 2700 };
bgneal@183 2701
bgneal@183 2702 function cloneContents() {
bgneal@183 2703 return _traverse(CLONE);
bgneal@183 2704 };
bgneal@183 2705
bgneal@183 2706 function insertNode(n) {
bgneal@183 2707 var startContainer = this[START_CONTAINER],
bgneal@183 2708 startOffset = this[START_OFFSET], nn, o;
bgneal@45 2709
bgneal@45 2710 // Node is TEXT_NODE or CDATA
bgneal@183 2711 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
bgneal@183 2712 if (!startOffset) {
bgneal@183 2713 // At the start of text
bgneal@183 2714 startContainer.parentNode.insertBefore(n, startContainer);
bgneal@183 2715 } else if (startOffset >= startContainer.nodeValue.length) {
bgneal@183 2716 // At the end of text
bgneal@183 2717 dom.insertAfter(n, startContainer);
bgneal@183 2718 } else {
bgneal@183 2719 // Middle, need to split
bgneal@183 2720 nn = startContainer.splitText(startOffset);
bgneal@183 2721 startContainer.parentNode.insertBefore(n, nn);
bgneal@183 2722 }
bgneal@45 2723 } else {
bgneal@45 2724 // Insert element node
bgneal@183 2725 if (startContainer.childNodes.length > 0)
bgneal@183 2726 o = startContainer.childNodes[startOffset];
bgneal@183 2727
bgneal@183 2728 if (o)
bgneal@183 2729 startContainer.insertBefore(n, o);
bgneal@183 2730 else
bgneal@183 2731 startContainer.appendChild(n);
bgneal@183 2732 }
bgneal@183 2733 };
bgneal@183 2734
bgneal@183 2735 function surroundContents(n) {
bgneal@183 2736 var f = t.extractContents();
bgneal@45 2737
bgneal@45 2738 t.insertNode(n);
bgneal@45 2739 n.appendChild(f);
bgneal@45 2740 t.selectNode(n);
bgneal@183 2741 };
bgneal@183 2742
bgneal@183 2743 function cloneRange() {
bgneal@183 2744 return extend(new Range(dom), {
bgneal@183 2745 startContainer : t[START_CONTAINER],
bgneal@183 2746 startOffset : t[START_OFFSET],
bgneal@183 2747 endContainer : t[END_CONTAINER],
bgneal@183 2748 endOffset : t[END_OFFSET],
bgneal@45 2749 collapsed : t.collapsed,
bgneal@45 2750 commonAncestorContainer : t.commonAncestorContainer
bgneal@45 2751 });
bgneal@183 2752 };
bgneal@183 2753
bgneal@183 2754 // Private methods
bgneal@183 2755
bgneal@183 2756 function _getSelectedNode(container, offset) {
bgneal@183 2757 var child;
bgneal@183 2758
bgneal@183 2759 if (container.nodeType == 3 /* TEXT_NODE */)
bgneal@183 2760 return container;
bgneal@183 2761
bgneal@183 2762 if (offset < 0)
bgneal@183 2763 return container;
bgneal@183 2764
bgneal@183 2765 child = container.firstChild;
bgneal@183 2766 while (child && offset > 0) {
bgneal@183 2767 --offset;
bgneal@183 2768 child = child.nextSibling;
bgneal@183 2769 }
bgneal@183 2770
bgneal@183 2771 if (child)
bgneal@183 2772 return child;
bgneal@183 2773
bgneal@183 2774 return container;
bgneal@183 2775 };
bgneal@183 2776
bgneal@183 2777 function _isCollapsed() {
bgneal@183 2778 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
bgneal@183 2779 };
bgneal@183 2780
bgneal@183 2781 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
bgneal@45 2782 var c, offsetC, n, cmnRoot, childA, childB;
bgneal@45 2783
bgneal@183 2784 // In the first case the boundary-points have the same container. A is before B
bgneal@183 2785 // if its offset is less than the offset of B, A is equal to B if its offset is
bgneal@183 2786 // equal to the offset of B, and A is after B if its offset is greater than the
bgneal@45 2787 // offset of B.
bgneal@45 2788 if (containerA == containerB) {
bgneal@183 2789 if (offsetA == offsetB)
bgneal@45 2790 return 0; // equal
bgneal@183 2791
bgneal@183 2792 if (offsetA < offsetB)
bgneal@45 2793 return -1; // before
bgneal@183 2794
bgneal@183 2795 return 1; // after
bgneal@183 2796 }
bgneal@183 2797
bgneal@183 2798 // In the second case a child node C of the container of A is an ancestor
bgneal@183 2799 // container of B. In this case, A is before B if the offset of A is less than or
bgneal@45 2800 // equal to the index of the child node C and A is after B otherwise.
bgneal@45 2801 c = containerB;
bgneal@183 2802 while (c && c.parentNode != containerA)
bgneal@45 2803 c = c.parentNode;
bgneal@183 2804
bgneal@45 2805 if (c) {
bgneal@45 2806 offsetC = 0;
bgneal@45 2807 n = containerA.firstChild;
bgneal@45 2808
bgneal@45 2809 while (n != c && offsetC < offsetA) {
bgneal@45 2810 offsetC++;
bgneal@45 2811 n = n.nextSibling;
bgneal@45 2812 }
bgneal@45 2813
bgneal@183 2814 if (offsetA <= offsetC)
bgneal@45 2815 return -1; // before
bgneal@183 2816
bgneal@183 2817 return 1; // after
bgneal@183 2818 }
bgneal@183 2819
bgneal@183 2820 // In the third case a child node C of the container of B is an ancestor container
bgneal@183 2821 // of A. In this case, A is before B if the index of the child node C is less than
bgneal@45 2822 // the offset of B and A is after B otherwise.
bgneal@45 2823 c = containerA;
bgneal@45 2824 while (c && c.parentNode != containerB) {
bgneal@45 2825 c = c.parentNode;
bgneal@45 2826 }
bgneal@45 2827
bgneal@45 2828 if (c) {
bgneal@45 2829 offsetC = 0;
bgneal@45 2830 n = containerB.firstChild;
bgneal@45 2831
bgneal@45 2832 while (n != c && offsetC < offsetB) {
bgneal@45 2833 offsetC++;
bgneal@45 2834 n = n.nextSibling;
bgneal@45 2835 }
bgneal@45 2836
bgneal@183 2837 if (offsetC < offsetB)
bgneal@45 2838 return -1; // before
bgneal@183 2839
bgneal@183 2840 return 1; // after
bgneal@183 2841 }
bgneal@183 2842
bgneal@183 2843 // In the fourth case, none of three other cases hold: the containers of A and B
bgneal@183 2844 // are siblings or descendants of sibling nodes. In this case, A is before B if
bgneal@45 2845 // the container of A is before the container of B in a pre-order traversal of the
bgneal@45 2846 // Ranges' context tree and A is after B otherwise.
bgneal@183 2847 cmnRoot = dom.findCommonAncestor(containerA, containerB);
bgneal@45 2848 childA = containerA;
bgneal@45 2849
bgneal@183 2850 while (childA && childA.parentNode != cmnRoot)
bgneal@183 2851 childA = childA.parentNode;
bgneal@183 2852
bgneal@183 2853 if (!childA)
bgneal@45 2854 childA = cmnRoot;
bgneal@45 2855
bgneal@45 2856 childB = containerB;
bgneal@183 2857 while (childB && childB.parentNode != cmnRoot)
bgneal@45 2858 childB = childB.parentNode;
bgneal@183 2859
bgneal@183 2860 if (!childB)
bgneal@45 2861 childB = cmnRoot;
bgneal@183 2862
bgneal@183 2863 if (childA == childB)
bgneal@45 2864 return 0; // equal
bgneal@45 2865
bgneal@45 2866 n = cmnRoot.firstChild;
bgneal@45 2867 while (n) {
bgneal@183 2868 if (n == childA)
bgneal@45 2869 return -1; // before
bgneal@183 2870
bgneal@183 2871 if (n == childB)
bgneal@45 2872 return 1; // after
bgneal@45 2873
bgneal@45 2874 n = n.nextSibling;
bgneal@45 2875 }
bgneal@183 2876 };
bgneal@183 2877
bgneal@183 2878 function _setEndPoint(st, n, o) {
bgneal@183 2879 var ec, sc;
bgneal@45 2880
bgneal@45 2881 if (st) {
bgneal@183 2882 t[START_CONTAINER] = n;
bgneal@183 2883 t[START_OFFSET] = o;
bgneal@45 2884 } else {
bgneal@183 2885 t[END_CONTAINER] = n;
bgneal@183 2886 t[END_OFFSET] = o;
bgneal@183 2887 }
bgneal@183 2888
bgneal@183 2889 // If one boundary-point of a Range is set to have a root container
bgneal@183 2890 // other than the current one for the Range, the Range is collapsed to
bgneal@45 2891 // the new position. This enforces the restriction that both boundary-
bgneal@45 2892 // points of a Range must have the same root container.
bgneal@183 2893 ec = t[END_CONTAINER];
bgneal@45 2894 while (ec.parentNode)
bgneal@45 2895 ec = ec.parentNode;
bgneal@45 2896
bgneal@183 2897 sc = t[START_CONTAINER];
bgneal@45 2898 while (sc.parentNode)
bgneal@45 2899 sc = sc.parentNode;
bgneal@45 2900
bgneal@183 2901 if (sc == ec) {
bgneal@183 2902 // The start position of a Range is guaranteed to never be after the
bgneal@183 2903 // end position. To enforce this restriction, if the start is set to
bgneal@183 2904 // be at a position after the end, the Range is collapsed to that
bgneal@183 2905 // position.
bgneal@183 2906 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
bgneal@183 2907 t.collapse(st);
bgneal@183 2908 } else
bgneal@45 2909 t.collapse(st);
bgneal@183 2910
bgneal@183 2911 t.collapsed = _isCollapsed();
bgneal@183 2912 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
bgneal@183 2913 };
bgneal@183 2914
bgneal@183 2915 function _traverse(how) {
bgneal@183 2916 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
bgneal@183 2917
bgneal@183 2918 if (t[START_CONTAINER] == t[END_CONTAINER])
bgneal@183 2919 return _traverseSameContainer(how);
bgneal@183 2920
bgneal@183 2921 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@183 2922 if (p == t[START_CONTAINER])
bgneal@183 2923 return _traverseCommonStartContainer(c, how);
bgneal@45 2924
bgneal@45 2925 ++endContainerDepth;
bgneal@45 2926 }
bgneal@45 2927
bgneal@183 2928 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@183 2929 if (p == t[END_CONTAINER])
bgneal@183 2930 return _traverseCommonEndContainer(c, how);
bgneal@45 2931
bgneal@45 2932 ++startContainerDepth;
bgneal@45 2933 }
bgneal@45 2934
bgneal@45 2935 depthDiff = startContainerDepth - endContainerDepth;
bgneal@45 2936
bgneal@183 2937 startNode = t[START_CONTAINER];
bgneal@45 2938 while (depthDiff > 0) {
bgneal@45 2939 startNode = startNode.parentNode;
bgneal@45 2940 depthDiff--;
bgneal@45 2941 }
bgneal@45 2942
bgneal@183 2943 endNode = t[END_CONTAINER];
bgneal@45 2944 while (depthDiff < 0) {
bgneal@45 2945 endNode = endNode.parentNode;
bgneal@45 2946 depthDiff++;
bgneal@45 2947 }
bgneal@45 2948
bgneal@45 2949 // ascend the ancestor hierarchy until we have a common parent.
bgneal@45 2950 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
bgneal@45 2951 startNode = sp;
bgneal@45 2952 endNode = ep;
bgneal@45 2953 }
bgneal@45 2954
bgneal@183 2955 return _traverseCommonAncestors(startNode, endNode, how);
bgneal@183 2956 };
bgneal@183 2957
bgneal@183 2958 function _traverseSameContainer(how) {
bgneal@183 2959 var frag, s, sub, n, cnt, sibling, xferNode;
bgneal@45 2960
bgneal@45 2961 if (how != DELETE)
bgneal@183 2962 frag = doc.createDocumentFragment();
bgneal@45 2963
bgneal@45 2964 // If selection is empty, just return the fragment
bgneal@183 2965 if (t[START_OFFSET] == t[END_OFFSET])
bgneal@45 2966 return frag;
bgneal@45 2967
bgneal@45 2968 // Text node needs special case handling
bgneal@183 2969 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
bgneal@45 2970 // get the substring
bgneal@183 2971 s = t[START_CONTAINER].nodeValue;
bgneal@183 2972 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
bgneal@45 2973
bgneal@45 2974 // set the original text node to its new value
bgneal@45 2975 if (how != CLONE) {
bgneal@183 2976 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
bgneal@45 2977
bgneal@45 2978 // Nothing is partially selected, so collapse to start point
bgneal@183 2979 t.collapse(TRUE);
bgneal@45 2980 }
bgneal@45 2981
bgneal@45 2982 if (how == DELETE)
bgneal@183 2983 return;
bgneal@183 2984
bgneal@183 2985 frag.appendChild(doc.createTextNode(sub));
bgneal@45 2986 return frag;
bgneal@45 2987 }
bgneal@45 2988
bgneal@45 2989 // Copy nodes between the start/end offsets.
bgneal@183 2990 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
bgneal@183 2991 cnt = t[END_OFFSET] - t[START_OFFSET];
bgneal@45 2992
bgneal@45 2993 while (cnt > 0) {
bgneal@45 2994 sibling = n.nextSibling;
bgneal@183 2995 xferNode = _traverseFullySelected(n, how);
bgneal@45 2996
bgneal@45 2997 if (frag)
bgneal@45 2998 frag.appendChild( xferNode );
bgneal@45 2999
bgneal@45 3000 --cnt;
bgneal@45 3001 n = sibling;
bgneal@45 3002 }
bgneal@45 3003
bgneal@45 3004 // Nothing is partially selected, so collapse to start point
bgneal@45 3005 if (how != CLONE)
bgneal@183 3006 t.collapse(TRUE);
bgneal@45 3007
bgneal@45 3008 return frag;
bgneal@183 3009 };
bgneal@183 3010
bgneal@183 3011 function _traverseCommonStartContainer(endAncestor, how) {
bgneal@183 3012 var frag, n, endIdx, cnt, sibling, xferNode;
bgneal@45 3013
bgneal@45 3014 if (how != DELETE)
bgneal@183 3015 frag = doc.createDocumentFragment();
bgneal@183 3016
bgneal@183 3017 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 3018
bgneal@45 3019 if (frag)
bgneal@45 3020 frag.appendChild(n);
bgneal@45 3021
bgneal@183 3022 endIdx = nodeIndex(endAncestor);
bgneal@183 3023 cnt = endIdx - t[START_OFFSET];
bgneal@45 3024
bgneal@45 3025 if (cnt <= 0) {
bgneal@183 3026 // Collapse to just before the endAncestor, which
bgneal@45 3027 // is partially selected.
bgneal@45 3028 if (how != CLONE) {
bgneal@45 3029 t.setEndBefore(endAncestor);
bgneal@183 3030 t.collapse(FALSE);
bgneal@45 3031 }
bgneal@45 3032
bgneal@45 3033 return frag;
bgneal@45 3034 }
bgneal@45 3035
bgneal@45 3036 n = endAncestor.previousSibling;
bgneal@45 3037 while (cnt > 0) {
bgneal@45 3038 sibling = n.previousSibling;
bgneal@183 3039 xferNode = _traverseFullySelected(n, how);
bgneal@45 3040
bgneal@45 3041 if (frag)
bgneal@45 3042 frag.insertBefore(xferNode, frag.firstChild);
bgneal@45 3043
bgneal@45 3044 --cnt;
bgneal@45 3045 n = sibling;
bgneal@45 3046 }
bgneal@45 3047
bgneal@183 3048 // Collapse to just before the endAncestor, which
bgneal@45 3049 // is partially selected.
bgneal@45 3050 if (how != CLONE) {
bgneal@45 3051 t.setEndBefore(endAncestor);
bgneal@183 3052 t.collapse(FALSE);
bgneal@45 3053 }
bgneal@45 3054
bgneal@45 3055 return frag;
bgneal@183 3056 };
bgneal@183 3057
bgneal@183 3058 function _traverseCommonEndContainer(startAncestor, how) {
bgneal@183 3059 var frag, startIdx, n, cnt, sibling, xferNode;
bgneal@45 3060
bgneal@45 3061 if (how != DELETE)
bgneal@183 3062 frag = doc.createDocumentFragment();
bgneal@183 3063
bgneal@183 3064 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 3065 if (frag)
bgneal@45 3066 frag.appendChild(n);
bgneal@45 3067
bgneal@183 3068 startIdx = nodeIndex(startAncestor);
bgneal@45 3069 ++startIdx; // Because we already traversed it....
bgneal@45 3070
bgneal@183 3071 cnt = t[END_OFFSET] - startIdx;
bgneal@45 3072 n = startAncestor.nextSibling;
bgneal@45 3073 while (cnt > 0) {
bgneal@45 3074 sibling = n.nextSibling;
bgneal@183 3075 xferNode = _traverseFullySelected(n, how);
bgneal@45 3076
bgneal@45 3077 if (frag)
bgneal@45 3078 frag.appendChild(xferNode);
bgneal@45 3079
bgneal@45 3080 --cnt;
bgneal@45 3081 n = sibling;
bgneal@45 3082 }
bgneal@45 3083
bgneal@45 3084 if (how != CLONE) {
bgneal@45 3085 t.setStartAfter(startAncestor);
bgneal@183 3086 t.collapse(TRUE);
bgneal@45 3087 }
bgneal@45 3088
bgneal@45 3089 return frag;
bgneal@183 3090 };
bgneal@183 3091
bgneal@183 3092 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
bgneal@183 3093 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
bgneal@45 3094
bgneal@45 3095 if (how != DELETE)
bgneal@183 3096 frag = doc.createDocumentFragment();
bgneal@183 3097
bgneal@183 3098 n = _traverseLeftBoundary(startAncestor, how);
bgneal@45 3099 if (frag)
bgneal@45 3100 frag.appendChild(n);
bgneal@45 3101
bgneal@45 3102 commonParent = startAncestor.parentNode;
bgneal@183 3103 startOffset = nodeIndex(startAncestor);
bgneal@183 3104 endOffset = nodeIndex(endAncestor);
bgneal@45 3105 ++startOffset;
bgneal@45 3106
bgneal@45 3107 cnt = endOffset - startOffset;
bgneal@45 3108 sibling = startAncestor.nextSibling;
bgneal@45 3109
bgneal@45 3110 while (cnt > 0) {
bgneal@45 3111 nextSibling = sibling.nextSibling;
bgneal@183 3112 n = _traverseFullySelected(sibling, how);
bgneal@45 3113
bgneal@45 3114 if (frag)
bgneal@45 3115 frag.appendChild(n);
bgneal@45 3116
bgneal@45 3117 sibling = nextSibling;
bgneal@45 3118 --cnt;
bgneal@45 3119 }
bgneal@45 3120
bgneal@183 3121 n = _traverseRightBoundary(endAncestor, how);
bgneal@45 3122
bgneal@45 3123 if (frag)
bgneal@45 3124 frag.appendChild(n);
bgneal@45 3125
bgneal@45 3126 if (how != CLONE) {
bgneal@45 3127 t.setStartAfter(startAncestor);
bgneal@183 3128 t.collapse(TRUE);
bgneal@45 3129 }
bgneal@45 3130
bgneal@45 3131 return frag;
bgneal@183 3132 };
bgneal@183 3133
bgneal@183 3134 function _traverseRightBoundary(root, how) {
bgneal@183 3135 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
bgneal@45 3136
bgneal@45 3137 if (next == root)
bgneal@183 3138 return _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 3139
bgneal@45 3140 parent = next.parentNode;
bgneal@183 3141 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@183 3142
bgneal@183 3143 while (parent) {
bgneal@183 3144 while (next) {
bgneal@45 3145 prevSibling = next.previousSibling;
bgneal@183 3146 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
bgneal@45 3147
bgneal@45 3148 if (how != DELETE)
bgneal@45 3149 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
bgneal@45 3150
bgneal@183 3151 isFullySelected = TRUE;
bgneal@45 3152 next = prevSibling;
bgneal@45 3153 }
bgneal@45 3154
bgneal@45 3155 if (parent == root)
bgneal@45 3156 return clonedParent;
bgneal@45 3157
bgneal@45 3158 next = parent.previousSibling;
bgneal@45 3159 parent = parent.parentNode;
bgneal@45 3160
bgneal@183 3161 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@45 3162
bgneal@45 3163 if (how != DELETE)
bgneal@45 3164 clonedGrandParent.appendChild(clonedParent);
bgneal@45 3165
bgneal@45 3166 clonedParent = clonedGrandParent;
bgneal@45 3167 }
bgneal@183 3168 };
bgneal@183 3169
bgneal@183 3170 function _traverseLeftBoundary(root, how) {
bgneal@183 3171 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
bgneal@45 3172
bgneal@45 3173 if (next == root)
bgneal@183 3174 return _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 3175
bgneal@45 3176 parent = next.parentNode;
bgneal@183 3177 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@183 3178
bgneal@183 3179 while (parent) {
bgneal@183 3180 while (next) {
bgneal@45 3181 nextSibling = next.nextSibling;
bgneal@183 3182 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
bgneal@45 3183
bgneal@45 3184 if (how != DELETE)
bgneal@45 3185 clonedParent.appendChild(clonedChild);
bgneal@45 3186
bgneal@183 3187 isFullySelected = TRUE;
bgneal@45 3188 next = nextSibling;
bgneal@45 3189 }
bgneal@45 3190
bgneal@45 3191 if (parent == root)
bgneal@45 3192 return clonedParent;
bgneal@45 3193
bgneal@45 3194 next = parent.nextSibling;
bgneal@45 3195 parent = parent.parentNode;
bgneal@45 3196
bgneal@183 3197 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@45 3198
bgneal@45 3199 if (how != DELETE)
bgneal@45 3200 clonedGrandParent.appendChild(clonedParent);
bgneal@45 3201
bgneal@45 3202 clonedParent = clonedGrandParent;
bgneal@45 3203 }
bgneal@183 3204 };
bgneal@183 3205
bgneal@183 3206 function _traverseNode(n, isFullySelected, isLeft, how) {
bgneal@183 3207 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
bgneal@45 3208
bgneal@45 3209 if (isFullySelected)
bgneal@183 3210 return _traverseFullySelected(n, how);
bgneal@45 3211
bgneal@45 3212 if (n.nodeType == 3 /* TEXT_NODE */) {
bgneal@45 3213 txtValue = n.nodeValue;
bgneal@45 3214
bgneal@45 3215 if (isLeft) {
bgneal@183 3216 offset = t[START_OFFSET];
bgneal@45 3217 newNodeValue = txtValue.substring(offset);
bgneal@45 3218 oldNodeValue = txtValue.substring(0, offset);
bgneal@45 3219 } else {
bgneal@183 3220 offset = t[END_OFFSET];
bgneal@45 3221 newNodeValue = txtValue.substring(0, offset);
bgneal@45 3222 oldNodeValue = txtValue.substring(offset);
bgneal@45 3223 }
bgneal@45 3224
bgneal@45 3225 if (how != CLONE)
bgneal@45 3226 n.nodeValue = oldNodeValue;
bgneal@45 3227
bgneal@45 3228 if (how == DELETE)
bgneal@183 3229 return;
bgneal@183 3230
bgneal@183 3231 newNode = n.cloneNode(FALSE);
bgneal@45 3232 newNode.nodeValue = newNodeValue;
bgneal@45 3233
bgneal@45 3234 return newNode;
bgneal@45 3235 }
bgneal@45 3236
bgneal@45 3237 if (how == DELETE)
bgneal@183 3238 return;
bgneal@183 3239
bgneal@183 3240 return n.cloneNode(FALSE);
bgneal@183 3241 };
bgneal@183 3242
bgneal@183 3243 function _traverseFullySelected(n, how) {
bgneal@45 3244 if (how != DELETE)
bgneal@183 3245 return how == CLONE ? n.cloneNode(TRUE) : n;
bgneal@45 3246
bgneal@45 3247 n.parentNode.removeChild(n);
bgneal@183 3248 };
bgneal@183 3249 };
bgneal@45 3250
bgneal@45 3251 ns.Range = Range;
bgneal@45 3252 })(tinymce.dom);
bgneal@183 3253
bgneal@45 3254 (function() {
bgneal@45 3255 function Selection(selection) {
bgneal@183 3256 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
bgneal@183 3257
bgneal@183 3258 // Compares two IE specific ranges to see if they are different
bgneal@183 3259 // this method is useful when invalidating the cached selection range
bgneal@183 3260 function compareRanges(rng1, rng2) {
bgneal@183 3261 if (rng1 && rng2) {
bgneal@183 3262 // Both are control ranges and the selected element matches
bgneal@183 3263 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
bgneal@183 3264 return TRUE;
bgneal@183 3265
bgneal@183 3266 // Both are text ranges and the range matches
bgneal@183 3267 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
bgneal@183 3268 // IE will say that the range is equal then produce an invalid argument exception
bgneal@183 3269 // if you perform specific operations in a keyup event. For example Ctrl+Del.
bgneal@183 3270 // This hack will invalidate the range cache if the exception occurs
bgneal@183 3271 try {
bgneal@183 3272 // Try accessing nextSibling will producer an invalid argument some times
bgneal@183 3273 range.startContainer.nextSibling;
bgneal@183 3274 return TRUE;
bgneal@183 3275 } catch (ex) {
bgneal@183 3276 // Ignore
bgneal@183 3277 }
bgneal@183 3278 }
bgneal@183 3279 }
bgneal@183 3280
bgneal@183 3281 return FALSE;
bgneal@183 3282 };
bgneal@183 3283
bgneal@183 3284 // Returns a W3C DOM compatible range object by using the IE Range API
bgneal@45 3285 function getRange() {
bgneal@183 3286 var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged;
bgneal@183 3287
bgneal@183 3288 // If selection is outside the current document just return an empty range
bgneal@183 3289 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
bgneal@183 3290 if (element.ownerDocument != dom.doc)
bgneal@45 3291 return domRange;
bgneal@183 3292
bgneal@183 3293 // Handle control selection or text selection of a image
bgneal@183 3294 if (ieRange.item || !element.hasChildNodes()) {
bgneal@183 3295 domRange.setStart(element.parentNode, dom.nodeIndex(element));
bgneal@183 3296 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
bgneal@183 3297
bgneal@183 3298 return domRange;
bgneal@183 3299 }
bgneal@183 3300
bgneal@183 3301 // Duplicare IE selection range and check if the range is collapsed
bgneal@183 3302 ieRange2 = ieRange.duplicate();
bgneal@183 3303 collapsed = selection.isCollapsed();
bgneal@183 3304
bgneal@183 3305 // Insert invisible start marker
bgneal@183 3306 ieRange.collapse();
bgneal@183 3307 ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>');
bgneal@183 3308
bgneal@183 3309 // Insert invisible end marker
bgneal@183 3310 if (!collapsed) {
bgneal@183 3311 ieRange2.collapse(FALSE);
bgneal@183 3312 ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>');
bgneal@183 3313 }
bgneal@183 3314
bgneal@183 3315 // Sets the end point of the range by looking for the marker
bgneal@183 3316 // This method also merges the text nodes it splits so that
bgneal@183 3317 // the DOM doesn't get fragmented.
bgneal@183 3318 function setEndPoint(start) {
bgneal@183 3319 var container, offset, marker, sibling;
bgneal@183 3320
bgneal@183 3321 // Look for endpoint marker
bgneal@183 3322 marker = dom.get('_mce_' + (start ? 'start' : 'end'));
bgneal@183 3323 sibling = marker.previousSibling;
bgneal@183 3324
bgneal@183 3325 // Is marker after a text node
bgneal@183 3326 if (sibling && sibling.nodeType == 3) {
bgneal@183 3327 // Get container node and calc offset
bgneal@183 3328 container = sibling;
bgneal@183 3329 offset = container.nodeValue.length;
bgneal@183 3330 dom.remove(marker);
bgneal@183 3331
bgneal@183 3332 // Merge text nodes to reduce DOM fragmentation
bgneal@183 3333 sibling = container.nextSibling;
bgneal@183 3334 if (sibling && sibling.nodeType == 3) {
bgneal@183 3335 isMerged = TRUE;
bgneal@183 3336 container.appendData(sibling.nodeValue);
bgneal@183 3337 dom.remove(sibling);
bgneal@183 3338 }
bgneal@183 3339 } else {
bgneal@183 3340 sibling = marker.nextSibling;
bgneal@183 3341
bgneal@183 3342 // Is marker before a text node
bgneal@183 3343 if (sibling && sibling.nodeType == 3) {
bgneal@183 3344 container = sibling;
bgneal@183 3345 offset = 0;
bgneal@183 3346 } else {
bgneal@183 3347 // Is marker before an element
bgneal@183 3348 if (sibling)
bgneal@183 3349 offset = dom.nodeIndex(sibling) - 1;
bgneal@183 3350 else
bgneal@183 3351 offset = dom.nodeIndex(marker);
bgneal@183 3352
bgneal@183 3353 container = marker.parentNode;
bgneal@183 3354 }
bgneal@183 3355
bgneal@183 3356 dom.remove(marker);
bgneal@183 3357 }
bgneal@183 3358
bgneal@183 3359 // Set start of range
bgneal@183 3360 if (start)
bgneal@183 3361 domRange.setStart(container, offset);
bgneal@183 3362
bgneal@183 3363 // Set end of range or automatically if it's collapsed to increase performance
bgneal@183 3364 if (!start || collapsed)
bgneal@183 3365 domRange.setEnd(container, offset);
bgneal@45 3366 };
bgneal@45 3367
bgneal@183 3368 // Set start of range
bgneal@183 3369 setEndPoint(TRUE);
bgneal@183 3370
bgneal@183 3371 // Set end of range if needed
bgneal@183 3372 if (!collapsed)
bgneal@183 3373 setEndPoint(FALSE);
bgneal@183 3374
bgneal@183 3375 // Restore selection if the range contents was merged
bgneal@183 3376 // since the selection was then moved since the text nodes got changed
bgneal@183 3377 if (isMerged)
bgneal@183 3378 t.addRange(domRange);
bgneal@183 3379
bgneal@183 3380 return domRange;
bgneal@183 3381 };
bgneal@183 3382
bgneal@183 3383 this.addRange = function(rng) {
bgneal@183 3384 var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd;
bgneal@183 3385
bgneal@183 3386 this.destroy();
bgneal@183 3387
bgneal@183 3388 // Setup some shorter versions
bgneal@183 3389 sc = rng.startContainer;
bgneal@183 3390 so = rng.startOffset;
bgneal@183 3391 ec = rng.endContainer;
bgneal@183 3392 eo = rng.endOffset;
bgneal@183 3393 ieRng = body.createTextRange();
bgneal@183 3394
bgneal@183 3395 // If document selection move caret to first node in document
bgneal@183 3396 if (sc == doc || ec == doc) {
bgneal@183 3397 ieRng = body.createTextRange();
bgneal@183 3398 ieRng.collapse();
bgneal@183 3399 ieRng.select();
bgneal@183 3400 return;
bgneal@183 3401 }
bgneal@183 3402
bgneal@183 3403 // If child index resolve it
bgneal@183 3404 if (sc.nodeType == 1 && sc.hasChildNodes()) {
bgneal@183 3405 lastIndex = sc.childNodes.length - 1;
bgneal@183 3406
bgneal@183 3407 // Index is higher that the child count then we need to jump over the start container
bgneal@183 3408 if (so > lastIndex) {
bgneal@183 3409 skipStart = 1;
bgneal@183 3410 sc = sc.childNodes[lastIndex];
bgneal@183 3411 } else
bgneal@183 3412 sc = sc.childNodes[so];
bgneal@183 3413
bgneal@183 3414 // Child was text node then move offset to start of it
bgneal@183 3415 if (sc.nodeType == 3)
bgneal@183 3416 so = 0;
bgneal@183 3417 }
bgneal@183 3418
bgneal@183 3419 // If child index resolve it
bgneal@183 3420 if (ec.nodeType == 1 && ec.hasChildNodes()) {
bgneal@183 3421 lastIndex = ec.childNodes.length - 1;
bgneal@183 3422
bgneal@183 3423 if (eo == 0) {
bgneal@183 3424 skipEnd = 1;
bgneal@183 3425 ec = ec.childNodes[0];
bgneal@183 3426 } else {
bgneal@183 3427 ec = ec.childNodes[Math.min(lastIndex, eo - 1)];
bgneal@183 3428
bgneal@183 3429 // Child was text node then move offset to end of text node
bgneal@183 3430 if (ec.nodeType == 3)
bgneal@183 3431 eo = ec.nodeValue.length;
bgneal@183 3432 }
bgneal@183 3433 }
bgneal@183 3434
bgneal@183 3435 // Single element selection
bgneal@183 3436 if (sc == ec && sc.nodeType == 1) {
bgneal@183 3437 // Make control selection for some elements
bgneal@183 3438 if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
bgneal@183 3439 ieRng = body.createControlRange();
bgneal@183 3440 ieRng.addElement(sc);
bgneal@183 3441 } else {
bgneal@183 3442 ieRng = body.createTextRange();
bgneal@183 3443
bgneal@183 3444 // Padd empty elements with invisible character
bgneal@183 3445 if (!sc.hasChildNodes() && sc.canHaveHTML)
bgneal@183 3446 sc.innerHTML = invisibleChar;
bgneal@183 3447
bgneal@183 3448 // Select element contents
bgneal@183 3449 ieRng.moveToElementText(sc);
bgneal@183 3450
bgneal@183 3451 // If it's only containing a padding remove it so the caret remains
bgneal@183 3452 if (sc.innerHTML == invisibleChar) {
bgneal@183 3453 ieRng.collapse(TRUE);
bgneal@183 3454 sc.removeChild(sc.firstChild);
bgneal@183 3455 }
bgneal@183 3456 }
bgneal@183 3457
bgneal@183 3458 if (so == eo)
bgneal@183 3459 ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
bgneal@183 3460
bgneal@183 3461 ieRng.select();
bgneal@183 3462 ieRng.scrollIntoView();
bgneal@183 3463 return;
bgneal@183 3464 }
bgneal@183 3465
bgneal@183 3466 // Create range and marker
bgneal@183 3467 ieRng = body.createTextRange();
bgneal@183 3468 marker = doc.createElement('span');
bgneal@183 3469 marker.innerHTML = ' ';
bgneal@183 3470
bgneal@183 3471 // Set start of range to startContainer/startOffset
bgneal@183 3472 if (sc.nodeType == 3) {
bgneal@183 3473 // Insert marker after/before startContainer
bgneal@183 3474 if (skipStart)
bgneal@183 3475 dom.insertAfter(marker, sc);
bgneal@183 3476 else
bgneal@183 3477 sc.parentNode.insertBefore(marker, sc);
bgneal@183 3478
bgneal@183 3479 // Select marker the caret to offset position
bgneal@183 3480 ieRng.moveToElementText(marker);
bgneal@183 3481 marker.parentNode.removeChild(marker);
bgneal@183 3482 ieRng.move('character', so);
bgneal@183 3483 } else {
bgneal@183 3484 ieRng.moveToElementText(sc);
bgneal@183 3485
bgneal@183 3486 if (skipStart)
bgneal@183 3487 ieRng.collapse(FALSE);
bgneal@183 3488 }
bgneal@183 3489
bgneal@183 3490 // If same text container then we can do a more simple move
bgneal@183 3491 if (sc == ec && sc.nodeType == 3) {
bgneal@183 3492 ieRng.moveEnd('character', eo - so);
bgneal@183 3493 ieRng.select();
bgneal@183 3494 ieRng.scrollIntoView();
bgneal@183 3495 return;
bgneal@183 3496 }
bgneal@183 3497
bgneal@183 3498 // Set end of range to endContainer/endOffset
bgneal@183 3499 ieRng2 = body.createTextRange();
bgneal@183 3500 if (ec.nodeType == 3) {
bgneal@183 3501 // Insert marker after/before startContainer
bgneal@183 3502 ec.parentNode.insertBefore(marker, ec);
bgneal@183 3503
bgneal@183 3504 // Move selection to end marker and move caret to end offset
bgneal@183 3505 ieRng2.moveToElementText(marker);
bgneal@183 3506 marker.parentNode.removeChild(marker);
bgneal@183 3507 ieRng2.move('character', eo);
bgneal@183 3508 ieRng.setEndPoint('EndToStart', ieRng2);
bgneal@183 3509 } else {
bgneal@183 3510 ieRng2.moveToElementText(ec);
bgneal@183 3511 ieRng2.collapse(!!skipEnd);
bgneal@183 3512 ieRng.setEndPoint('EndToEnd', ieRng2);
bgneal@183 3513 }
bgneal@183 3514
bgneal@183 3515 ieRng.select();
bgneal@183 3516 ieRng.scrollIntoView();
bgneal@183 3517 };
bgneal@183 3518
bgneal@183 3519 this.getRangeAt = function() {
bgneal@183 3520 // Setup new range if the cache is empty
bgneal@183 3521 if (!range || !compareRanges(lastIERng, selection.getRng())) {
bgneal@183 3522 range = getRange();
bgneal@183 3523
bgneal@183 3524 // Store away text range for next call
bgneal@183 3525 lastIERng = selection.getRng();
bgneal@183 3526 }
bgneal@183 3527
bgneal@183 3528 // Return cached range
bgneal@183 3529 return range;
bgneal@183 3530 };
bgneal@183 3531
bgneal@183 3532 this.destroy = function() {
bgneal@183 3533 // Destroy cached range and last IE range to avoid memory leaks
bgneal@183 3534 lastIERng = range = null;
bgneal@183 3535 };
bgneal@183 3536
bgneal@183 3537 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
bgneal@183 3538 if (selection.dom.boxModel) {
bgneal@183 3539 (function() {
bgneal@183 3540 var doc = dom.doc, body = doc.body, started, startRng;
bgneal@183 3541
bgneal@183 3542 // Make HTML element unselectable since we are going to handle selection by hand
bgneal@183 3543 doc.documentElement.unselectable = TRUE;
bgneal@183 3544
bgneal@183 3545 // Return range from point or null if it failed
bgneal@183 3546 function rngFromPoint(x, y) {
bgneal@183 3547 var rng = body.createTextRange();
bgneal@183 3548
bgneal@183 3549 try {
bgneal@183 3550 rng.moveToPoint(x, y);
bgneal@183 3551 } catch (ex) {
bgneal@183 3552 // IE sometimes throws and exception, so lets just ignore it
bgneal@183 3553 rng = null;
bgneal@183 3554 }
bgneal@183 3555
bgneal@183 3556 return rng;
bgneal@183 3557 };
bgneal@183 3558
bgneal@183 3559 // Fires while the selection is changing
bgneal@183 3560 function selectionChange(e) {
bgneal@183 3561 var pointRng;
bgneal@183 3562
bgneal@183 3563 // Check if the button is down or not
bgneal@183 3564 if (e.button) {
bgneal@183 3565 // Create range from mouse position
bgneal@183 3566 pointRng = rngFromPoint(e.x, e.y);
bgneal@183 3567
bgneal@183 3568 if (pointRng) {
bgneal@183 3569 // Check if pointRange is before/after selection then change the endPoint
bgneal@183 3570 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
bgneal@183 3571 pointRng.setEndPoint('StartToStart', startRng);
bgneal@183 3572 else
bgneal@183 3573 pointRng.setEndPoint('EndToEnd', startRng);
bgneal@183 3574
bgneal@183 3575 pointRng.select();
bgneal@183 3576 }
bgneal@45 3577 } else
bgneal@183 3578 endSelection();
bgneal@183 3579 }
bgneal@183 3580
bgneal@183 3581 // Removes listeners
bgneal@183 3582 function endSelection() {
bgneal@183 3583 dom.unbind(doc, 'mouseup', endSelection);
bgneal@183 3584 dom.unbind(doc, 'mousemove', selectionChange);
bgneal@183 3585 started = 0;
bgneal@183 3586 };
bgneal@183 3587
bgneal@183 3588 // Detect when user selects outside BODY
bgneal@183 3589 dom.bind(doc, 'mousedown', function(e) {
bgneal@183 3590 if (e.target.nodeName === 'HTML') {
bgneal@183 3591 if (started)
bgneal@183 3592 endSelection();
bgneal@183 3593
bgneal@183 3594 started = 1;
bgneal@183 3595
bgneal@183 3596 // Setup start position
bgneal@183 3597 startRng = rngFromPoint(e.x, e.y);
bgneal@183 3598 if (startRng) {
bgneal@183 3599 // Listen for selection change events
bgneal@183 3600 dom.bind(doc, 'mouseup', endSelection);
bgneal@183 3601 dom.bind(doc, 'mousemove', selectionChange);
bgneal@183 3602
bgneal@183 3603 startRng.select();
bgneal@183 3604 }
bgneal@183 3605 }
bgneal@183 3606 });
bgneal@183 3607 })();
bgneal@183 3608 }
bgneal@45 3609 };
bgneal@45 3610
bgneal@45 3611 // Expose the selection object
bgneal@45 3612 tinymce.dom.TridentSelection = Selection;
bgneal@45 3613 })();
bgneal@45 3614
bgneal@183 3615
bgneal@45 3616 /*
bgneal@45 3617 * Sizzle CSS Selector Engine - v1.0
bgneal@45 3618 * Copyright 2009, The Dojo Foundation
bgneal@45 3619 * Released under the MIT, BSD, and GPL Licenses.
bgneal@45 3620 * More information: http://sizzlejs.com/
bgneal@45 3621 */
bgneal@45 3622 (function(){
bgneal@45 3623
bgneal@45 3624 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
bgneal@45 3625 done = 0,
bgneal@45 3626 toString = Object.prototype.toString,
bgneal@183 3627 hasDuplicate = false;
bgneal@45 3628
bgneal@45 3629 var Sizzle = function(selector, context, results, seed) {
bgneal@45 3630 results = results || [];
bgneal@45 3631 var origContext = context = context || document;
bgneal@45 3632
bgneal@45 3633 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
bgneal@45 3634 return [];
bgneal@45 3635 }
bgneal@45 3636
bgneal@45 3637 if ( !selector || typeof selector !== "string" ) {
bgneal@45 3638 return results;
bgneal@45 3639 }
bgneal@45 3640
bgneal@45 3641 var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context);
bgneal@45 3642
bgneal@45 3643 // Reset the position of the chunker regexp (start from head)
bgneal@45 3644 chunker.lastIndex = 0;
bgneal@45 3645
bgneal@45 3646 while ( (m = chunker.exec(selector)) !== null ) {
bgneal@45 3647 parts.push( m[1] );
bgneal@45 3648
bgneal@45 3649 if ( m[2] ) {
bgneal@45 3650 extra = RegExp.rightContext;
bgneal@45 3651 break;
bgneal@45 3652 }
bgneal@45 3653 }
bgneal@45 3654
bgneal@45 3655 if ( parts.length > 1 && origPOS.exec( selector ) ) {
bgneal@45 3656 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
bgneal@45 3657 set = posProcess( parts[0] + parts[1], context );
bgneal@45 3658 } else {
bgneal@45 3659 set = Expr.relative[ parts[0] ] ?
bgneal@45 3660 [ context ] :
bgneal@45 3661 Sizzle( parts.shift(), context );
bgneal@45 3662
bgneal@45 3663 while ( parts.length ) {
bgneal@45 3664 selector = parts.shift();
bgneal@45 3665
bgneal@45 3666 if ( Expr.relative[ selector ] )
bgneal@45 3667 selector += parts.shift();
bgneal@45 3668
bgneal@45 3669 set = posProcess( selector, set );
bgneal@45 3670 }
bgneal@45 3671 }
bgneal@45 3672 } else {
bgneal@45 3673 // Take a shortcut and set the context if the root selector is an ID
bgneal@45 3674 // (but not if it'll be faster if the inner selector is an ID)
bgneal@45 3675 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
bgneal@45 3676 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
bgneal@45 3677 var ret = Sizzle.find( parts.shift(), context, contextXML );
bgneal@45 3678 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
bgneal@45 3679 }
bgneal@45 3680
bgneal@45 3681 if ( context ) {
bgneal@45 3682 var ret = seed ?
bgneal@45 3683 { expr: parts.pop(), set: makeArray(seed) } :
bgneal@45 3684 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
bgneal@45 3685 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
bgneal@45 3686
bgneal@45 3687 if ( parts.length > 0 ) {
bgneal@45 3688 checkSet = makeArray(set);
bgneal@45 3689 } else {
bgneal@45 3690 prune = false;
bgneal@45 3691 }
bgneal@45 3692
bgneal@45 3693 while ( parts.length ) {
bgneal@45 3694 var cur = parts.pop(), pop = cur;
bgneal@45 3695
bgneal@45 3696 if ( !Expr.relative[ cur ] ) {
bgneal@45 3697 cur = "";
bgneal@45 3698 } else {
bgneal@45 3699 pop = parts.pop();
bgneal@45 3700 }
bgneal@45 3701
bgneal@45 3702 if ( pop == null ) {
bgneal@45 3703 pop = context;
bgneal@45 3704 }
bgneal@45 3705
bgneal@45 3706 Expr.relative[ cur ]( checkSet, pop, contextXML );
bgneal@45 3707 }
bgneal@45 3708 } else {
bgneal@45 3709 checkSet = parts = [];
bgneal@45 3710 }
bgneal@45 3711 }
bgneal@45 3712
bgneal@45 3713 if ( !checkSet ) {
bgneal@45 3714 checkSet = set;
bgneal@45 3715 }
bgneal@45 3716
bgneal@45 3717 if ( !checkSet ) {
bgneal@45 3718 throw "Syntax error, unrecognized expression: " + (cur || selector);
bgneal@45 3719 }
bgneal@45 3720
bgneal@45 3721 if ( toString.call(checkSet) === "[object Array]" ) {
bgneal@45 3722 if ( !prune ) {
bgneal@183 3723 results.push.apply( results, checkSet );
bgneal@45 3724 } else if ( context && context.nodeType === 1 ) {
bgneal@45 3725 for ( var i = 0; checkSet[i] != null; i++ ) {
bgneal@45 3726 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
bgneal@183 3727 results.push( set[i] );
bgneal@45 3728 }
bgneal@45 3729 }
bgneal@45 3730 } else {
bgneal@45 3731 for ( var i = 0; checkSet[i] != null; i++ ) {
bgneal@45 3732 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
bgneal@183 3733 results.push( set[i] );
bgneal@45 3734 }
bgneal@45 3735 }
bgneal@45 3736 }
bgneal@45 3737 } else {
bgneal@45 3738 makeArray( checkSet, results );
bgneal@45 3739 }
bgneal@45 3740
bgneal@45 3741 if ( extra ) {
bgneal@45 3742 Sizzle( extra, origContext, results, seed );
bgneal@45 3743 Sizzle.uniqueSort( results );
bgneal@45 3744 }
bgneal@45 3745
bgneal@45 3746 return results;
bgneal@45 3747 };
bgneal@45 3748
bgneal@45 3749 Sizzle.uniqueSort = function(results){
bgneal@45 3750 if ( sortOrder ) {
bgneal@45 3751 hasDuplicate = false;
bgneal@183 3752 results.sort(sortOrder);
bgneal@45 3753
bgneal@45 3754 if ( hasDuplicate ) {
bgneal@45 3755 for ( var i = 1; i < results.length; i++ ) {
bgneal@45 3756 if ( results[i] === results[i-1] ) {
bgneal@183 3757 results.splice(i--, 1);
bgneal@45 3758 }
bgneal@45 3759 }
bgneal@45 3760 }
bgneal@45 3761 }
bgneal@45 3762 };
bgneal@45 3763
bgneal@45 3764 Sizzle.matches = function(expr, set){
bgneal@45 3765 return Sizzle(expr, null, null, set);
bgneal@45 3766 };
bgneal@45 3767
bgneal@45 3768 Sizzle.find = function(expr, context, isXML){
bgneal@45 3769 var set, match;
bgneal@45 3770
bgneal@45 3771 if ( !expr ) {
bgneal@45 3772 return [];
bgneal@45 3773 }
bgneal@45 3774
bgneal@45 3775 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
bgneal@45 3776 var type = Expr.order[i], match;
bgneal@45 3777
bgneal@45 3778 if ( (match = Expr.match[ type ].exec( expr )) ) {
bgneal@45 3779 var left = RegExp.leftContext;
bgneal@45 3780
bgneal@45 3781 if ( left.substr( left.length - 1 ) !== "\\" ) {
bgneal@45 3782 match[1] = (match[1] || "").replace(/\\/g, "");
bgneal@45 3783 set = Expr.find[ type ]( match, context, isXML );
bgneal@45 3784 if ( set != null ) {
bgneal@45 3785 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 3786 break;
bgneal@45 3787 }
bgneal@45 3788 }
bgneal@45 3789 }
bgneal@45 3790 }
bgneal@45 3791
bgneal@45 3792 if ( !set ) {
bgneal@45 3793 set = context.getElementsByTagName("*");
bgneal@45 3794 }
bgneal@45 3795
bgneal@45 3796 return {set: set, expr: expr};
bgneal@45 3797 };
bgneal@45 3798
bgneal@45 3799 Sizzle.filter = function(expr, set, inplace, not){
bgneal@45 3800 var old = expr, result = [], curLoop = set, match, anyFound,
bgneal@45 3801 isXMLFilter = set && set[0] && isXML(set[0]);
bgneal@45 3802
bgneal@45 3803 while ( expr && set.length ) {
bgneal@45 3804 for ( var type in Expr.filter ) {
bgneal@45 3805 if ( (match = Expr.match[ type ].exec( expr )) != null ) {
bgneal@45 3806 var filter = Expr.filter[ type ], found, item;
bgneal@45 3807 anyFound = false;
bgneal@45 3808
bgneal@45 3809 if ( curLoop == result ) {
bgneal@45 3810 result = [];
bgneal@45 3811 }
bgneal@45 3812
bgneal@45 3813 if ( Expr.preFilter[ type ] ) {
bgneal@45 3814 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
bgneal@45 3815
bgneal@45 3816 if ( !match ) {
bgneal@45 3817 anyFound = found = true;
bgneal@45 3818 } else if ( match === true ) {
bgneal@45 3819 continue;
bgneal@45 3820 }
bgneal@45 3821 }
bgneal@45 3822
bgneal@45 3823 if ( match ) {
bgneal@45 3824 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
bgneal@45 3825 if ( item ) {
bgneal@45 3826 found = filter( item, match, i, curLoop );
bgneal@45 3827 var pass = not ^ !!found;
bgneal@45 3828
bgneal@45 3829 if ( inplace && found != null ) {
bgneal@45 3830 if ( pass ) {
bgneal@45 3831 anyFound = true;
bgneal@45 3832 } else {
bgneal@45 3833 curLoop[i] = false;
bgneal@45 3834 }
bgneal@45 3835 } else if ( pass ) {
bgneal@45 3836 result.push( item );
bgneal@45 3837 anyFound = true;
bgneal@45 3838 }
bgneal@45 3839 }
bgneal@45 3840 }
bgneal@45 3841 }
bgneal@45 3842
bgneal@45 3843 if ( found !== undefined ) {
bgneal@45 3844 if ( !inplace ) {
bgneal@45 3845 curLoop = result;
bgneal@45 3846 }
bgneal@45 3847
bgneal@45 3848 expr = expr.replace( Expr.match[ type ], "" );
bgneal@45 3849
bgneal@45 3850 if ( !anyFound ) {
bgneal@45 3851 return [];
bgneal@45 3852 }
bgneal@45 3853
bgneal@45 3854 break;
bgneal@45 3855 }
bgneal@45 3856 }
bgneal@45 3857 }
bgneal@45 3858
bgneal@45 3859 // Improper expression
bgneal@45 3860 if ( expr == old ) {
bgneal@45 3861 if ( anyFound == null ) {
bgneal@45 3862 throw "Syntax error, unrecognized expression: " + expr;
bgneal@45 3863 } else {
bgneal@45 3864 break;
bgneal@45 3865 }
bgneal@45 3866 }
bgneal@45 3867
bgneal@45 3868 old = expr;
bgneal@45 3869 }
bgneal@45 3870
bgneal@45 3871 return curLoop;
bgneal@45 3872 };
bgneal@45 3873
bgneal@45 3874 var Expr = Sizzle.selectors = {
bgneal@45 3875 order: [ "ID", "NAME", "TAG" ],
bgneal@45 3876 match: {
bgneal@45 3877 ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
bgneal@45 3878 CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
bgneal@45 3879 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
bgneal@45 3880 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
bgneal@45 3881 TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
bgneal@45 3882 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
bgneal@45 3883 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
bgneal@45 3884 PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
bgneal@45 3885 },
bgneal@45 3886 attrMap: {
bgneal@45 3887 "class": "className",
bgneal@45 3888 "for": "htmlFor"
bgneal@45 3889 },
bgneal@45 3890 attrHandle: {
bgneal@45 3891 href: function(elem){
bgneal@45 3892 return elem.getAttribute("href");
bgneal@45 3893 }
bgneal@45 3894 },
bgneal@45 3895 relative: {
bgneal@45 3896 "+": function(checkSet, part, isXML){
bgneal@45 3897 var isPartStr = typeof part === "string",
bgneal@45 3898 isTag = isPartStr && !/\W/.test(part),
bgneal@45 3899 isPartStrNotTag = isPartStr && !isTag;
bgneal@45 3900
bgneal@45 3901 if ( isTag && !isXML ) {
bgneal@45 3902 part = part.toUpperCase();
bgneal@45 3903 }
bgneal@45 3904
bgneal@45 3905 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
bgneal@45 3906 if ( (elem = checkSet[i]) ) {
bgneal@45 3907 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
bgneal@45 3908
bgneal@45 3909 checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
bgneal@45 3910 elem || false :
bgneal@45 3911 elem === part;
bgneal@45 3912 }
bgneal@45 3913 }
bgneal@45 3914
bgneal@45 3915 if ( isPartStrNotTag ) {
bgneal@45 3916 Sizzle.filter( part, checkSet, true );
bgneal@45 3917 }
bgneal@45 3918 },
bgneal@45 3919 ">": function(checkSet, part, isXML){
bgneal@45 3920 var isPartStr = typeof part === "string";
bgneal@45 3921
bgneal@45 3922 if ( isPartStr && !/\W/.test(part) ) {
bgneal@45 3923 part = isXML ? part : part.toUpperCase();
bgneal@45 3924
bgneal@45 3925 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 3926 var elem = checkSet[i];
bgneal@45 3927 if ( elem ) {
bgneal@45 3928 var parent = elem.parentNode;
bgneal@45 3929 checkSet[i] = parent.nodeName === part ? parent : false;
bgneal@45 3930 }
bgneal@45 3931 }
bgneal@45 3932 } else {
bgneal@45 3933 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 3934 var elem = checkSet[i];
bgneal@45 3935 if ( elem ) {
bgneal@45 3936 checkSet[i] = isPartStr ?
bgneal@45 3937 elem.parentNode :
bgneal@45 3938 elem.parentNode === part;
bgneal@45 3939 }
bgneal@45 3940 }
bgneal@45 3941
bgneal@45 3942 if ( isPartStr ) {
bgneal@45 3943 Sizzle.filter( part, checkSet, true );
bgneal@45 3944 }
bgneal@45 3945 }
bgneal@45 3946 },
bgneal@45 3947 "": function(checkSet, part, isXML){
bgneal@45 3948 var doneName = done++, checkFn = dirCheck;
bgneal@45 3949
bgneal@45 3950 if ( !part.match(/\W/) ) {
bgneal@45 3951 var nodeCheck = part = isXML ? part : part.toUpperCase();
bgneal@45 3952 checkFn = dirNodeCheck;
bgneal@45 3953 }
bgneal@45 3954
bgneal@45 3955 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 3956 },
bgneal@45 3957 "~": function(checkSet, part, isXML){
bgneal@45 3958 var doneName = done++, checkFn = dirCheck;
bgneal@45 3959
bgneal@45 3960 if ( typeof part === "string" && !part.match(/\W/) ) {
bgneal@45 3961 var nodeCheck = part = isXML ? part : part.toUpperCase();
bgneal@45 3962 checkFn = dirNodeCheck;
bgneal@45 3963 }
bgneal@45 3964
bgneal@45 3965 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
bgneal@45 3966 }
bgneal@45 3967 },
bgneal@45 3968 find: {
bgneal@45 3969 ID: function(match, context, isXML){
bgneal@45 3970 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 3971 var m = context.getElementById(match[1]);
bgneal@45 3972 return m ? [m] : [];
bgneal@45 3973 }
bgneal@45 3974 },
bgneal@45 3975 NAME: function(match, context, isXML){
bgneal@45 3976 if ( typeof context.getElementsByName !== "undefined" ) {
bgneal@45 3977 var ret = [], results = context.getElementsByName(match[1]);
bgneal@45 3978
bgneal@45 3979 for ( var i = 0, l = results.length; i < l; i++ ) {
bgneal@45 3980 if ( results[i].getAttribute("name") === match[1] ) {
bgneal@45 3981 ret.push( results[i] );
bgneal@45 3982 }
bgneal@45 3983 }
bgneal@45 3984
bgneal@45 3985 return ret.length === 0 ? null : ret;
bgneal@45 3986 }
bgneal@45 3987 },
bgneal@45 3988 TAG: function(match, context){
bgneal@45 3989 return context.getElementsByTagName(match[1]);
bgneal@45 3990 }
bgneal@45 3991 },
bgneal@45 3992 preFilter: {
bgneal@45 3993 CLASS: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 3994 match = " " + match[1].replace(/\\/g, "") + " ";
bgneal@45 3995
bgneal@45 3996 if ( isXML ) {
bgneal@45 3997 return match;
bgneal@45 3998 }
bgneal@45 3999
bgneal@45 4000 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
bgneal@45 4001 if ( elem ) {
bgneal@45 4002 if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
bgneal@45 4003 if ( !inplace )
bgneal@45 4004 result.push( elem );
bgneal@45 4005 } else if ( inplace ) {
bgneal@45 4006 curLoop[i] = false;
bgneal@45 4007 }
bgneal@45 4008 }
bgneal@45 4009 }
bgneal@45 4010
bgneal@45 4011 return false;
bgneal@45 4012 },
bgneal@45 4013 ID: function(match){
bgneal@45 4014 return match[1].replace(/\\/g, "");
bgneal@45 4015 },
bgneal@45 4016 TAG: function(match, curLoop){
bgneal@45 4017 for ( var i = 0; curLoop[i] === false; i++ ){}
bgneal@45 4018 return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
bgneal@45 4019 },
bgneal@45 4020 CHILD: function(match){
bgneal@45 4021 if ( match[1] == "nth" ) {
bgneal@45 4022 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
bgneal@45 4023 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
bgneal@45 4024 match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
bgneal@45 4025 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
bgneal@45 4026
bgneal@45 4027 // calculate the numbers (first)n+(last) including if they are negative
bgneal@45 4028 match[2] = (test[1] + (test[2] || 1)) - 0;
bgneal@45 4029 match[3] = test[3] - 0;
bgneal@45 4030 }
bgneal@45 4031
bgneal@45 4032 // TODO: Move to normal caching system
bgneal@45 4033 match[0] = done++;
bgneal@45 4034
bgneal@45 4035 return match;
bgneal@45 4036 },
bgneal@45 4037 ATTR: function(match, curLoop, inplace, result, not, isXML){
bgneal@45 4038 var name = match[1].replace(/\\/g, "");
bgneal@45 4039
bgneal@45 4040 if ( !isXML && Expr.attrMap[name] ) {
bgneal@45 4041 match[1] = Expr.attrMap[name];
bgneal@45 4042 }
bgneal@45 4043
bgneal@45 4044 if ( match[2] === "~=" ) {
bgneal@45 4045 match[4] = " " + match[4] + " ";
bgneal@45 4046 }
bgneal@45 4047
bgneal@45 4048 return match;
bgneal@45 4049 },
bgneal@45 4050 PSEUDO: function(match, curLoop, inplace, result, not){
bgneal@45 4051 if ( match[1] === "not" ) {
bgneal@45 4052 // If we're dealing with a complex expression, or a simple one
bgneal@45 4053 if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
bgneal@45 4054 match[3] = Sizzle(match[3], null, null, curLoop);
bgneal@45 4055 } else {
bgneal@45 4056 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
bgneal@45 4057 if ( !inplace ) {
bgneal@45 4058 result.push.apply( result, ret );
bgneal@45 4059 }
bgneal@45 4060 return false;
bgneal@45 4061 }
bgneal@45 4062 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
bgneal@45 4063 return true;
bgneal@45 4064 }
bgneal@45 4065
bgneal@45 4066 return match;
bgneal@45 4067 },
bgneal@45 4068 POS: function(match){
bgneal@45 4069 match.unshift( true );
bgneal@45 4070 return match;
bgneal@45 4071 }
bgneal@45 4072 },
bgneal@45 4073 filters: {
bgneal@45 4074 enabled: function(elem){
bgneal@45 4075 return elem.disabled === false && elem.type !== "hidden";
bgneal@45 4076 },
bgneal@45 4077 disabled: function(elem){
bgneal@45 4078 return elem.disabled === true;
bgneal@45 4079 },
bgneal@45 4080 checked: function(elem){
bgneal@45 4081 return elem.checked === true;
bgneal@45 4082 },
bgneal@45 4083 selected: function(elem){
bgneal@45 4084 // Accessing this property makes selected-by-default
bgneal@45 4085 // options in Safari work properly
bgneal@45 4086 elem.parentNode.selectedIndex;
bgneal@45 4087 return elem.selected === true;
bgneal@45 4088 },
bgneal@45 4089 parent: function(elem){
bgneal@45 4090 return !!elem.firstChild;
bgneal@45 4091 },
bgneal@45 4092 empty: function(elem){
bgneal@45 4093 return !elem.firstChild;
bgneal@45 4094 },
bgneal@45 4095 has: function(elem, i, match){
bgneal@45 4096 return !!Sizzle( match[3], elem ).length;
bgneal@45 4097 },
bgneal@45 4098 header: function(elem){
bgneal@45 4099 return /h\d/i.test( elem.nodeName );
bgneal@45 4100 },
bgneal@45 4101 text: function(elem){
bgneal@45 4102 return "text" === elem.type;
bgneal@45 4103 },
bgneal@45 4104 radio: function(elem){
bgneal@45 4105 return "radio" === elem.type;
bgneal@45 4106 },
bgneal@45 4107 checkbox: function(elem){
bgneal@45 4108 return "checkbox" === elem.type;
bgneal@45 4109 },
bgneal@45 4110 file: function(elem){
bgneal@45 4111 return "file" === elem.type;
bgneal@45 4112 },
bgneal@45 4113 password: function(elem){
bgneal@45 4114 return "password" === elem.type;
bgneal@45 4115 },
bgneal@45 4116 submit: function(elem){
bgneal@45 4117 return "submit" === elem.type;
bgneal@45 4118 },
bgneal@45 4119 image: function(elem){
bgneal@45 4120 return "image" === elem.type;
bgneal@45 4121 },
bgneal@45 4122 reset: function(elem){
bgneal@45 4123 return "reset" === elem.type;
bgneal@45 4124 },
bgneal@45 4125 button: function(elem){
bgneal@45 4126 return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
bgneal@45 4127 },
bgneal@45 4128 input: function(elem){
bgneal@45 4129 return /input|select|textarea|button/i.test(elem.nodeName);
bgneal@45 4130 }
bgneal@45 4131 },
bgneal@45 4132 setFilters: {
bgneal@45 4133 first: function(elem, i){
bgneal@45 4134 return i === 0;
bgneal@45 4135 },
bgneal@45 4136 last: function(elem, i, match, array){
bgneal@45 4137 return i === array.length - 1;
bgneal@45 4138 },
bgneal@45 4139 even: function(elem, i){
bgneal@45 4140 return i % 2 === 0;
bgneal@45 4141 },
bgneal@45 4142 odd: function(elem, i){
bgneal@45 4143 return i % 2 === 1;
bgneal@45 4144 },
bgneal@45 4145 lt: function(elem, i, match){
bgneal@45 4146 return i < match[3] - 0;
bgneal@45 4147 },
bgneal@45 4148 gt: function(elem, i, match){
bgneal@45 4149 return i > match[3] - 0;
bgneal@45 4150 },
bgneal@45 4151 nth: function(elem, i, match){
bgneal@45 4152 return match[3] - 0 == i;
bgneal@45 4153 },
bgneal@45 4154 eq: function(elem, i, match){
bgneal@45 4155 return match[3] - 0 == i;
bgneal@45 4156 }
bgneal@45 4157 },
bgneal@45 4158 filter: {
bgneal@45 4159 PSEUDO: function(elem, match, i, array){
bgneal@45 4160 var name = match[1], filter = Expr.filters[ name ];
bgneal@45 4161
bgneal@45 4162 if ( filter ) {
bgneal@45 4163 return filter( elem, i, match, array );
bgneal@45 4164 } else if ( name === "contains" ) {
bgneal@45 4165 return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
bgneal@45 4166 } else if ( name === "not" ) {
bgneal@45 4167 var not = match[3];
bgneal@45 4168
bgneal@45 4169 for ( var i = 0, l = not.length; i < l; i++ ) {
bgneal@45 4170 if ( not[i] === elem ) {
bgneal@45 4171 return false;
bgneal@45 4172 }
bgneal@45 4173 }
bgneal@45 4174
bgneal@45 4175 return true;
bgneal@45 4176 }
bgneal@45 4177 },
bgneal@45 4178 CHILD: function(elem, match){
bgneal@45 4179 var type = match[1], node = elem;
bgneal@45 4180 switch (type) {
bgneal@45 4181 case 'only':
bgneal@45 4182 case 'first':
bgneal@45 4183 while (node = node.previousSibling) {
bgneal@45 4184 if ( node.nodeType === 1 ) return false;
bgneal@45 4185 }
bgneal@45 4186 if ( type == 'first') return true;
bgneal@45 4187 node = elem;
bgneal@45 4188 case 'last':
bgneal@45 4189 while (node = node.nextSibling) {
bgneal@45 4190 if ( node.nodeType === 1 ) return false;
bgneal@45 4191 }
bgneal@45 4192 return true;
bgneal@45 4193 case 'nth':
bgneal@45 4194 var first = match[2], last = match[3];
bgneal@45 4195
bgneal@45 4196 if ( first == 1 && last == 0 ) {
bgneal@45 4197 return true;
bgneal@45 4198 }
bgneal@45 4199
bgneal@45 4200 var doneName = match[0],
bgneal@45 4201 parent = elem.parentNode;
bgneal@45 4202
bgneal@45 4203 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
bgneal@45 4204 var count = 0;
bgneal@45 4205 for ( node = parent.firstChild; node; node = node.nextSibling ) {
bgneal@45 4206 if ( node.nodeType === 1 ) {
bgneal@45 4207 node.nodeIndex = ++count;
bgneal@45 4208 }
bgneal@45 4209 }
bgneal@45 4210 parent.sizcache = doneName;
bgneal@45 4211 }
bgneal@45 4212
bgneal@45 4213 var diff = elem.nodeIndex - last;
bgneal@45 4214 if ( first == 0 ) {
bgneal@45 4215 return diff == 0;
bgneal@45 4216 } else {
bgneal@45 4217 return ( diff % first == 0 && diff / first >= 0 );
bgneal@45 4218 }
bgneal@45 4219 }
bgneal@45 4220 },
bgneal@45 4221 ID: function(elem, match){
bgneal@45 4222 return elem.nodeType === 1 && elem.getAttribute("id") === match;
bgneal@45 4223 },
bgneal@45 4224 TAG: function(elem, match){
bgneal@45 4225 return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
bgneal@45 4226 },
bgneal@45 4227 CLASS: function(elem, match){
bgneal@45 4228 return (" " + (elem.className || elem.getAttribute("class")) + " ")
bgneal@45 4229 .indexOf( match ) > -1;
bgneal@45 4230 },
bgneal@45 4231 ATTR: function(elem, match){
bgneal@45 4232 var name = match[1],
bgneal@45 4233 result = Expr.attrHandle[ name ] ?
bgneal@45 4234 Expr.attrHandle[ name ]( elem ) :
bgneal@45 4235 elem[ name ] != null ?
bgneal@45 4236 elem[ name ] :
bgneal@45 4237 elem.getAttribute( name ),
bgneal@45 4238 value = result + "",
bgneal@45 4239 type = match[2],
bgneal@45 4240 check = match[4];
bgneal@45 4241
bgneal@45 4242 return result == null ?
bgneal@45 4243 type === "!=" :
bgneal@45 4244 type === "=" ?
bgneal@45 4245 value === check :
bgneal@45 4246 type === "*=" ?
bgneal@45 4247 value.indexOf(check) >= 0 :
bgneal@45 4248 type === "~=" ?
bgneal@45 4249 (" " + value + " ").indexOf(check) >= 0 :
bgneal@45 4250 !check ?
bgneal@45 4251 value && result !== false :
bgneal@45 4252 type === "!=" ?
bgneal@45 4253 value != check :
bgneal@45 4254 type === "^=" ?
bgneal@45 4255 value.indexOf(check) === 0 :
bgneal@45 4256 type === "$=" ?
bgneal@45 4257 value.substr(value.length - check.length) === check :
bgneal@45 4258 type === "|=" ?
bgneal@45 4259 value === check || value.substr(0, check.length + 1) === check + "-" :
bgneal@45 4260 false;
bgneal@45 4261 },
bgneal@45 4262 POS: function(elem, match, i, array){
bgneal@45 4263 var name = match[2], filter = Expr.setFilters[ name ];
bgneal@45 4264
bgneal@45 4265 if ( filter ) {
bgneal@45 4266 return filter( elem, i, match, array );
bgneal@45 4267 }
bgneal@45 4268 }
bgneal@45 4269 }
bgneal@45 4270 };
bgneal@45 4271
bgneal@45 4272 var origPOS = Expr.match.POS;
bgneal@45 4273
bgneal@45 4274 for ( var type in Expr.match ) {
bgneal@45 4275 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
bgneal@45 4276 }
bgneal@45 4277
bgneal@45 4278 var makeArray = function(array, results) {
bgneal@45 4279 array = Array.prototype.slice.call( array );
bgneal@45 4280
bgneal@45 4281 if ( results ) {
bgneal@183 4282 results.push.apply( results, array );
bgneal@45 4283 return results;
bgneal@45 4284 }
bgneal@45 4285
bgneal@45 4286 return array;
bgneal@45 4287 };
bgneal@45 4288
bgneal@45 4289 // Perform a simple check to determine if the browser is capable of
bgneal@45 4290 // converting a NodeList to an array using builtin methods.
bgneal@45 4291 try {
bgneal@45 4292 Array.prototype.slice.call( document.documentElement.childNodes );
bgneal@45 4293
bgneal@45 4294 // Provide a fallback method if it does not work
bgneal@45 4295 } catch(e){
bgneal@45 4296 makeArray = function(array, results) {
bgneal@45 4297 var ret = results || [];
bgneal@45 4298
bgneal@45 4299 if ( toString.call(array) === "[object Array]" ) {
bgneal@45 4300 Array.prototype.push.apply( ret, array );
bgneal@45 4301 } else {
bgneal@45 4302 if ( typeof array.length === "number" ) {
bgneal@45 4303 for ( var i = 0, l = array.length; i < l; i++ ) {
bgneal@45 4304 ret.push( array[i] );
bgneal@45 4305 }
bgneal@45 4306 } else {
bgneal@45 4307 for ( var i = 0; array[i]; i++ ) {
bgneal@45 4308 ret.push( array[i] );
bgneal@45 4309 }
bgneal@45 4310 }
bgneal@45 4311 }
bgneal@45 4312
bgneal@45 4313 return ret;
bgneal@45 4314 };
bgneal@45 4315 }
bgneal@45 4316
bgneal@45 4317 var sortOrder;
bgneal@45 4318
bgneal@45 4319 if ( document.documentElement.compareDocumentPosition ) {
bgneal@45 4320 sortOrder = function( a, b ) {
bgneal@45 4321 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
bgneal@45 4322 if ( ret === 0 ) {
bgneal@45 4323 hasDuplicate = true;
bgneal@45 4324 }
bgneal@45 4325 return ret;
bgneal@45 4326 };
bgneal@45 4327 } else if ( "sourceIndex" in document.documentElement ) {
bgneal@45 4328 sortOrder = function( a, b ) {
bgneal@45 4329 var ret = a.sourceIndex - b.sourceIndex;
bgneal@45 4330 if ( ret === 0 ) {
bgneal@45 4331 hasDuplicate = true;
bgneal@45 4332 }
bgneal@45 4333 return ret;
bgneal@45 4334 };
bgneal@45 4335 } else if ( document.createRange ) {
bgneal@45 4336 sortOrder = function( a, b ) {
bgneal@45 4337 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
bgneal@183 4338 aRange.setStart(a, 0);
bgneal@183 4339 aRange.setEnd(a, 0);
bgneal@183 4340 bRange.setStart(b, 0);
bgneal@183 4341 bRange.setEnd(b, 0);
bgneal@45 4342 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
bgneal@45 4343 if ( ret === 0 ) {
bgneal@45 4344 hasDuplicate = true;
bgneal@45 4345 }
bgneal@45 4346 return ret;
bgneal@45 4347 };
bgneal@45 4348 }
bgneal@45 4349
bgneal@45 4350 // Check to see if the browser returns elements by name when
bgneal@45 4351 // querying by getElementById (and provide a workaround)
bgneal@45 4352 (function(){
bgneal@45 4353 // We're going to inject a fake input element with a specified name
bgneal@183 4354 var form = document.createElement("div"),
bgneal@45 4355 id = "script" + (new Date).getTime();
bgneal@183 4356 form.innerHTML = "<a name='" + id + "'/>";
bgneal@45 4357
bgneal@45 4358 // Inject it into the root element, check its status, and remove it quickly
bgneal@45 4359 var root = document.documentElement;
bgneal@45 4360 root.insertBefore( form, root.firstChild );
bgneal@45 4361
bgneal@45 4362 // The workaround has to do additional checks after a getElementById
bgneal@45 4363 // Which slows things down for other browsers (hence the branching)
bgneal@45 4364 if ( !!document.getElementById( id ) ) {
bgneal@45 4365 Expr.find.ID = function(match, context, isXML){
bgneal@45 4366 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@45 4367 var m = context.getElementById(match[1]);
bgneal@45 4368 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
bgneal@45 4369 }
bgneal@45 4370 };
bgneal@45 4371
bgneal@45 4372 Expr.filter.ID = function(elem, match){
bgneal@45 4373 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
bgneal@45 4374 return elem.nodeType === 1 && node && node.nodeValue === match;
bgneal@45 4375 };
bgneal@45 4376 }
bgneal@45 4377
bgneal@45 4378 root.removeChild( form );
bgneal@45 4379 })();
bgneal@45 4380
bgneal@45 4381 (function(){
bgneal@45 4382 // Check to see if the browser returns only elements
bgneal@45 4383 // when doing getElementsByTagName("*")
bgneal@45 4384
bgneal@45 4385 // Create a fake element
bgneal@45 4386 var div = document.createElement("div");
bgneal@45 4387 div.appendChild( document.createComment("") );
bgneal@45 4388
bgneal@45 4389 // Make sure no comments are found
bgneal@45 4390 if ( div.getElementsByTagName("*").length > 0 ) {
bgneal@45 4391 Expr.find.TAG = function(match, context){
bgneal@45 4392 var results = context.getElementsByTagName(match[1]);
bgneal@45 4393
bgneal@45 4394 // Filter out possible comments
bgneal@45 4395 if ( match[1] === "*" ) {
bgneal@45 4396 var tmp = [];
bgneal@45 4397
bgneal@45 4398 for ( var i = 0; results[i]; i++ ) {
bgneal@45 4399 if ( results[i].nodeType === 1 ) {
bgneal@45 4400 tmp.push( results[i] );
bgneal@45 4401 }
bgneal@45 4402 }
bgneal@45 4403
bgneal@45 4404 results = tmp;
bgneal@45 4405 }
bgneal@45 4406
bgneal@45 4407 return results;
bgneal@45 4408 };
bgneal@45 4409 }
bgneal@45 4410
bgneal@45 4411 // Check to see if an attribute returns normalized href attributes
bgneal@45 4412 div.innerHTML = "<a href='#'></a>";
bgneal@45 4413 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
bgneal@45 4414 div.firstChild.getAttribute("href") !== "#" ) {
bgneal@45 4415 Expr.attrHandle.href = function(elem){
bgneal@45 4416 return elem.getAttribute("href", 2);
bgneal@45 4417 };
bgneal@45 4418 }
bgneal@45 4419 })();
bgneal@45 4420
bgneal@45 4421 if ( document.querySelectorAll ) (function(){
bgneal@45 4422 var oldSizzle = Sizzle, div = document.createElement("div");
bgneal@45 4423 div.innerHTML = "<p class='TEST'></p>";
bgneal@45 4424
bgneal@45 4425 // Safari can't handle uppercase or unicode characters when
bgneal@45 4426 // in quirks mode.
bgneal@45 4427 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
bgneal@45 4428 return;
bgneal@45 4429 }
bgneal@45 4430
bgneal@45 4431 Sizzle = function(query, context, extra, seed){
bgneal@45 4432 context = context || document;
bgneal@45 4433
bgneal@45 4434 // Only use querySelectorAll on non-XML documents
bgneal@45 4435 // (ID selectors don't work in non-HTML documents)
bgneal@45 4436 if ( !seed && context.nodeType === 9 && !isXML(context) ) {
bgneal@45 4437 try {
bgneal@45 4438 return makeArray( context.querySelectorAll(query), extra );
bgneal@45 4439 } catch(e){}
bgneal@45 4440 }
bgneal@45 4441
bgneal@45 4442 return oldSizzle(query, context, extra, seed);
bgneal@45 4443 };
bgneal@45 4444
bgneal@45 4445 for ( var prop in oldSizzle ) {
bgneal@45 4446 Sizzle[ prop ] = oldSizzle[ prop ];
bgneal@45 4447 }
bgneal@45 4448 })();
bgneal@45 4449
bgneal@45 4450 if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
bgneal@45 4451 var div = document.createElement("div");
bgneal@45 4452 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
bgneal@45 4453
bgneal@45 4454 // Opera can't find a second classname (in 9.6)
bgneal@45 4455 if ( div.getElementsByClassName("e").length === 0 )
bgneal@45 4456 return;
bgneal@45 4457
bgneal@45 4458 // Safari caches class attributes, doesn't catch changes (in 3.2)
bgneal@45 4459 div.lastChild.className = "e";
bgneal@45 4460
bgneal@45 4461 if ( div.getElementsByClassName("e").length === 1 )
bgneal@45 4462 return;
bgneal@45 4463
bgneal@45 4464 Expr.order.splice(1, 0, "CLASS");
bgneal@45 4465 Expr.find.CLASS = function(match, context, isXML) {
bgneal@45 4466 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
bgneal@45 4467 return context.getElementsByClassName(match[1]);
bgneal@45 4468 }
bgneal@45 4469 };
bgneal@45 4470 })();
bgneal@45 4471
bgneal@45 4472 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 4473 var sibDir = dir == "previousSibling" && !isXML;
bgneal@45 4474 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 4475 var elem = checkSet[i];
bgneal@45 4476 if ( elem ) {
bgneal@45 4477 if ( sibDir && elem.nodeType === 1 ){
bgneal@45 4478 elem.sizcache = doneName;
bgneal@45 4479 elem.sizset = i;
bgneal@45 4480 }
bgneal@45 4481 elem = elem[dir];
bgneal@45 4482 var match = false;
bgneal@45 4483
bgneal@45 4484 while ( elem ) {
bgneal@45 4485 if ( elem.sizcache === doneName ) {
bgneal@45 4486 match = checkSet[elem.sizset];
bgneal@45 4487 break;
bgneal@45 4488 }
bgneal@45 4489
bgneal@45 4490 if ( elem.nodeType === 1 && !isXML ){
bgneal@45 4491 elem.sizcache = doneName;
bgneal@45 4492 elem.sizset = i;
bgneal@45 4493 }
bgneal@45 4494
bgneal@45 4495 if ( elem.nodeName === cur ) {
bgneal@45 4496 match = elem;
bgneal@45 4497 break;
bgneal@45 4498 }
bgneal@45 4499
bgneal@45 4500 elem = elem[dir];
bgneal@45 4501 }
bgneal@45 4502
bgneal@45 4503 checkSet[i] = match;
bgneal@45 4504 }
bgneal@45 4505 }
bgneal@45 4506 }
bgneal@45 4507
bgneal@45 4508 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@45 4509 var sibDir = dir == "previousSibling" && !isXML;
bgneal@45 4510 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@45 4511 var elem = checkSet[i];
bgneal@45 4512 if ( elem ) {
bgneal@45 4513 if ( sibDir && elem.nodeType === 1 ) {
bgneal@45 4514 elem.sizcache = doneName;
bgneal@45 4515 elem.sizset = i;
bgneal@45 4516 }
bgneal@45 4517 elem = elem[dir];
bgneal@45 4518 var match = false;
bgneal@45 4519
bgneal@45 4520 while ( elem ) {
bgneal@45 4521 if ( elem.sizcache === doneName ) {
bgneal@45 4522 match = checkSet[elem.sizset];
bgneal@45 4523 break;
bgneal@45 4524 }
bgneal@45 4525
bgneal@45 4526 if ( elem.nodeType === 1 ) {
bgneal@45 4527 if ( !isXML ) {
bgneal@45 4528 elem.sizcache = doneName;
bgneal@45 4529 elem.sizset = i;
bgneal@45 4530 }
bgneal@45 4531 if ( typeof cur !== "string" ) {
bgneal@45 4532 if ( elem === cur ) {
bgneal@45 4533 match = true;
bgneal@45 4534 break;
bgneal@45 4535 }
bgneal@45 4536
bgneal@45 4537 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
bgneal@45 4538 match = elem;
bgneal@45 4539 break;
bgneal@45 4540 }
bgneal@45 4541 }
bgneal@45 4542
bgneal@45 4543 elem = elem[dir];
bgneal@45 4544 }
bgneal@45 4545
bgneal@45 4546 checkSet[i] = match;
bgneal@45 4547 }
bgneal@45 4548 }
bgneal@45 4549 }
bgneal@45 4550
bgneal@45 4551 var contains = document.compareDocumentPosition ? function(a, b){
bgneal@45 4552 return a.compareDocumentPosition(b) & 16;
bgneal@45 4553 } : function(a, b){
bgneal@45 4554 return a !== b && (a.contains ? a.contains(b) : true);
bgneal@45 4555 };
bgneal@45 4556
bgneal@45 4557 var isXML = function(elem){
bgneal@45 4558 return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
bgneal@45 4559 !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
bgneal@45 4560 };
bgneal@45 4561
bgneal@45 4562 var posProcess = function(selector, context){
bgneal@45 4563 var tmpSet = [], later = "", match,
bgneal@45 4564 root = context.nodeType ? [context] : context;
bgneal@45 4565
bgneal@45 4566 // Position selectors must be done after the filter
bgneal@45 4567 // And so must :not(positional) so we move all PSEUDOs to the end
bgneal@45 4568 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
bgneal@45 4569 later += match[0];
bgneal@45 4570 selector = selector.replace( Expr.match.PSEUDO, "" );
bgneal@45 4571 }
bgneal@45 4572
bgneal@45 4573 selector = Expr.relative[selector] ? selector + "*" : selector;
bgneal@45 4574
bgneal@45 4575 for ( var i = 0, l = root.length; i < l; i++ ) {
bgneal@45 4576 Sizzle( selector, root[i], tmpSet );
bgneal@45 4577 }
bgneal@45 4578
bgneal@45 4579 return Sizzle.filter( later, tmpSet );
bgneal@45 4580 };
bgneal@45 4581
bgneal@45 4582 // EXPOSE
bgneal@45 4583
bgneal@45 4584 window.tinymce.dom.Sizzle = Sizzle;
bgneal@45 4585
bgneal@45 4586 })();
bgneal@183 4587
bgneal@183 4588
bgneal@45 4589 (function(tinymce) {
bgneal@45 4590 // Shorten names
bgneal@45 4591 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
bgneal@45 4592
bgneal@183 4593 tinymce.create('tinymce.dom.EventUtils', {
bgneal@183 4594 EventUtils : function() {
bgneal@183 4595 this.inits = [];
bgneal@183 4596 this.events = [];
bgneal@183 4597 },
bgneal@45 4598
bgneal@45 4599 add : function(o, n, f, s) {
bgneal@45 4600 var cb, t = this, el = t.events, r;
bgneal@45 4601
bgneal@183 4602 if (n instanceof Array) {
bgneal@183 4603 r = [];
bgneal@183 4604
bgneal@183 4605 each(n, function(n) {
bgneal@183 4606 r.push(t.add(o, n, f, s));
bgneal@183 4607 });
bgneal@183 4608
bgneal@183 4609 return r;
bgneal@183 4610 }
bgneal@183 4611
bgneal@45 4612 // Handle array
bgneal@45 4613 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@45 4614 r = [];
bgneal@45 4615
bgneal@45 4616 each(o, function(o) {
bgneal@45 4617 o = DOM.get(o);
bgneal@45 4618 r.push(t.add(o, n, f, s));
bgneal@45 4619 });
bgneal@45 4620
bgneal@45 4621 return r;
bgneal@45 4622 }
bgneal@45 4623
bgneal@45 4624 o = DOM.get(o);
bgneal@45 4625
bgneal@45 4626 if (!o)
bgneal@45 4627 return;
bgneal@45 4628
bgneal@45 4629 // Setup event callback
bgneal@45 4630 cb = function(e) {
bgneal@183 4631 // Is all events disabled
bgneal@183 4632 if (t.disabled)
bgneal@183 4633 return;
bgneal@183 4634
bgneal@45 4635 e = e || window.event;
bgneal@45 4636
bgneal@183 4637 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
bgneal@183 4638 if (e && isIE) {
bgneal@183 4639 if (!e.target)
bgneal@183 4640 e.target = e.srcElement;
bgneal@183 4641
bgneal@183 4642 // Patch in preventDefault, stopPropagation methods for W3C compatibility
bgneal@183 4643 tinymce.extend(e, t._stoppers);
bgneal@183 4644 }
bgneal@45 4645
bgneal@45 4646 if (!s)
bgneal@45 4647 return f(e);
bgneal@45 4648
bgneal@45 4649 return f.call(s, e);
bgneal@45 4650 };
bgneal@45 4651
bgneal@45 4652 if (n == 'unload') {
bgneal@45 4653 tinymce.unloads.unshift({func : cb});
bgneal@45 4654 return cb;
bgneal@45 4655 }
bgneal@45 4656
bgneal@45 4657 if (n == 'init') {
bgneal@45 4658 if (t.domLoaded)
bgneal@45 4659 cb();
bgneal@45 4660 else
bgneal@45 4661 t.inits.push(cb);
bgneal@45 4662
bgneal@45 4663 return cb;
bgneal@45 4664 }
bgneal@45 4665
bgneal@45 4666 // Store away listener reference
bgneal@45 4667 el.push({
bgneal@45 4668 obj : o,
bgneal@45 4669 name : n,
bgneal@45 4670 func : f,
bgneal@45 4671 cfunc : cb,
bgneal@45 4672 scope : s
bgneal@45 4673 });
bgneal@45 4674
bgneal@45 4675 t._add(o, n, cb);
bgneal@45 4676
bgneal@45 4677 return f;
bgneal@45 4678 },
bgneal@45 4679
bgneal@45 4680 remove : function(o, n, f) {
bgneal@45 4681 var t = this, a = t.events, s = false, r;
bgneal@45 4682
bgneal@45 4683 // Handle array
bgneal@45 4684 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@45 4685 r = [];
bgneal@45 4686
bgneal@45 4687 each(o, function(o) {
bgneal@45 4688 o = DOM.get(o);
bgneal@45 4689 r.push(t.remove(o, n, f));
bgneal@45 4690 });
bgneal@45 4691
bgneal@45 4692 return r;
bgneal@45 4693 }
bgneal@45 4694
bgneal@45 4695 o = DOM.get(o);
bgneal@45 4696
bgneal@45 4697 each(a, function(e, i) {
bgneal@45 4698 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
bgneal@45 4699 a.splice(i, 1);
bgneal@45 4700 t._remove(o, n, e.cfunc);
bgneal@45 4701 s = true;
bgneal@45 4702 return false;
bgneal@45 4703 }
bgneal@45 4704 });
bgneal@45 4705
bgneal@45 4706 return s;
bgneal@45 4707 },
bgneal@45 4708
bgneal@45 4709 clear : function(o) {
bgneal@45 4710 var t = this, a = t.events, i, e;
bgneal@45 4711
bgneal@45 4712 if (o) {
bgneal@45 4713 o = DOM.get(o);
bgneal@45 4714
bgneal@45 4715 for (i = a.length - 1; i >= 0; i--) {
bgneal@45 4716 e = a[i];
bgneal@45 4717
bgneal@45 4718 if (e.obj === o) {
bgneal@45 4719 t._remove(e.obj, e.name, e.cfunc);
bgneal@45 4720 e.obj = e.cfunc = null;
bgneal@45 4721 a.splice(i, 1);
bgneal@45 4722 }
bgneal@45 4723 }
bgneal@45 4724 }
bgneal@45 4725 },
bgneal@45 4726
bgneal@45 4727 cancel : function(e) {
bgneal@45 4728 if (!e)
bgneal@45 4729 return false;
bgneal@45 4730
bgneal@45 4731 this.stop(e);
bgneal@183 4732
bgneal@45 4733 return this.prevent(e);
bgneal@45 4734 },
bgneal@45 4735
bgneal@45 4736 stop : function(e) {
bgneal@45 4737 if (e.stopPropagation)
bgneal@45 4738 e.stopPropagation();
bgneal@45 4739 else
bgneal@45 4740 e.cancelBubble = true;
bgneal@45 4741
bgneal@45 4742 return false;
bgneal@45 4743 },
bgneal@45 4744
bgneal@45 4745 prevent : function(e) {
bgneal@45 4746 if (e.preventDefault)
bgneal@45 4747 e.preventDefault();
bgneal@45 4748 else
bgneal@45 4749 e.returnValue = false;
bgneal@45 4750
bgneal@45 4751 return false;
bgneal@45 4752 },
bgneal@45 4753
bgneal@183 4754 destroy : function() {
bgneal@183 4755 var t = this;
bgneal@45 4756
bgneal@45 4757 each(t.events, function(e, i) {
bgneal@45 4758 t._remove(e.obj, e.name, e.cfunc);
bgneal@45 4759 e.obj = e.cfunc = null;
bgneal@45 4760 });
bgneal@45 4761
bgneal@45 4762 t.events = [];
bgneal@45 4763 t = null;
bgneal@45 4764 },
bgneal@45 4765
bgneal@45 4766 _add : function(o, n, f) {
bgneal@45 4767 if (o.attachEvent)
bgneal@45 4768 o.attachEvent('on' + n, f);
bgneal@45 4769 else if (o.addEventListener)
bgneal@45 4770 o.addEventListener(n, f, false);
bgneal@45 4771 else
bgneal@45 4772 o['on' + n] = f;
bgneal@45 4773 },
bgneal@45 4774
bgneal@45 4775 _remove : function(o, n, f) {
bgneal@45 4776 if (o) {
bgneal@45 4777 try {
bgneal@45 4778 if (o.detachEvent)
bgneal@45 4779 o.detachEvent('on' + n, f);
bgneal@45 4780 else if (o.removeEventListener)
bgneal@45 4781 o.removeEventListener(n, f, false);
bgneal@45 4782 else
bgneal@45 4783 o['on' + n] = null;
bgneal@45 4784 } catch (ex) {
bgneal@45 4785 // Might fail with permission denined on IE so we just ignore that
bgneal@45 4786 }
bgneal@45 4787 }
bgneal@45 4788 },
bgneal@45 4789
bgneal@183 4790 _pageInit : function(win) {
bgneal@183 4791 var t = this;
bgneal@183 4792
bgneal@183 4793 // Keep it from running more than once
bgneal@183 4794 if (t.domLoaded)
bgneal@45 4795 return;
bgneal@45 4796
bgneal@183 4797 t.domLoaded = true;
bgneal@183 4798
bgneal@183 4799 each(t.inits, function(c) {
bgneal@45 4800 c();
bgneal@45 4801 });
bgneal@45 4802
bgneal@183 4803 t.inits = [];
bgneal@183 4804 },
bgneal@183 4805
bgneal@183 4806 _wait : function(win) {
bgneal@183 4807 var t = this, doc = win.document;
bgneal@45 4808
bgneal@45 4809 // No need since the document is already loaded
bgneal@183 4810 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
bgneal@183 4811 t.domLoaded = 1;
bgneal@45 4812 return;
bgneal@45 4813 }
bgneal@45 4814
bgneal@183 4815 // Use IE method
bgneal@183 4816 if (doc.attachEvent) {
bgneal@183 4817 doc.attachEvent("onreadystatechange", function() {
bgneal@183 4818 if (doc.readyState === "complete") {
bgneal@183 4819 doc.detachEvent("onreadystatechange", arguments.callee);
bgneal@183 4820 t._pageInit(win);
bgneal@183 4821 }
bgneal@183 4822 });
bgneal@183 4823
bgneal@183 4824 if (doc.documentElement.doScroll && win == win.top) {
bgneal@183 4825 (function() {
bgneal@183 4826 if (t.domLoaded)
bgneal@183 4827 return;
bgneal@183 4828
bgneal@183 4829 try {
bgneal@183 4830 // If IE is used, use the trick by Diego Perini
bgneal@183 4831 // http://javascript.nwbox.com/IEContentLoaded/
bgneal@183 4832 doc.documentElement.doScroll("left");
bgneal@183 4833 } catch (ex) {
bgneal@183 4834 setTimeout(arguments.callee, 0);
bgneal@183 4835 return;
bgneal@183 4836 }
bgneal@183 4837
bgneal@183 4838 t._pageInit(win);
bgneal@183 4839 })();
bgneal@183 4840 }
bgneal@183 4841 } else if (doc.addEventListener) {
bgneal@183 4842 t._add(win, 'DOMContentLoaded', function() {
bgneal@183 4843 t._pageInit(win);
bgneal@183 4844 });
bgneal@183 4845 }
bgneal@183 4846
bgneal@183 4847 t._add(win, 'load', function() {
bgneal@183 4848 t._pageInit(win);
bgneal@183 4849 });
bgneal@183 4850 },
bgneal@183 4851
bgneal@183 4852 _stoppers : {
bgneal@183 4853 preventDefault : function() {
bgneal@183 4854 this.returnValue = false;
bgneal@183 4855 },
bgneal@183 4856
bgneal@183 4857 stopPropagation : function() {
bgneal@183 4858 this.cancelBubble = true;
bgneal@45 4859 }
bgneal@45 4860 }
bgneal@183 4861 });
bgneal@183 4862
bgneal@183 4863 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
bgneal@45 4864
bgneal@45 4865 // Dispatch DOM content loaded event for IE and Safari
bgneal@183 4866 Event._wait(window);
bgneal@183 4867
bgneal@183 4868 tinymce.addUnload(function() {
bgneal@183 4869 Event.destroy();
bgneal@183 4870 });
bgneal@45 4871 })(tinymce);
bgneal@183 4872
bgneal@45 4873 (function(tinymce) {
bgneal@183 4874 tinymce.dom.Element = function(id, settings) {
bgneal@183 4875 var t = this, dom, el;
bgneal@183 4876
bgneal@183 4877 t.settings = settings = settings || {};
bgneal@183 4878 t.id = id;
bgneal@183 4879 t.dom = dom = settings.dom || tinymce.DOM;
bgneal@183 4880
bgneal@183 4881 // Only IE leaks DOM references, this is a lot faster
bgneal@183 4882 if (!tinymce.isIE)
bgneal@183 4883 el = dom.get(t.id);
bgneal@183 4884
bgneal@183 4885 tinymce.each(
bgneal@183 4886 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
bgneal@183 4887 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
bgneal@183 4888 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
bgneal@183 4889 'isHidden,setHTML,get').split(/,/)
bgneal@183 4890 , function(k) {
bgneal@45 4891 t[k] = function() {
bgneal@45 4892 var a = [id], i;
bgneal@45 4893
bgneal@45 4894 for (i = 0; i < arguments.length; i++)
bgneal@45 4895 a.push(arguments[i]);
bgneal@45 4896
bgneal@45 4897 a = dom[k].apply(dom, a);
bgneal@45 4898 t.update(k);
bgneal@45 4899
bgneal@45 4900 return a;
bgneal@45 4901 };
bgneal@45 4902 });
bgneal@183 4903
bgneal@183 4904 tinymce.extend(t, {
bgneal@183 4905 on : function(n, f, s) {
bgneal@183 4906 return tinymce.dom.Event.add(t.id, n, f, s);
bgneal@183 4907 },
bgneal@183 4908
bgneal@183 4909 getXY : function() {
bgneal@183 4910 return {
bgneal@183 4911 x : parseInt(t.getStyle('left')),
bgneal@183 4912 y : parseInt(t.getStyle('top'))
bgneal@183 4913 };
bgneal@183 4914 },
bgneal@183 4915
bgneal@183 4916 getSize : function() {
bgneal@183 4917 var n = dom.get(t.id);
bgneal@183 4918
bgneal@183 4919 return {
bgneal@183 4920 w : parseInt(t.getStyle('width') || n.clientWidth),
bgneal@183 4921 h : parseInt(t.getStyle('height') || n.clientHeight)
bgneal@183 4922 };
bgneal@183 4923 },
bgneal@183 4924
bgneal@183 4925 moveTo : function(x, y) {
bgneal@183 4926 t.setStyles({left : x, top : y});
bgneal@183 4927 },
bgneal@183 4928
bgneal@183 4929 moveBy : function(x, y) {
bgneal@183 4930 var p = t.getXY();
bgneal@183 4931
bgneal@183 4932 t.moveTo(p.x + x, p.y + y);
bgneal@183 4933 },
bgneal@183 4934
bgneal@183 4935 resizeTo : function(w, h) {
bgneal@183 4936 t.setStyles({width : w, height : h});
bgneal@183 4937 },
bgneal@183 4938
bgneal@183 4939 resizeBy : function(w, h) {
bgneal@183 4940 var s = t.getSize();
bgneal@183 4941
bgneal@183 4942 t.resizeTo(s.w + w, s.h + h);
bgneal@183 4943 },
bgneal@183 4944
bgneal@183 4945 update : function(k) {
bgneal@183 4946 var b;
bgneal@183 4947
bgneal@183 4948 if (tinymce.isIE6 && settings.blocker) {
bgneal@183 4949 k = k || '';
bgneal@183 4950
bgneal@183 4951 // Ignore getters
bgneal@183 4952 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
bgneal@183 4953 return;
bgneal@183 4954
bgneal@183 4955 // Remove blocker on remove
bgneal@183 4956 if (k == 'remove') {
bgneal@183 4957 dom.remove(t.blocker);
bgneal@183 4958 return;
bgneal@183 4959 }
bgneal@183 4960
bgneal@183 4961 if (!t.blocker) {
bgneal@183 4962 t.blocker = dom.uniqueId();
bgneal@183 4963 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
bgneal@183 4964 dom.setStyle(b, 'opacity', 0);
bgneal@183 4965 } else
bgneal@183 4966 b = dom.get(t.blocker);
bgneal@183 4967
bgneal@183 4968 dom.setStyles(b, {
bgneal@183 4969 left : t.getStyle('left', 1),
bgneal@183 4970 top : t.getStyle('top', 1),
bgneal@183 4971 width : t.getStyle('width', 1),
bgneal@183 4972 height : t.getStyle('height', 1),
bgneal@183 4973 display : t.getStyle('display', 1),
bgneal@183 4974 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
bgneal@183 4975 });
bgneal@183 4976 }
bgneal@183 4977 }
bgneal@183 4978 });
bgneal@183 4979 };
bgneal@45 4980 })(tinymce);
bgneal@183 4981
bgneal@45 4982 (function(tinymce) {
bgneal@45 4983 function trimNl(s) {
bgneal@45 4984 return s.replace(/[\n\r]+/g, '');
bgneal@45 4985 };
bgneal@45 4986
bgneal@45 4987 // Shorten names
bgneal@45 4988 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
bgneal@45 4989
bgneal@45 4990 tinymce.create('tinymce.dom.Selection', {
bgneal@45 4991 Selection : function(dom, win, serializer) {
bgneal@45 4992 var t = this;
bgneal@45 4993
bgneal@45 4994 t.dom = dom;
bgneal@45 4995 t.win = win;
bgneal@45 4996 t.serializer = serializer;
bgneal@45 4997
bgneal@45 4998 // Add events
bgneal@45 4999 each([
bgneal@45 5000 'onBeforeSetContent',
bgneal@45 5001 'onBeforeGetContent',
bgneal@45 5002 'onSetContent',
bgneal@45 5003 'onGetContent'
bgneal@45 5004 ], function(e) {
bgneal@45 5005 t[e] = new tinymce.util.Dispatcher(t);
bgneal@45 5006 });
bgneal@45 5007
bgneal@45 5008 // No W3C Range support
bgneal@45 5009 if (!t.win.getSelection)
bgneal@45 5010 t.tridentSel = new tinymce.dom.TridentSelection(t);
bgneal@45 5011
bgneal@45 5012 // Prevent leaks
bgneal@45 5013 tinymce.addUnload(t.destroy, t);
bgneal@45 5014 },
bgneal@45 5015
bgneal@45 5016 getContent : function(s) {
bgneal@45 5017 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
bgneal@45 5018
bgneal@45 5019 s = s || {};
bgneal@45 5020 wb = wa = '';
bgneal@45 5021 s.get = true;
bgneal@45 5022 s.format = s.format || 'html';
bgneal@45 5023 t.onBeforeGetContent.dispatch(t, s);
bgneal@45 5024
bgneal@45 5025 if (s.format == 'text')
bgneal@45 5026 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
bgneal@45 5027
bgneal@45 5028 if (r.cloneContents) {
bgneal@45 5029 n = r.cloneContents();
bgneal@45 5030
bgneal@45 5031 if (n)
bgneal@45 5032 e.appendChild(n);
bgneal@45 5033 } else if (is(r.item) || is(r.htmlText))
bgneal@45 5034 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
bgneal@45 5035 else
bgneal@45 5036 e.innerHTML = r.toString();
bgneal@45 5037
bgneal@45 5038 // Keep whitespace before and after
bgneal@45 5039 if (/^\s/.test(e.innerHTML))
bgneal@45 5040 wb = ' ';
bgneal@45 5041
bgneal@45 5042 if (/\s+$/.test(e.innerHTML))
bgneal@45 5043 wa = ' ';
bgneal@45 5044
bgneal@45 5045 s.getInner = true;
bgneal@45 5046
bgneal@45 5047 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
bgneal@45 5048 t.onGetContent.dispatch(t, s);
bgneal@45 5049
bgneal@45 5050 return s.content;
bgneal@45 5051 },
bgneal@45 5052
bgneal@45 5053 setContent : function(h, s) {
bgneal@45 5054 var t = this, r = t.getRng(), c, d = t.win.document;
bgneal@45 5055
bgneal@45 5056 s = s || {format : 'html'};
bgneal@45 5057 s.set = true;
bgneal@45 5058 h = s.content = t.dom.processHTML(h);
bgneal@45 5059
bgneal@45 5060 // Dispatch before set content event
bgneal@45 5061 t.onBeforeSetContent.dispatch(t, s);
bgneal@45 5062 h = s.content;
bgneal@45 5063
bgneal@45 5064 if (r.insertNode) {
bgneal@45 5065 // Make caret marker since insertNode places the caret in the beginning of text after insert
bgneal@45 5066 h += '<span id="__caret">_</span>';
bgneal@45 5067
bgneal@45 5068 // Delete and insert new node
bgneal@183 5069 if (r.startContainer == d && r.endContainer == d) {
bgneal@183 5070 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
bgneal@183 5071 d.body.innerHTML = h;
bgneal@183 5072 } else {
bgneal@183 5073 r.deleteContents();
bgneal@183 5074 r.insertNode(t.getRng().createContextualFragment(h));
bgneal@183 5075 }
bgneal@45 5076
bgneal@45 5077 // Move to caret marker
bgneal@45 5078 c = t.dom.get('__caret');
bgneal@45 5079
bgneal@45 5080 // Make sure we wrap it compleatly, Opera fails with a simple select call
bgneal@45 5081 r = d.createRange();
bgneal@45 5082 r.setStartBefore(c);
bgneal@183 5083 r.setEndBefore(c);
bgneal@45 5084 t.setRng(r);
bgneal@45 5085
bgneal@45 5086 // Remove the caret position
bgneal@45 5087 t.dom.remove('__caret');
bgneal@45 5088 } else {
bgneal@45 5089 if (r.item) {
bgneal@45 5090 // Delete content and get caret text selection
bgneal@45 5091 d.execCommand('Delete', false, null);
bgneal@45 5092 r = t.getRng();
bgneal@45 5093 }
bgneal@45 5094
bgneal@45 5095 r.pasteHTML(h);
bgneal@45 5096 }
bgneal@45 5097
bgneal@45 5098 // Dispatch set content event
bgneal@45 5099 t.onSetContent.dispatch(t, s);
bgneal@45 5100 },
bgneal@45 5101
bgneal@45 5102 getStart : function() {
bgneal@45 5103 var t = this, r = t.getRng(), e;
bgneal@45 5104
bgneal@183 5105 if (r.duplicate || r.item) {
bgneal@45 5106 if (r.item)
bgneal@45 5107 return r.item(0);
bgneal@45 5108
bgneal@45 5109 r = r.duplicate();
bgneal@45 5110 r.collapse(1);
bgneal@45 5111 e = r.parentElement();
bgneal@45 5112
bgneal@45 5113 if (e && e.nodeName == 'BODY')
bgneal@183 5114 return e.firstChild || e;
bgneal@45 5115
bgneal@45 5116 return e;
bgneal@45 5117 } else {
bgneal@45 5118 e = r.startContainer;
bgneal@45 5119
bgneal@183 5120 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@183 5121 e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)];
bgneal@183 5122
bgneal@183 5123 if (e && e.nodeType == 3)
bgneal@183 5124 return e.parentNode;
bgneal@183 5125
bgneal@183 5126 return e;
bgneal@45 5127 }
bgneal@45 5128 },
bgneal@45 5129
bgneal@45 5130 getEnd : function() {
bgneal@183 5131 var t = this, r = t.getRng(), e, eo;
bgneal@183 5132
bgneal@183 5133 if (r.duplicate || r.item) {
bgneal@45 5134 if (r.item)
bgneal@45 5135 return r.item(0);
bgneal@45 5136
bgneal@45 5137 r = r.duplicate();
bgneal@45 5138 r.collapse(0);
bgneal@45 5139 e = r.parentElement();
bgneal@45 5140
bgneal@45 5141 if (e && e.nodeName == 'BODY')
bgneal@183 5142 return e.lastChild || e;
bgneal@45 5143
bgneal@45 5144 return e;
bgneal@45 5145 } else {
bgneal@45 5146 e = r.endContainer;
bgneal@183 5147 eo = r.endOffset;
bgneal@183 5148
bgneal@183 5149 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@183 5150 e = e.childNodes[eo > 0 ? eo - 1 : eo];
bgneal@183 5151
bgneal@183 5152 if (e && e.nodeType == 3)
bgneal@183 5153 return e.parentNode;
bgneal@183 5154
bgneal@183 5155 return e;
bgneal@183 5156 }
bgneal@183 5157 },
bgneal@183 5158
bgneal@183 5159 getBookmark : function(type, normalized) {
bgneal@183 5160 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
bgneal@183 5161
bgneal@183 5162 function findIndex(name, element) {
bgneal@183 5163 var index = 0;
bgneal@183 5164
bgneal@183 5165 each(dom.select(name), function(node, i) {
bgneal@183 5166 if (node == element)
bgneal@183 5167 index = i;
bgneal@183 5168 });
bgneal@183 5169
bgneal@183 5170 return index;
bgneal@183 5171 };
bgneal@183 5172
bgneal@183 5173 if (type == 2) {
bgneal@183 5174 function getLocation() {
bgneal@183 5175 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
bgneal@183 5176
bgneal@183 5177 function getPoint(rng, start) {
bgneal@183 5178 var container = rng[start ? 'startContainer' : 'endContainer'],
bgneal@183 5179 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
bgneal@183 5180
bgneal@183 5181 if (container.nodeType == 3) {
bgneal@183 5182 if (normalized) {
bgneal@183 5183 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
bgneal@183 5184 offset += node.nodeValue.length;
bgneal@183 5185 }
bgneal@183 5186
bgneal@183 5187 point.push(offset);
bgneal@183 5188 } else {
bgneal@183 5189 childNodes = container.childNodes;
bgneal@183 5190
bgneal@183 5191 if (offset >= childNodes.length) {
bgneal@183 5192 after = 1;
bgneal@183 5193 offset = childNodes.length - 1;
bgneal@183 5194 }
bgneal@183 5195
bgneal@183 5196 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
bgneal@183 5197 }
bgneal@183 5198
bgneal@183 5199 for (; container && container != root; container = container.parentNode)
bgneal@183 5200 point.push(t.dom.nodeIndex(container, normalized));
bgneal@183 5201
bgneal@183 5202 return point;
bgneal@45 5203 };
bgneal@183 5204
bgneal@183 5205 bookmark.start = getPoint(rng, true);
bgneal@183 5206
bgneal@183 5207 if (!t.isCollapsed())
bgneal@183 5208 bookmark.end = getPoint(rng);
bgneal@183 5209
bgneal@183 5210 return bookmark;
bgneal@183 5211 };
bgneal@183 5212
bgneal@183 5213 return getLocation();
bgneal@183 5214 }
bgneal@183 5215
bgneal@183 5216 // Handle simple range
bgneal@183 5217 if (type)
bgneal@183 5218 return {rng : t.getRng()};
bgneal@183 5219
bgneal@183 5220 rng = t.getRng();
bgneal@183 5221 id = dom.uniqueId();
bgneal@183 5222 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
bgneal@183 5223 styles = 'overflow:hidden;line-height:0px';
bgneal@183 5224
bgneal@183 5225 // Explorer method
bgneal@183 5226 if (rng.duplicate || rng.item) {
bgneal@45 5227 // Text selection
bgneal@183 5228 if (!rng.item) {
bgneal@183 5229 rng2 = rng.duplicate();
bgneal@183 5230
bgneal@183 5231 // Insert start marker
bgneal@183 5232 rng.collapse();
bgneal@183 5233 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
bgneal@183 5234
bgneal@183 5235 // Insert end marker
bgneal@183 5236 if (!collapsed) {
bgneal@183 5237 rng2.collapse(false);
bgneal@183 5238 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
bgneal@183 5239 }
bgneal@183 5240 } else {
bgneal@183 5241 // Control selection
bgneal@183 5242 element = rng.item(0);
bgneal@183 5243 name = element.nodeName;
bgneal@183 5244
bgneal@183 5245 return {name : name, index : findIndex(name, element)};
bgneal@183 5246 }
bgneal@183 5247 } else {
bgneal@183 5248 element = t.getNode();
bgneal@183 5249 name = element.nodeName;
bgneal@183 5250 if (name == 'IMG')
bgneal@183 5251 return {name : name, index : findIndex(name, element)};
bgneal@183 5252
bgneal@183 5253 // W3C method
bgneal@183 5254 rng2 = rng.cloneRange();
bgneal@183 5255
bgneal@183 5256 // Insert end marker
bgneal@183 5257 if (!collapsed) {
bgneal@183 5258 rng2.collapse(false);
bgneal@183 5259 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
bgneal@183 5260 }
bgneal@183 5261
bgneal@183 5262 rng.collapse(true);
bgneal@183 5263 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
bgneal@183 5264 }
bgneal@183 5265
bgneal@183 5266 t.moveToBookmark({id : id, keep : 1});
bgneal@183 5267
bgneal@183 5268 return {id : id};
bgneal@183 5269 },
bgneal@183 5270
bgneal@183 5271 moveToBookmark : function(bookmark) {
bgneal@183 5272 var t = this, dom = t.dom, marker1, marker2, rng, root;
bgneal@183 5273
bgneal@183 5274 // Clear selection cache
bgneal@183 5275 if (t.tridentSel)
bgneal@183 5276 t.tridentSel.destroy();
bgneal@183 5277
bgneal@183 5278 if (bookmark) {
bgneal@183 5279 if (bookmark.start) {
bgneal@183 5280 rng = dom.createRng();
bgneal@183 5281 root = dom.getRoot();
bgneal@183 5282
bgneal@183 5283 function setEndPoint(start) {
bgneal@183 5284 var point = bookmark[start ? 'start' : 'end'], i, node, offset;
bgneal@183 5285
bgneal@183 5286 if (point) {
bgneal@183 5287 // Find container node
bgneal@183 5288 for (node = root, i = point.length - 1; i >= 1; i--)
bgneal@183 5289 node = node.childNodes[point[i]];
bgneal@183 5290
bgneal@183 5291 // Set offset within container node
bgneal@183 5292 if (start)
bgneal@183 5293 rng.setStart(node, point[0]);
bgneal@183 5294 else
bgneal@183 5295 rng.setEnd(node, point[0]);
bgneal@183 5296 }
bgneal@183 5297 };
bgneal@183 5298
bgneal@183 5299 setEndPoint(true);
bgneal@183 5300 setEndPoint();
bgneal@183 5301
bgneal@183 5302 t.setRng(rng);
bgneal@183 5303 } else if (bookmark.id) {
bgneal@183 5304 rng = dom.createRng();
bgneal@183 5305
bgneal@183 5306 function restoreEndPoint(suffix) {
bgneal@183 5307 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
bgneal@183 5308
bgneal@183 5309 if (marker) {
bgneal@183 5310 node = marker.parentNode;
bgneal@183 5311
bgneal@183 5312 if (suffix == 'start') {
bgneal@183 5313 if (!keep) {
bgneal@183 5314 idx = dom.nodeIndex(marker);
bgneal@183 5315 } else {
bgneal@183 5316 node = marker;
bgneal@183 5317 idx = 1;
bgneal@183 5318 }
bgneal@183 5319
bgneal@183 5320 rng.setStart(node, idx);
bgneal@183 5321 rng.setEnd(node, idx);
bgneal@183 5322 } else {
bgneal@183 5323 if (!keep) {
bgneal@183 5324 idx = dom.nodeIndex(marker);
bgneal@183 5325 } else {
bgneal@183 5326 node = marker;
bgneal@183 5327 idx = 1;
bgneal@183 5328 }
bgneal@183 5329
bgneal@183 5330 rng.setEnd(node, idx);
bgneal@183 5331 }
bgneal@183 5332
bgneal@183 5333 if (!keep) {
bgneal@183 5334 prev = marker.previousSibling;
bgneal@183 5335 next = marker.nextSibling;
bgneal@183 5336
bgneal@183 5337 // Remove all marker text nodes
bgneal@183 5338 each(tinymce.grep(marker.childNodes), function(node) {
bgneal@183 5339 if (node.nodeType == 3)
bgneal@183 5340 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
bgneal@183 5341 });
bgneal@183 5342
bgneal@183 5343 // Remove marker but keep children if for example contents where inserted into the marker
bgneal@183 5344 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
bgneal@183 5345 while (marker = dom.get(bookmark.id + '_' + suffix))
bgneal@183 5346 dom.remove(marker, 1);
bgneal@183 5347
bgneal@183 5348 // If siblings are text nodes then merge them
bgneal@183 5349 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
bgneal@183 5350 idx = prev.nodeValue.length;
bgneal@183 5351 prev.appendData(next.nodeValue);
bgneal@183 5352 dom.remove(next);
bgneal@183 5353
bgneal@183 5354 if (suffix == 'start') {
bgneal@183 5355 rng.setStart(prev, idx);
bgneal@183 5356 rng.setEnd(prev, idx);
bgneal@183 5357 } else
bgneal@183 5358 rng.setEnd(prev, idx);
bgneal@183 5359 }
bgneal@183 5360 }
bgneal@183 5361 }
bgneal@183 5362 };
bgneal@183 5363
bgneal@183 5364 // Restore start/end points
bgneal@183 5365 restoreEndPoint('start');
bgneal@183 5366 restoreEndPoint('end');
bgneal@183 5367
bgneal@183 5368 t.setRng(rng);
bgneal@183 5369 } else if (bookmark.name) {
bgneal@183 5370 t.select(dom.select(bookmark.name)[bookmark.index]);
bgneal@183 5371 } else if (bookmark.rng)
bgneal@183 5372 t.setRng(bookmark.rng);
bgneal@183 5373 }
bgneal@183 5374 },
bgneal@183 5375
bgneal@183 5376 select : function(node, content) {
bgneal@183 5377 var t = this, dom = t.dom, rng = dom.createRng(), idx;
bgneal@183 5378
bgneal@183 5379 idx = dom.nodeIndex(node);
bgneal@183 5380 rng.setStart(node.parentNode, idx);
bgneal@183 5381 rng.setEnd(node.parentNode, idx + 1);
bgneal@183 5382
bgneal@183 5383 // Find first/last text node or BR element
bgneal@183 5384 if (content) {
bgneal@183 5385 function setPoint(node, start) {
bgneal@183 5386 var walker = new tinymce.dom.TreeWalker(node, node);
bgneal@183 5387
bgneal@183 5388 do {
bgneal@183 5389 // Text node
bgneal@183 5390 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
bgneal@183 5391 if (start)
bgneal@183 5392 rng.setStart(node, 0);
bgneal@183 5393 else
bgneal@183 5394 rng.setEnd(node, node.nodeValue.length);
bgneal@183 5395
bgneal@183 5396 return;
bgneal@183 5397 }
bgneal@183 5398
bgneal@183 5399 // BR element
bgneal@183 5400 if (node.nodeName == 'BR') {
bgneal@183 5401 if (start)
bgneal@183 5402 rng.setStartBefore(node);
bgneal@183 5403 else
bgneal@183 5404 rng.setEndBefore(node);
bgneal@183 5405
bgneal@183 5406 return;
bgneal@183 5407 }
bgneal@183 5408 } while (node = (start ? walker.next() : walker.prev()));
bgneal@45 5409 };
bgneal@183 5410
bgneal@183 5411 setPoint(node, 1);
bgneal@183 5412 setPoint(node);
bgneal@183 5413 }
bgneal@183 5414
bgneal@183 5415 t.setRng(rng);
bgneal@183 5416
bgneal@183 5417 return node;
bgneal@45 5418 },
bgneal@45 5419
bgneal@45 5420 isCollapsed : function() {
bgneal@45 5421 var t = this, r = t.getRng(), s = t.getSel();
bgneal@45 5422
bgneal@45 5423 if (!r || r.item)
bgneal@45 5424 return false;
bgneal@45 5425
bgneal@183 5426 if (r.compareEndPoints)
bgneal@183 5427 return r.compareEndPoints('StartToEnd', r) === 0;
bgneal@183 5428
bgneal@183 5429 return !s || r.collapsed;
bgneal@45 5430 },
bgneal@45 5431
bgneal@45 5432 collapse : function(b) {
bgneal@45 5433 var t = this, r = t.getRng(), n;
bgneal@45 5434
bgneal@45 5435 // Control range on IE
bgneal@45 5436 if (r.item) {
bgneal@45 5437 n = r.item(0);
bgneal@45 5438 r = this.win.document.body.createTextRange();
bgneal@45 5439 r.moveToElementText(n);
bgneal@45 5440 }
bgneal@45 5441
bgneal@45 5442 r.collapse(!!b);
bgneal@45 5443 t.setRng(r);
bgneal@45 5444 },
bgneal@45 5445
bgneal@45 5446 getSel : function() {
bgneal@45 5447 var t = this, w = this.win;
bgneal@45 5448
bgneal@45 5449 return w.getSelection ? w.getSelection() : w.document.selection;
bgneal@45 5450 },
bgneal@45 5451
bgneal@45 5452 getRng : function(w3c) {
bgneal@45 5453 var t = this, s, r;
bgneal@45 5454
bgneal@45 5455 // Found tridentSel object then we need to use that one
bgneal@45 5456 if (w3c && t.tridentSel)
bgneal@45 5457 return t.tridentSel.getRangeAt(0);
bgneal@45 5458
bgneal@45 5459 try {
bgneal@45 5460 if (s = t.getSel())
bgneal@45 5461 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
bgneal@45 5462 } catch (ex) {
bgneal@45 5463 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
bgneal@45 5464 }
bgneal@45 5465
bgneal@45 5466 // No range found then create an empty one
bgneal@45 5467 // This can occur when the editor is placed in a hidden container element on Gecko
bgneal@45 5468 // Or on IE when there was an exception
bgneal@45 5469 if (!r)
bgneal@183 5470 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
bgneal@45 5471
bgneal@45 5472 return r;
bgneal@45 5473 },
bgneal@45 5474
bgneal@45 5475 setRng : function(r) {
bgneal@45 5476 var s, t = this;
bgneal@45 5477
bgneal@45 5478 if (!t.tridentSel) {
bgneal@45 5479 s = t.getSel();
bgneal@45 5480
bgneal@45 5481 if (s) {
bgneal@45 5482 s.removeAllRanges();
bgneal@45 5483 s.addRange(r);
bgneal@45 5484 }
bgneal@45 5485 } else {
bgneal@45 5486 // Is W3C Range
bgneal@45 5487 if (r.cloneRange) {
bgneal@45 5488 t.tridentSel.addRange(r);
bgneal@45 5489 return;
bgneal@45 5490 }
bgneal@45 5491
bgneal@45 5492 // Is IE specific range
bgneal@45 5493 try {
bgneal@45 5494 r.select();
bgneal@45 5495 } catch (ex) {
bgneal@45 5496 // Needed for some odd IE bug #1843306
bgneal@45 5497 }
bgneal@45 5498 }
bgneal@45 5499 },
bgneal@45 5500
bgneal@45 5501 setNode : function(n) {
bgneal@45 5502 var t = this;
bgneal@45 5503
bgneal@45 5504 t.setContent(t.dom.getOuterHTML(n));
bgneal@45 5505
bgneal@45 5506 return n;
bgneal@45 5507 },
bgneal@45 5508
bgneal@45 5509 getNode : function() {
bgneal@183 5510 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
bgneal@183 5511
bgneal@183 5512 if (rng.setStart) {
bgneal@45 5513 // Range maybe lost after the editor is made visible again
bgneal@183 5514 if (!rng)
bgneal@45 5515 return t.dom.getRoot();
bgneal@45 5516
bgneal@183 5517 elm = rng.commonAncestorContainer;
bgneal@45 5518
bgneal@45 5519 // Handle selection a image or other control like element such as anchors
bgneal@183 5520 if (!rng.collapsed) {
bgneal@183 5521 if (rng.startContainer == rng.endContainer) {
bgneal@183 5522 if (rng.startOffset - rng.endOffset < 2) {
bgneal@183 5523 if (rng.startContainer.hasChildNodes())
bgneal@183 5524 elm = rng.startContainer.childNodes[rng.startOffset];
bgneal@183 5525 }
bgneal@183 5526 }
bgneal@183 5527
bgneal@45 5528 // If the anchor node is a element instead of a text node then return this element
bgneal@183 5529 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
bgneal@183 5530 return sel.anchorNode.childNodes[sel.anchorOffset];
bgneal@183 5531 }
bgneal@183 5532
bgneal@183 5533 if (elm && elm.nodeType == 3)
bgneal@183 5534 return elm.parentNode;
bgneal@183 5535
bgneal@183 5536 return elm;
bgneal@183 5537 }
bgneal@183 5538
bgneal@183 5539 return rng.item ? rng.item(0) : rng.parentElement();
bgneal@45 5540 },
bgneal@45 5541
bgneal@45 5542 getSelectedBlocks : function(st, en) {
bgneal@45 5543 var t = this, dom = t.dom, sb, eb, n, bl = [];
bgneal@45 5544
bgneal@45 5545 sb = dom.getParent(st || t.getStart(), dom.isBlock);
bgneal@45 5546 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
bgneal@45 5547
bgneal@45 5548 if (sb)
bgneal@45 5549 bl.push(sb);
bgneal@45 5550
bgneal@45 5551 if (sb && eb && sb != eb) {
bgneal@45 5552 n = sb;
bgneal@45 5553
bgneal@45 5554 while ((n = n.nextSibling) && n != eb) {
bgneal@45 5555 if (dom.isBlock(n))
bgneal@45 5556 bl.push(n);
bgneal@45 5557 }
bgneal@45 5558 }
bgneal@45 5559
bgneal@45 5560 if (eb && sb != eb)
bgneal@45 5561 bl.push(eb);
bgneal@45 5562
bgneal@45 5563 return bl;
bgneal@45 5564 },
bgneal@45 5565
bgneal@45 5566 destroy : function(s) {
bgneal@45 5567 var t = this;
bgneal@45 5568
bgneal@45 5569 t.win = null;
bgneal@45 5570
bgneal@183 5571 if (t.tridentSel)
bgneal@183 5572 t.tridentSel.destroy();
bgneal@183 5573
bgneal@45 5574 // Manual destroy then remove unload handler
bgneal@45 5575 if (!s)
bgneal@45 5576 tinymce.removeUnload(t.destroy);
bgneal@45 5577 }
bgneal@183 5578 });
bgneal@45 5579 })(tinymce);
bgneal@183 5580
bgneal@45 5581 (function(tinymce) {
bgneal@45 5582 tinymce.create('tinymce.dom.XMLWriter', {
bgneal@45 5583 node : null,
bgneal@45 5584
bgneal@45 5585 XMLWriter : function(s) {
bgneal@45 5586 // Get XML document
bgneal@45 5587 function getXML() {
bgneal@45 5588 var i = document.implementation;
bgneal@45 5589
bgneal@45 5590 if (!i || !i.createDocument) {
bgneal@45 5591 // Try IE objects
bgneal@45 5592 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
bgneal@45 5593 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
bgneal@45 5594 } else
bgneal@45 5595 return i.createDocument('', '', null);
bgneal@45 5596 };
bgneal@45 5597
bgneal@45 5598 this.doc = getXML();
bgneal@45 5599
bgneal@45 5600 // Since Opera and WebKit doesn't escape > into &gt; we need to do it our self to normalize the output for all browsers
bgneal@45 5601 this.valid = tinymce.isOpera || tinymce.isWebKit;
bgneal@45 5602
bgneal@45 5603 this.reset();
bgneal@45 5604 },
bgneal@45 5605
bgneal@45 5606 reset : function() {
bgneal@45 5607 var t = this, d = t.doc;
bgneal@45 5608
bgneal@45 5609 if (d.firstChild)
bgneal@45 5610 d.removeChild(d.firstChild);
bgneal@45 5611
bgneal@45 5612 t.node = d.appendChild(d.createElement("html"));
bgneal@45 5613 },
bgneal@45 5614
bgneal@45 5615 writeStartElement : function(n) {
bgneal@45 5616 var t = this;
bgneal@45 5617
bgneal@45 5618 t.node = t.node.appendChild(t.doc.createElement(n));
bgneal@45 5619 },
bgneal@45 5620
bgneal@45 5621 writeAttribute : function(n, v) {
bgneal@45 5622 if (this.valid)
bgneal@45 5623 v = v.replace(/>/g, '%MCGT%');
bgneal@45 5624
bgneal@45 5625 this.node.setAttribute(n, v);
bgneal@45 5626 },
bgneal@45 5627
bgneal@45 5628 writeEndElement : function() {
bgneal@45 5629 this.node = this.node.parentNode;
bgneal@45 5630 },
bgneal@45 5631
bgneal@45 5632 writeFullEndElement : function() {
bgneal@45 5633 var t = this, n = t.node;
bgneal@45 5634
bgneal@45 5635 n.appendChild(t.doc.createTextNode(""));
bgneal@45 5636 t.node = n.parentNode;
bgneal@45 5637 },
bgneal@45 5638
bgneal@45 5639 writeText : function(v) {
bgneal@45 5640 if (this.valid)
bgneal@45 5641 v = v.replace(/>/g, '%MCGT%');
bgneal@45 5642
bgneal@45 5643 this.node.appendChild(this.doc.createTextNode(v));
bgneal@45 5644 },
bgneal@45 5645
bgneal@45 5646 writeCDATA : function(v) {
bgneal@183 5647 this.node.appendChild(this.doc.createCDATASection(v));
bgneal@45 5648 },
bgneal@45 5649
bgneal@45 5650 writeComment : function(v) {
bgneal@45 5651 // Fix for bug #2035694
bgneal@45 5652 if (tinymce.isIE)
bgneal@45 5653 v = v.replace(/^\-|\-$/g, ' ');
bgneal@45 5654
bgneal@45 5655 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
bgneal@45 5656 },
bgneal@45 5657
bgneal@45 5658 getContent : function() {
bgneal@45 5659 var h;
bgneal@45 5660
bgneal@45 5661 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
bgneal@45 5662 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
bgneal@45 5663 h = h.replace(/ ?\/>/g, ' />');
bgneal@45 5664
bgneal@45 5665 if (this.valid)
bgneal@45 5666 h = h.replace(/\%MCGT%/g, '&gt;');
bgneal@45 5667
bgneal@45 5668 return h;
bgneal@45 5669 }
bgneal@183 5670 });
bgneal@45 5671 })(tinymce);
bgneal@183 5672
bgneal@45 5673 (function(tinymce) {
bgneal@45 5674 tinymce.create('tinymce.dom.StringWriter', {
bgneal@45 5675 str : null,
bgneal@45 5676 tags : null,
bgneal@45 5677 count : 0,
bgneal@45 5678 settings : null,
bgneal@45 5679 indent : null,
bgneal@45 5680
bgneal@45 5681 StringWriter : function(s) {
bgneal@45 5682 this.settings = tinymce.extend({
bgneal@45 5683 indent_char : ' ',
bgneal@183 5684 indentation : 0
bgneal@45 5685 }, s);
bgneal@45 5686
bgneal@45 5687 this.reset();
bgneal@45 5688 },
bgneal@45 5689
bgneal@45 5690 reset : function() {
bgneal@45 5691 this.indent = '';
bgneal@45 5692 this.str = "";
bgneal@45 5693 this.tags = [];
bgneal@45 5694 this.count = 0;
bgneal@45 5695 },
bgneal@45 5696
bgneal@45 5697 writeStartElement : function(n) {
bgneal@45 5698 this._writeAttributesEnd();
bgneal@45 5699 this.writeRaw('<' + n);
bgneal@45 5700 this.tags.push(n);
bgneal@45 5701 this.inAttr = true;
bgneal@45 5702 this.count++;
bgneal@45 5703 this.elementCount = this.count;
bgneal@45 5704 },
bgneal@45 5705
bgneal@45 5706 writeAttribute : function(n, v) {
bgneal@45 5707 var t = this;
bgneal@45 5708
bgneal@45 5709 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
bgneal@45 5710 },
bgneal@45 5711
bgneal@45 5712 writeEndElement : function() {
bgneal@45 5713 var n;
bgneal@45 5714
bgneal@45 5715 if (this.tags.length > 0) {
bgneal@45 5716 n = this.tags.pop();
bgneal@45 5717
bgneal@45 5718 if (this._writeAttributesEnd(1))
bgneal@45 5719 this.writeRaw('</' + n + '>');
bgneal@45 5720
bgneal@45 5721 if (this.settings.indentation > 0)
bgneal@45 5722 this.writeRaw('\n');
bgneal@45 5723 }
bgneal@45 5724 },
bgneal@45 5725
bgneal@45 5726 writeFullEndElement : function() {
bgneal@45 5727 if (this.tags.length > 0) {
bgneal@45 5728 this._writeAttributesEnd();
bgneal@45 5729 this.writeRaw('</' + this.tags.pop() + '>');
bgneal@45 5730
bgneal@45 5731 if (this.settings.indentation > 0)
bgneal@45 5732 this.writeRaw('\n');
bgneal@45 5733 }
bgneal@45 5734 },
bgneal@45 5735
bgneal@45 5736 writeText : function(v) {
bgneal@45 5737 this._writeAttributesEnd();
bgneal@45 5738 this.writeRaw(this.encode(v));
bgneal@45 5739 this.count++;
bgneal@45 5740 },
bgneal@45 5741
bgneal@45 5742 writeCDATA : function(v) {
bgneal@45 5743 this._writeAttributesEnd();
bgneal@45 5744 this.writeRaw('<![CDATA[' + v + ']]>');
bgneal@45 5745 this.count++;
bgneal@45 5746 },
bgneal@45 5747
bgneal@45 5748 writeComment : function(v) {
bgneal@45 5749 this._writeAttributesEnd();
bgneal@45 5750 this.writeRaw('<!-- ' + v + '-->');
bgneal@45 5751 this.count++;
bgneal@45 5752 },
bgneal@45 5753
bgneal@45 5754 writeRaw : function(v) {
bgneal@45 5755 this.str += v;
bgneal@45 5756 },
bgneal@45 5757
bgneal@45 5758 encode : function(s) {
bgneal@45 5759 return s.replace(/[<>&"]/g, function(v) {
bgneal@45 5760 switch (v) {
bgneal@45 5761 case '<':
bgneal@45 5762 return '&lt;';
bgneal@45 5763
bgneal@45 5764 case '>':
bgneal@45 5765 return '&gt;';
bgneal@45 5766
bgneal@45 5767 case '&':
bgneal@45 5768 return '&amp;';
bgneal@45 5769
bgneal@45 5770 case '"':
bgneal@45 5771 return '&quot;';
bgneal@45 5772 }
bgneal@45 5773
bgneal@45 5774 return v;
bgneal@45 5775 });
bgneal@45 5776 },
bgneal@45 5777
bgneal@45 5778 getContent : function() {
bgneal@45 5779 return this.str;
bgneal@45 5780 },
bgneal@45 5781
bgneal@45 5782 _writeAttributesEnd : function(s) {
bgneal@45 5783 if (!this.inAttr)
bgneal@45 5784 return;
bgneal@45 5785
bgneal@45 5786 this.inAttr = false;
bgneal@45 5787
bgneal@45 5788 if (s && this.elementCount == this.count) {
bgneal@45 5789 this.writeRaw(' />');
bgneal@45 5790 return false;
bgneal@45 5791 }
bgneal@45 5792
bgneal@45 5793 this.writeRaw('>');
bgneal@45 5794
bgneal@45 5795 return true;
bgneal@45 5796 }
bgneal@183 5797 });
bgneal@45 5798 })(tinymce);
bgneal@183 5799
bgneal@45 5800 (function(tinymce) {
bgneal@45 5801 // Shorten names
bgneal@45 5802 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
bgneal@45 5803
bgneal@45 5804 function wildcardToRE(s) {
bgneal@45 5805 return s.replace(/([?+*])/g, '.$1');
bgneal@45 5806 };
bgneal@45 5807
bgneal@45 5808 tinymce.create('tinymce.dom.Serializer', {
bgneal@45 5809 Serializer : function(s) {
bgneal@45 5810 var t = this;
bgneal@45 5811
bgneal@45 5812 t.key = 0;
bgneal@45 5813 t.onPreProcess = new Dispatcher(t);
bgneal@45 5814 t.onPostProcess = new Dispatcher(t);
bgneal@45 5815
bgneal@45 5816 try {
bgneal@45 5817 t.writer = new tinymce.dom.XMLWriter();
bgneal@45 5818 } catch (ex) {
bgneal@45 5819 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
bgneal@45 5820 t.writer = new tinymce.dom.StringWriter();
bgneal@45 5821 }
bgneal@45 5822
bgneal@45 5823 // Default settings
bgneal@45 5824 t.settings = s = extend({
bgneal@45 5825 dom : tinymce.DOM,
bgneal@45 5826 valid_nodes : 0,
bgneal@45 5827 node_filter : 0,
bgneal@45 5828 attr_filter : 0,
bgneal@183 5829 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
bgneal@183 5830 closed : /^(br|hr|input|meta|img|link|param|area)$/,
bgneal@45 5831 entity_encoding : 'named',
bgneal@45 5832 entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
bgneal@45 5833 valid_elements : '*[*]',
bgneal@45 5834 extended_valid_elements : 0,
bgneal@45 5835 invalid_elements : 0,
bgneal@45 5836 fix_table_elements : 1,
bgneal@45 5837 fix_list_elements : true,
bgneal@45 5838 fix_content_duplication : true,
bgneal@45 5839 convert_fonts_to_spans : false,
bgneal@45 5840 font_size_classes : 0,
bgneal@45 5841 apply_source_formatting : 0,
bgneal@45 5842 indent_mode : 'simple',
bgneal@45 5843 indent_char : '\t',
bgneal@45 5844 indent_levels : 1,
bgneal@45 5845 remove_linebreaks : 1,
bgneal@45 5846 remove_redundant_brs : 1,
bgneal@45 5847 element_format : 'xhtml'
bgneal@45 5848 }, s);
bgneal@45 5849
bgneal@45 5850 t.dom = s.dom;
bgneal@183 5851 t.schema = s.schema;
bgneal@183 5852
bgneal@183 5853 // Use raw entities if no entities are defined
bgneal@183 5854 if (s.entity_encoding == 'named' && !s.entities)
bgneal@183 5855 s.entity_encoding = 'raw';
bgneal@45 5856
bgneal@45 5857 if (s.remove_redundant_brs) {
bgneal@45 5858 t.onPostProcess.add(function(se, o) {
bgneal@183 5859 // Remove single BR at end of block elements since they get rendered
bgneal@183 5860 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
bgneal@183 5861 // Check if it's a single element
bgneal@183 5862 if (/^<br \/>\s*<\//.test(a))
bgneal@183 5863 return '</' + c + '>';
bgneal@183 5864
bgneal@183 5865 return a;
bgneal@183 5866 });
bgneal@45 5867 });
bgneal@45 5868 }
bgneal@45 5869
bgneal@45 5870 // Remove XHTML element endings i.e. produce crap :) XHTML is better
bgneal@45 5871 if (s.element_format == 'html') {
bgneal@45 5872 t.onPostProcess.add(function(se, o) {
bgneal@45 5873 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
bgneal@45 5874 });
bgneal@45 5875 }
bgneal@45 5876
bgneal@45 5877 if (s.fix_list_elements) {
bgneal@45 5878 t.onPreProcess.add(function(se, o) {
bgneal@45 5879 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
bgneal@45 5880
bgneal@45 5881 function prevNode(e, n) {
bgneal@45 5882 var a = n.split(','), i;
bgneal@45 5883
bgneal@45 5884 while ((e = e.previousSibling) != null) {
bgneal@45 5885 for (i=0; i<a.length; i++) {
bgneal@45 5886 if (e.nodeName == a[i])
bgneal@45 5887 return e;
bgneal@45 5888 }
bgneal@45 5889 }
bgneal@45 5890
bgneal@45 5891 return null;
bgneal@45 5892 };
bgneal@45 5893
bgneal@45 5894 for (x=0; x<a.length; x++) {
bgneal@45 5895 nl = t.dom.select(a[x], o.node);
bgneal@45 5896
bgneal@45 5897 for (i=0; i<nl.length; i++) {
bgneal@45 5898 n = nl[i];
bgneal@45 5899 p = n.parentNode;
bgneal@45 5900
bgneal@45 5901 if (r.test(p.nodeName)) {
bgneal@45 5902 np = prevNode(n, 'LI');
bgneal@45 5903
bgneal@45 5904 if (!np) {
bgneal@45 5905 np = t.dom.create('li');
bgneal@45 5906 np.innerHTML = '&nbsp;';
bgneal@45 5907 np.appendChild(n);
bgneal@45 5908 p.insertBefore(np, p.firstChild);
bgneal@45 5909 } else
bgneal@45 5910 np.appendChild(n);
bgneal@45 5911 }
bgneal@45 5912 }
bgneal@45 5913 }
bgneal@45 5914 });
bgneal@45 5915 }
bgneal@45 5916
bgneal@45 5917 if (s.fix_table_elements) {
bgneal@45 5918 t.onPreProcess.add(function(se, o) {
bgneal@183 5919 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
bgneal@183 5920 // so Opera users with an older version will have to live with less compaible output not much we can do here
bgneal@183 5921 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
bgneal@183 5922 each(t.dom.select('p table', o.node).reverse(), function(n) {
bgneal@183 5923 var parent = t.dom.getParent(n.parentNode, 'table,p');
bgneal@183 5924
bgneal@183 5925 if (parent.nodeName != 'TABLE') {
bgneal@183 5926 try {
bgneal@183 5927 t.dom.split(parent, n);
bgneal@183 5928 } catch (ex) {
bgneal@183 5929 // IE can sometimes fire an unknown runtime error so we just ignore it
bgneal@183 5930 }
bgneal@183 5931 }
bgneal@183 5932 });
bgneal@183 5933 }
bgneal@45 5934 });
bgneal@45 5935 }
bgneal@45 5936 },
bgneal@45 5937
bgneal@45 5938 setEntities : function(s) {
bgneal@183 5939 var t = this, a, i, l = {}, v;
bgneal@45 5940
bgneal@45 5941 // No need to setup more than once
bgneal@45 5942 if (t.entityLookup)
bgneal@45 5943 return;
bgneal@45 5944
bgneal@45 5945 // Build regex and lookup array
bgneal@45 5946 a = s.split(',');
bgneal@45 5947 for (i = 0; i < a.length; i += 2) {
bgneal@45 5948 v = a[i];
bgneal@45 5949
bgneal@45 5950 // Don't add default &amp; &quot; etc.
bgneal@45 5951 if (v == 34 || v == 38 || v == 60 || v == 62)
bgneal@45 5952 continue;
bgneal@45 5953
bgneal@45 5954 l[String.fromCharCode(a[i])] = a[i + 1];
bgneal@45 5955
bgneal@45 5956 v = parseInt(a[i]).toString(16);
bgneal@183 5957 }
bgneal@183 5958
bgneal@45 5959 t.entityLookup = l;
bgneal@45 5960 },
bgneal@45 5961
bgneal@45 5962 setRules : function(s) {
bgneal@45 5963 var t = this;
bgneal@45 5964
bgneal@45 5965 t._setup();
bgneal@45 5966 t.rules = {};
bgneal@45 5967 t.wildRules = [];
bgneal@45 5968 t.validElements = {};
bgneal@45 5969
bgneal@45 5970 return t.addRules(s);
bgneal@45 5971 },
bgneal@45 5972
bgneal@45 5973 addRules : function(s) {
bgneal@45 5974 var t = this, dr;
bgneal@45 5975
bgneal@45 5976 if (!s)
bgneal@45 5977 return;
bgneal@45 5978
bgneal@45 5979 t._setup();
bgneal@45 5980
bgneal@45 5981 each(s.split(','), function(s) {
bgneal@45 5982 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
bgneal@45 5983
bgneal@45 5984 // Extend with default rules
bgneal@45 5985 if (dr)
bgneal@45 5986 at = tinymce.extend([], dr.attribs);
bgneal@45 5987
bgneal@45 5988 // Parse attributes
bgneal@45 5989 if (p.length > 1) {
bgneal@45 5990 each(p[1].split('|'), function(s) {
bgneal@45 5991 var ar = {}, i;
bgneal@45 5992
bgneal@45 5993 at = at || [];
bgneal@45 5994
bgneal@45 5995 // Parse attribute rule
bgneal@45 5996 s = s.replace(/::/g, '~');
bgneal@45 5997 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
bgneal@45 5998 s[2] = s[2].replace(/~/g, ':');
bgneal@45 5999
bgneal@45 6000 // Add required attributes
bgneal@45 6001 if (s[1] == '!') {
bgneal@45 6002 ra = ra || [];
bgneal@45 6003 ra.push(s[2]);
bgneal@45 6004 }
bgneal@45 6005
bgneal@45 6006 // Remove inherited attributes
bgneal@45 6007 if (s[1] == '-') {
bgneal@45 6008 for (i = 0; i <at.length; i++) {
bgneal@45 6009 if (at[i].name == s[2]) {
bgneal@45 6010 at.splice(i, 1);
bgneal@45 6011 return;
bgneal@45 6012 }
bgneal@45 6013 }
bgneal@45 6014 }
bgneal@45 6015
bgneal@45 6016 switch (s[3]) {
bgneal@45 6017 // Add default attrib values
bgneal@45 6018 case '=':
bgneal@45 6019 ar.defaultVal = s[4] || '';
bgneal@45 6020 break;
bgneal@45 6021
bgneal@45 6022 // Add forced attrib values
bgneal@45 6023 case ':':
bgneal@45 6024 ar.forcedVal = s[4];
bgneal@45 6025 break;
bgneal@45 6026
bgneal@45 6027 // Add validation values
bgneal@45 6028 case '<':
bgneal@45 6029 ar.validVals = s[4].split('?');
bgneal@45 6030 break;
bgneal@45 6031 }
bgneal@45 6032
bgneal@45 6033 if (/[*.?]/.test(s[2])) {
bgneal@45 6034 wat = wat || [];
bgneal@45 6035 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
bgneal@45 6036 wat.push(ar);
bgneal@45 6037 } else {
bgneal@45 6038 ar.name = s[2];
bgneal@45 6039 at.push(ar);
bgneal@45 6040 }
bgneal@45 6041
bgneal@45 6042 va.push(s[2]);
bgneal@45 6043 });
bgneal@45 6044 }
bgneal@45 6045
bgneal@45 6046 // Handle element names
bgneal@45 6047 each(tn, function(s, i) {
bgneal@45 6048 var pr = s.charAt(0), x = 1, ru = {};
bgneal@45 6049
bgneal@45 6050 // Extend with default rule data
bgneal@45 6051 if (dr) {
bgneal@45 6052 if (dr.noEmpty)
bgneal@45 6053 ru.noEmpty = dr.noEmpty;
bgneal@45 6054
bgneal@45 6055 if (dr.fullEnd)
bgneal@45 6056 ru.fullEnd = dr.fullEnd;
bgneal@45 6057
bgneal@45 6058 if (dr.padd)
bgneal@45 6059 ru.padd = dr.padd;
bgneal@45 6060 }
bgneal@45 6061
bgneal@45 6062 // Handle prefixes
bgneal@45 6063 switch (pr) {
bgneal@45 6064 case '-':
bgneal@45 6065 ru.noEmpty = true;
bgneal@45 6066 break;
bgneal@45 6067
bgneal@45 6068 case '+':
bgneal@45 6069 ru.fullEnd = true;
bgneal@45 6070 break;
bgneal@45 6071
bgneal@45 6072 case '#':
bgneal@45 6073 ru.padd = true;
bgneal@45 6074 break;
bgneal@45 6075
bgneal@45 6076 default:
bgneal@45 6077 x = 0;
bgneal@45 6078 }
bgneal@45 6079
bgneal@45 6080 tn[i] = s = s.substring(x);
bgneal@45 6081 t.validElements[s] = 1;
bgneal@45 6082
bgneal@45 6083 // Add element name or element regex
bgneal@45 6084 if (/[*.?]/.test(tn[0])) {
bgneal@45 6085 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
bgneal@45 6086 t.wildRules = t.wildRules || {};
bgneal@45 6087 t.wildRules.push(ru);
bgneal@45 6088 } else {
bgneal@45 6089 ru.name = tn[0];
bgneal@45 6090
bgneal@45 6091 // Store away default rule
bgneal@45 6092 if (tn[0] == '@')
bgneal@45 6093 dr = ru;
bgneal@45 6094
bgneal@45 6095 t.rules[s] = ru;
bgneal@45 6096 }
bgneal@45 6097
bgneal@45 6098 ru.attribs = at;
bgneal@45 6099
bgneal@45 6100 if (ra)
bgneal@45 6101 ru.requiredAttribs = ra;
bgneal@45 6102
bgneal@45 6103 if (wat) {
bgneal@45 6104 // Build valid attributes regexp
bgneal@45 6105 s = '';
bgneal@45 6106 each(va, function(v) {
bgneal@45 6107 if (s)
bgneal@45 6108 s += '|';
bgneal@45 6109
bgneal@45 6110 s += '(' + wildcardToRE(v) + ')';
bgneal@45 6111 });
bgneal@45 6112 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
bgneal@45 6113 ru.wildAttribs = wat;
bgneal@45 6114 }
bgneal@45 6115 });
bgneal@45 6116 });
bgneal@45 6117
bgneal@45 6118 // Build valid elements regexp
bgneal@45 6119 s = '';
bgneal@45 6120 each(t.validElements, function(v, k) {
bgneal@45 6121 if (s)
bgneal@45 6122 s += '|';
bgneal@45 6123
bgneal@45 6124 if (k != '@')
bgneal@45 6125 s += k;
bgneal@45 6126 });
bgneal@45 6127 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
bgneal@45 6128
bgneal@45 6129 //console.debug(t.validElementsRE.toString());
bgneal@45 6130 //console.dir(t.rules);
bgneal@45 6131 //console.dir(t.wildRules);
bgneal@45 6132 },
bgneal@45 6133
bgneal@45 6134 findRule : function(n) {
bgneal@45 6135 var t = this, rl = t.rules, i, r;
bgneal@45 6136
bgneal@45 6137 t._setup();
bgneal@45 6138
bgneal@45 6139 // Exact match
bgneal@45 6140 r = rl[n];
bgneal@45 6141 if (r)
bgneal@45 6142 return r;
bgneal@45 6143
bgneal@45 6144 // Try wildcards
bgneal@45 6145 rl = t.wildRules;
bgneal@45 6146 for (i = 0; i < rl.length; i++) {
bgneal@45 6147 if (rl[i].nameRE.test(n))
bgneal@45 6148 return rl[i];
bgneal@45 6149 }
bgneal@45 6150
bgneal@45 6151 return null;
bgneal@45 6152 },
bgneal@45 6153
bgneal@45 6154 findAttribRule : function(ru, n) {
bgneal@45 6155 var i, wa = ru.wildAttribs;
bgneal@45 6156
bgneal@45 6157 for (i = 0; i < wa.length; i++) {
bgneal@45 6158 if (wa[i].nameRE.test(n))
bgneal@45 6159 return wa[i];
bgneal@45 6160 }
bgneal@45 6161
bgneal@45 6162 return null;
bgneal@45 6163 },
bgneal@45 6164
bgneal@45 6165 serialize : function(n, o) {
bgneal@183 6166 var h, t = this, doc, oldDoc, impl, selected;
bgneal@45 6167
bgneal@45 6168 t._setup();
bgneal@45 6169 o = o || {};
bgneal@45 6170 o.format = o.format || 'html';
bgneal@45 6171 t.processObj = o;
bgneal@183 6172
bgneal@183 6173 // IE looses the selected attribute on option elements so we need to store it
bgneal@183 6174 // See: http://support.microsoft.com/kb/829907
bgneal@183 6175 if (isIE) {
bgneal@183 6176 selected = [];
bgneal@183 6177 each(n.getElementsByTagName('option'), function(n) {
bgneal@183 6178 var v = t.dom.getAttrib(n, 'selected');
bgneal@183 6179
bgneal@183 6180 selected.push(v ? v : null);
bgneal@183 6181 });
bgneal@183 6182 }
bgneal@183 6183
bgneal@45 6184 n = n.cloneNode(true);
bgneal@183 6185
bgneal@183 6186 // IE looses the selected attribute on option elements so we need to restore it
bgneal@183 6187 if (isIE) {
bgneal@183 6188 each(n.getElementsByTagName('option'), function(n, i) {
bgneal@183 6189 t.dom.setAttrib(n, 'selected', selected[i]);
bgneal@183 6190 });
bgneal@183 6191 }
bgneal@183 6192
bgneal@183 6193 // Nodes needs to be attached to something in WebKit/Opera
bgneal@183 6194 // Older builds of Opera crashes if you attach the node to an document created dynamically
bgneal@183 6195 // and since we can't feature detect a crash we need to sniff the acutal build number
bgneal@183 6196 // This fix will make DOM ranges and make Sizzle happy!
bgneal@183 6197 impl = n.ownerDocument.implementation;
bgneal@183 6198 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
bgneal@183 6199 // Create an empty HTML document
bgneal@183 6200 doc = impl.createHTMLDocument("");
bgneal@183 6201
bgneal@183 6202 // Add the element or it's children if it's a body element to the new document
bgneal@183 6203 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
bgneal@183 6204 doc.body.appendChild(doc.importNode(node, true));
bgneal@183 6205 });
bgneal@183 6206
bgneal@183 6207 // Grab first child or body element for serialization
bgneal@183 6208 if (n.nodeName != 'BODY')
bgneal@183 6209 n = doc.body.firstChild;
bgneal@183 6210 else
bgneal@183 6211 n = doc.body;
bgneal@183 6212
bgneal@183 6213 // set the new document in DOMUtils so createElement etc works
bgneal@183 6214 oldDoc = t.dom.doc;
bgneal@183 6215 t.dom.doc = doc;
bgneal@183 6216 }
bgneal@183 6217
bgneal@45 6218 t.key = '' + (parseInt(t.key) + 1);
bgneal@45 6219
bgneal@45 6220 // Pre process
bgneal@45 6221 if (!o.no_events) {
bgneal@45 6222 o.node = n;
bgneal@45 6223 t.onPreProcess.dispatch(t, o);
bgneal@45 6224 }
bgneal@45 6225
bgneal@45 6226 // Serialize HTML DOM into a string
bgneal@45 6227 t.writer.reset();
bgneal@183 6228 t._info = o;
bgneal@45 6229 t._serializeNode(n, o.getInner);
bgneal@45 6230
bgneal@45 6231 // Post process
bgneal@45 6232 o.content = t.writer.getContent();
bgneal@45 6233
bgneal@183 6234 // Restore the old document if it was changed
bgneal@183 6235 if (oldDoc)
bgneal@183 6236 t.dom.doc = oldDoc;
bgneal@183 6237
bgneal@45 6238 if (!o.no_events)
bgneal@45 6239 t.onPostProcess.dispatch(t, o);
bgneal@45 6240
bgneal@45 6241 t._postProcess(o);
bgneal@45 6242 o.node = null;
bgneal@45 6243
bgneal@45 6244 return tinymce.trim(o.content);
bgneal@45 6245 },
bgneal@45 6246
bgneal@45 6247 // Internal functions
bgneal@45 6248
bgneal@45 6249 _postProcess : function(o) {
bgneal@45 6250 var t = this, s = t.settings, h = o.content, sc = [], p;
bgneal@45 6251
bgneal@45 6252 if (o.format == 'html') {
bgneal@45 6253 // Protect some elements
bgneal@45 6254 p = t._protect({
bgneal@45 6255 content : h,
bgneal@45 6256 patterns : [
bgneal@45 6257 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
bgneal@183 6258 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
bgneal@45 6259 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
bgneal@45 6260 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
bgneal@45 6261 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
bgneal@45 6262 ]
bgneal@45 6263 });
bgneal@45 6264
bgneal@45 6265 h = p.content;
bgneal@45 6266
bgneal@45 6267 // Entity encode
bgneal@45 6268 if (s.entity_encoding !== 'raw')
bgneal@45 6269 h = t._encode(h);
bgneal@45 6270
bgneal@45 6271 // Use BR instead of &nbsp; padded P elements inside editor and use <p>&nbsp;</p> outside editor
bgneal@45 6272 /* if (o.set)
bgneal@45 6273 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
bgneal@45 6274 else
bgneal@45 6275 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
bgneal@45 6276
bgneal@45 6277 // Since Gecko and Safari keeps whitespace in the DOM we need to
bgneal@45 6278 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
bgneal@45 6279 // This process is only done when getting contents out from the editor.
bgneal@45 6280 if (!o.set) {
bgneal@45 6281 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
bgneal@45 6282 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1>&#160;</p>' : '<p$1>&nbsp;</p>');
bgneal@45 6283
bgneal@45 6284 if (s.remove_linebreaks) {
bgneal@45 6285 h = h.replace(/\r?\n|\r/g, ' ');
bgneal@45 6286 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
bgneal@45 6287 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
bgneal@45 6288 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
bgneal@45 6289 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
bgneal@45 6290 h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
bgneal@45 6291 }
bgneal@45 6292
bgneal@45 6293 // Simple indentation
bgneal@45 6294 if (s.apply_source_formatting && s.indent_mode == 'simple') {
bgneal@45 6295 // Add line breaks before and after block elements
bgneal@45 6296 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
bgneal@45 6297 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
bgneal@45 6298 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
bgneal@45 6299 h = h.replace(/\n\n/g, '\n');
bgneal@45 6300 }
bgneal@45 6301 }
bgneal@45 6302
bgneal@45 6303 h = t._unprotect(h, p);
bgneal@45 6304
bgneal@45 6305 // Restore CDATA sections
bgneal@45 6306 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
bgneal@45 6307
bgneal@45 6308 // Restore the \u00a0 character if raw mode is enabled
bgneal@45 6309 if (s.entity_encoding == 'raw')
bgneal@45 6310 h = h.replace(/<p>&nbsp;<\/p>|<p([^>]+)>&nbsp;<\/p>/g, '<p$1>\u00a0</p>');
bgneal@183 6311
bgneal@183 6312 // Restore noscript elements
bgneal@183 6313 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
bgneal@183 6314 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
bgneal@183 6315 });
bgneal@45 6316 }
bgneal@45 6317
bgneal@45 6318 o.content = h;
bgneal@45 6319 },
bgneal@45 6320
bgneal@183 6321 _serializeNode : function(n, inner) {
bgneal@183 6322 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;
bgneal@45 6323
bgneal@45 6324 if (!s.node_filter || s.node_filter(n)) {
bgneal@45 6325 switch (n.nodeType) {
bgneal@45 6326 case 1: // Element
bgneal@183 6327 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
bgneal@45 6328 return;
bgneal@45 6329
bgneal@183 6330 iv = keep = false;
bgneal@45 6331 hc = n.hasChildNodes();
bgneal@183 6332 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
bgneal@183 6333
bgneal@183 6334 // Get internal type
bgneal@183 6335 type = n.getAttribute('_mce_type');
bgneal@183 6336 if (type) {
bgneal@183 6337 if (!t._info.cleanup) {
bgneal@183 6338 iv = true;
bgneal@183 6339 return;
bgneal@183 6340 } else
bgneal@183 6341 keep = 1;
bgneal@183 6342 }
bgneal@45 6343
bgneal@45 6344 // Add correct prefix on IE
bgneal@45 6345 if (isIE) {
bgneal@45 6346 if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
bgneal@45 6347 nn = n.scopeName + ':' + nn;
bgneal@45 6348 }
bgneal@45 6349
bgneal@45 6350 // Remove mce prefix on IE needed for the abbr element
bgneal@45 6351 if (nn.indexOf('mce:') === 0)
bgneal@45 6352 nn = nn.substring(4);
bgneal@45 6353
bgneal@45 6354 // Check if valid
bgneal@183 6355 if (!keep) {
bgneal@183 6356 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
bgneal@183 6357 iv = true;
bgneal@183 6358 break;
bgneal@183 6359 }
bgneal@45 6360 }
bgneal@45 6361
bgneal@45 6362 if (isIE) {
bgneal@45 6363 // Fix IE content duplication (DOM can have multiple copies of the same node)
bgneal@45 6364 if (s.fix_content_duplication) {
bgneal@183 6365 if (n._mce_serialized == t.key)
bgneal@45 6366 return;
bgneal@45 6367
bgneal@183 6368 n._mce_serialized = t.key;
bgneal@45 6369 }
bgneal@45 6370
bgneal@45 6371 // IE sometimes adds a / infront of the node name
bgneal@45 6372 if (nn.charAt(0) == '/')
bgneal@45 6373 nn = nn.substring(1);
bgneal@45 6374 } else if (isGecko) {
bgneal@45 6375 // Ignore br elements
bgneal@45 6376 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
bgneal@45 6377 return;
bgneal@45 6378 }
bgneal@45 6379
bgneal@45 6380 // Check if valid child
bgneal@183 6381 if (s.validate_children) {
bgneal@183 6382 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
bgneal@183 6383 iv = true;
bgneal@183 6384 break;
bgneal@45 6385 }
bgneal@45 6386
bgneal@45 6387 t.elementName = nn;
bgneal@45 6388 }
bgneal@45 6389
bgneal@45 6390 ru = t.findRule(nn);
bgneal@183 6391
bgneal@183 6392 // No valid rule for this element could be found then skip it
bgneal@183 6393 if (!ru) {
bgneal@183 6394 iv = true;
bgneal@183 6395 break;
bgneal@183 6396 }
bgneal@183 6397
bgneal@45 6398 nn = ru.name || nn;
bgneal@183 6399 closed = s.closed.test(nn);
bgneal@45 6400
bgneal@45 6401 // Skip empty nodes or empty node name in IE
bgneal@45 6402 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
bgneal@45 6403 iv = true;
bgneal@45 6404 break;
bgneal@45 6405 }
bgneal@45 6406
bgneal@45 6407 // Check required
bgneal@45 6408 if (ru.requiredAttribs) {
bgneal@45 6409 a = ru.requiredAttribs;
bgneal@45 6410
bgneal@45 6411 for (i = a.length - 1; i >= 0; i--) {
bgneal@45 6412 if (this.dom.getAttrib(n, a[i]) !== '')
bgneal@45 6413 break;
bgneal@45 6414 }
bgneal@45 6415
bgneal@45 6416 // None of the required was there
bgneal@45 6417 if (i == -1) {
bgneal@45 6418 iv = true;
bgneal@45 6419 break;
bgneal@45 6420 }
bgneal@45 6421 }
bgneal@45 6422
bgneal@45 6423 w.writeStartElement(nn);
bgneal@45 6424
bgneal@45 6425 // Add ordered attributes
bgneal@45 6426 if (ru.attribs) {
bgneal@45 6427 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
bgneal@45 6428 a = at[i];
bgneal@45 6429 v = t._getAttrib(n, a);
bgneal@45 6430
bgneal@45 6431 if (v !== null)
bgneal@45 6432 w.writeAttribute(a.name, v);
bgneal@45 6433 }
bgneal@45 6434 }
bgneal@45 6435
bgneal@45 6436 // Add wild attributes
bgneal@45 6437 if (ru.validAttribsRE) {
bgneal@45 6438 at = t.dom.getAttribs(n);
bgneal@45 6439 for (i=at.length-1; i>-1; i--) {
bgneal@45 6440 no = at[i];
bgneal@45 6441
bgneal@45 6442 if (no.specified) {
bgneal@45 6443 a = no.nodeName.toLowerCase();
bgneal@45 6444
bgneal@45 6445 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
bgneal@45 6446 continue;
bgneal@45 6447
bgneal@45 6448 ar = t.findAttribRule(ru, a);
bgneal@45 6449 v = t._getAttrib(n, ar, a);
bgneal@45 6450
bgneal@45 6451 if (v !== null)
bgneal@45 6452 w.writeAttribute(a, v);
bgneal@45 6453 }
bgneal@45 6454 }
bgneal@45 6455 }
bgneal@45 6456
bgneal@183 6457 // Keep type attribute
bgneal@183 6458 if (type && keep)
bgneal@183 6459 w.writeAttribute('_mce_type', type);
bgneal@183 6460
bgneal@183 6461 // Write text from script
bgneal@183 6462 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
bgneal@183 6463 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
bgneal@183 6464 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
bgneal@183 6465 hc = false;
bgneal@183 6466 break;
bgneal@183 6467 }
bgneal@183 6468
bgneal@45 6469 // Padd empty nodes with a &nbsp;
bgneal@45 6470 if (ru.padd) {
bgneal@45 6471 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
bgneal@45 6472 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
bgneal@183 6473 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
bgneal@45 6474 w.writeText('\u00a0');
bgneal@45 6475 } else if (!hc)
bgneal@45 6476 w.writeText('\u00a0'); // No children then padd it
bgneal@45 6477 }
bgneal@45 6478
bgneal@45 6479 break;
bgneal@45 6480
bgneal@45 6481 case 3: // Text
bgneal@45 6482 // Check if valid child
bgneal@183 6483 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
bgneal@183 6484 return;
bgneal@45 6485
bgneal@45 6486 return w.writeText(n.nodeValue);
bgneal@45 6487
bgneal@45 6488 case 4: // CDATA
bgneal@45 6489 return w.writeCDATA(n.nodeValue);
bgneal@45 6490
bgneal@45 6491 case 8: // Comment
bgneal@45 6492 return w.writeComment(n.nodeValue);
bgneal@45 6493 }
bgneal@45 6494 } else if (n.nodeType == 1)
bgneal@45 6495 hc = n.hasChildNodes();
bgneal@45 6496
bgneal@183 6497 if (hc && !closed) {
bgneal@45 6498 cn = n.firstChild;
bgneal@45 6499
bgneal@45 6500 while (cn) {
bgneal@45 6501 t._serializeNode(cn);
bgneal@45 6502 t.elementName = nn;
bgneal@45 6503 cn = cn.nextSibling;
bgneal@45 6504 }
bgneal@45 6505 }
bgneal@45 6506
bgneal@45 6507 // Write element end
bgneal@45 6508 if (!iv) {
bgneal@183 6509 if (!closed)
bgneal@45 6510 w.writeFullEndElement();
bgneal@45 6511 else
bgneal@45 6512 w.writeEndElement();
bgneal@45 6513 }
bgneal@45 6514 },
bgneal@45 6515
bgneal@45 6516 _protect : function(o) {
bgneal@45 6517 var t = this;
bgneal@45 6518
bgneal@45 6519 o.items = o.items || [];
bgneal@45 6520
bgneal@45 6521 function enc(s) {
bgneal@45 6522 return s.replace(/[\r\n\\]/g, function(c) {
bgneal@45 6523 if (c === '\n')
bgneal@45 6524 return '\\n';
bgneal@45 6525 else if (c === '\\')
bgneal@45 6526 return '\\\\';
bgneal@45 6527
bgneal@45 6528 return '\\r';
bgneal@45 6529 });
bgneal@45 6530 };
bgneal@45 6531
bgneal@45 6532 function dec(s) {
bgneal@45 6533 return s.replace(/\\[\\rn]/g, function(c) {
bgneal@45 6534 if (c === '\\n')
bgneal@45 6535 return '\n';
bgneal@45 6536 else if (c === '\\\\')
bgneal@45 6537 return '\\';
bgneal@45 6538
bgneal@45 6539 return '\r';
bgneal@45 6540 });
bgneal@45 6541 };
bgneal@45 6542
bgneal@45 6543 each(o.patterns, function(p) {
bgneal@45 6544 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
bgneal@45 6545 b = dec(b);
bgneal@45 6546
bgneal@45 6547 if (p.encode)
bgneal@45 6548 b = t._encode(b);
bgneal@45 6549
bgneal@45 6550 o.items.push(b);
bgneal@45 6551 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
bgneal@45 6552 }));
bgneal@45 6553 });
bgneal@45 6554
bgneal@45 6555 return o;
bgneal@45 6556 },
bgneal@45 6557
bgneal@45 6558 _unprotect : function(h, o) {
bgneal@45 6559 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
bgneal@45 6560 return o.items[parseInt(b)];
bgneal@45 6561 });
bgneal@45 6562
bgneal@45 6563 o.items = [];
bgneal@45 6564
bgneal@45 6565 return h;
bgneal@45 6566 },
bgneal@45 6567
bgneal@45 6568 _encode : function(h) {
bgneal@45 6569 var t = this, s = t.settings, l;
bgneal@45 6570
bgneal@45 6571 // Entity encode
bgneal@45 6572 if (s.entity_encoding !== 'raw') {
bgneal@45 6573 if (s.entity_encoding.indexOf('named') != -1) {
bgneal@45 6574 t.setEntities(s.entities);
bgneal@45 6575 l = t.entityLookup;
bgneal@45 6576
bgneal@183 6577 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@45 6578 var v;
bgneal@45 6579
bgneal@45 6580 if (v = l[a])
bgneal@45 6581 a = '&' + v + ';';
bgneal@45 6582
bgneal@45 6583 return a;
bgneal@45 6584 });
bgneal@45 6585 }
bgneal@45 6586
bgneal@45 6587 if (s.entity_encoding.indexOf('numeric') != -1) {
bgneal@45 6588 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
bgneal@45 6589 return '&#' + a.charCodeAt(0) + ';';
bgneal@45 6590 });
bgneal@45 6591 }
bgneal@45 6592 }
bgneal@45 6593
bgneal@45 6594 return h;
bgneal@45 6595 },
bgneal@45 6596
bgneal@45 6597 _setup : function() {
bgneal@45 6598 var t = this, s = this.settings;
bgneal@45 6599
bgneal@45 6600 if (t.done)
bgneal@45 6601 return;
bgneal@45 6602
bgneal@45 6603 t.done = 1;
bgneal@45 6604
bgneal@45 6605 t.setRules(s.valid_elements);
bgneal@45 6606 t.addRules(s.extended_valid_elements);
bgneal@45 6607
bgneal@45 6608 if (s.invalid_elements)
bgneal@45 6609 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
bgneal@45 6610
bgneal@45 6611 if (s.attrib_value_filter)
bgneal@45 6612 t.attribValueFilter = s.attribValueFilter;
bgneal@45 6613 },
bgneal@45 6614
bgneal@45 6615 _getAttrib : function(n, a, na) {
bgneal@45 6616 var i, v;
bgneal@45 6617
bgneal@45 6618 na = na || a.name;
bgneal@45 6619
bgneal@45 6620 if (a.forcedVal && (v = a.forcedVal)) {
bgneal@45 6621 if (v === '{$uid}')
bgneal@45 6622 return this.dom.uniqueId();
bgneal@45 6623
bgneal@45 6624 return v;
bgneal@45 6625 }
bgneal@45 6626
bgneal@45 6627 v = this.dom.getAttrib(n, na);
bgneal@45 6628
bgneal@45 6629 switch (na) {
bgneal@45 6630 case 'rowspan':
bgneal@45 6631 case 'colspan':
bgneal@45 6632 // Whats the point? Remove usless attribute value
bgneal@45 6633 if (v == '1')
bgneal@45 6634 v = '';
bgneal@45 6635
bgneal@45 6636 break;
bgneal@45 6637 }
bgneal@45 6638
bgneal@45 6639 if (this.attribValueFilter)
bgneal@45 6640 v = this.attribValueFilter(na, v, n);
bgneal@45 6641
bgneal@45 6642 if (a.validVals) {
bgneal@45 6643 for (i = a.validVals.length - 1; i >= 0; i--) {
bgneal@45 6644 if (v == a.validVals[i])
bgneal@45 6645 break;
bgneal@45 6646 }
bgneal@45 6647
bgneal@45 6648 if (i == -1)
bgneal@45 6649 return null;
bgneal@45 6650 }
bgneal@45 6651
bgneal@45 6652 if (v === '' && typeof(a.defaultVal) != 'undefined') {
bgneal@45 6653 v = a.defaultVal;
bgneal@45 6654
bgneal@45 6655 if (v === '{$uid}')
bgneal@45 6656 return this.dom.uniqueId();
bgneal@45 6657
bgneal@45 6658 return v;
bgneal@45 6659 } else {
bgneal@45 6660 // Remove internal mceItemXX classes when content is extracted from editor
bgneal@45 6661 if (na == 'class' && this.processObj.get)
bgneal@45 6662 v = v.replace(/\s?mceItem\w+\s?/g, '');
bgneal@45 6663 }
bgneal@45 6664
bgneal@45 6665 if (v === '')
bgneal@45 6666 return null;
bgneal@45 6667
bgneal@45 6668
bgneal@45 6669 return v;
bgneal@45 6670 }
bgneal@183 6671 });
bgneal@45 6672 })(tinymce);
bgneal@183 6673
bgneal@45 6674 (function(tinymce) {
bgneal@183 6675 tinymce.dom.ScriptLoader = function(settings) {
bgneal@183 6676 var QUEUED = 0,
bgneal@183 6677 LOADING = 1,
bgneal@183 6678 LOADED = 2,
bgneal@183 6679 states = {},
bgneal@183 6680 queue = [],
bgneal@183 6681 scriptLoadedCallbacks = {},
bgneal@183 6682 queueLoadedCallbacks = [],
bgneal@183 6683 loading = 0,
bgneal@183 6684 undefined;
bgneal@183 6685
bgneal@183 6686 function loadScript(url, callback) {
bgneal@183 6687 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
bgneal@183 6688
bgneal@183 6689 // Execute callback when script is loaded
bgneal@183 6690 function done() {
bgneal@183 6691 dom.remove(id);
bgneal@183 6692
bgneal@183 6693 if (elm)
bgneal@183 6694 elm.onreadystatechange = elm.onload = elm = null;
bgneal@183 6695
bgneal@183 6696 callback();
bgneal@183 6697 };
bgneal@183 6698
bgneal@183 6699 id = dom.uniqueId();
bgneal@183 6700
bgneal@183 6701 if (tinymce.isIE6) {
bgneal@183 6702 uri = new tinymce.util.URI(url);
bgneal@183 6703 loc = location;
bgneal@183 6704
bgneal@183 6705 // If script is from same domain and we
bgneal@183 6706 // use IE 6 then use XHR since it's more reliable
bgneal@183 6707 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
bgneal@45 6708 tinymce.util.XHR.send({
bgneal@183 6709 url : tinymce._addVer(uri.getURI()),
bgneal@183 6710 success : function(content) {
bgneal@183 6711 // Create new temp script element
bgneal@183 6712 var script = dom.create('script', {
bgneal@183 6713 type : 'text/javascript'
bgneal@183 6714 });
bgneal@183 6715
bgneal@183 6716 // Evaluate script in global scope
bgneal@183 6717 script.text = content;
bgneal@183 6718 document.getElementsByTagName('head')[0].appendChild(script);
bgneal@183 6719 dom.remove(script);
bgneal@183 6720
bgneal@183 6721 done();
bgneal@45 6722 }
bgneal@45 6723 });
bgneal@183 6724
bgneal@183 6725 return;
bgneal@183 6726 }
bgneal@183 6727 }
bgneal@183 6728
bgneal@183 6729 // Create new script element
bgneal@183 6730 elm = dom.create('script', {
bgneal@183 6731 id : id,
bgneal@183 6732 type : 'text/javascript',
bgneal@183 6733 src : tinymce._addVer(url)
bgneal@183 6734 });
bgneal@183 6735
bgneal@183 6736 // Add onload and readystate listeners
bgneal@183 6737 elm.onload = done;
bgneal@183 6738 elm.onreadystatechange = function() {
bgneal@183 6739 var state = elm.readyState;
bgneal@183 6740
bgneal@183 6741 // Loaded state is passed on IE 6 however there
bgneal@183 6742 // are known issues with this method but we can't use
bgneal@183 6743 // XHR in a cross domain loading
bgneal@183 6744 if (state == 'complete' || state == 'loaded')
bgneal@183 6745 done();
bgneal@45 6746 };
bgneal@45 6747
bgneal@183 6748 // Most browsers support this feature so we report errors
bgneal@183 6749 // for those at least to help users track their missing plugins etc
bgneal@183 6750 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
bgneal@183 6751 /*elm.onerror = function() {
bgneal@183 6752 alert('Failed to load: ' + url);
bgneal@183 6753 };*/
bgneal@183 6754
bgneal@183 6755 // Add script to document
bgneal@183 6756 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
bgneal@183 6757 };
bgneal@183 6758
bgneal@183 6759 this.isDone = function(url) {
bgneal@183 6760 return states[url] == LOADED;
bgneal@183 6761 };
bgneal@183 6762
bgneal@183 6763 this.markDone = function(url) {
bgneal@183 6764 states[url] = LOADED;
bgneal@183 6765 };
bgneal@183 6766
bgneal@183 6767 this.add = this.load = function(url, callback, scope) {
bgneal@183 6768 var item, state = states[url];
bgneal@183 6769
bgneal@183 6770 // Add url to load queue
bgneal@183 6771 if (state == undefined) {
bgneal@183 6772 queue.push(url);
bgneal@183 6773 states[url] = QUEUED;
bgneal@183 6774 }
bgneal@183 6775
bgneal@183 6776 if (callback) {
bgneal@183 6777 // Store away callback for later execution
bgneal@183 6778 if (!scriptLoadedCallbacks[url])
bgneal@183 6779 scriptLoadedCallbacks[url] = [];
bgneal@183 6780
bgneal@183 6781 scriptLoadedCallbacks[url].push({
bgneal@183 6782 func : callback,
bgneal@183 6783 scope : scope || this
bgneal@183 6784 });
bgneal@183 6785 }
bgneal@183 6786 };
bgneal@183 6787
bgneal@183 6788 this.loadQueue = function(callback, scope) {
bgneal@183 6789 this.loadScripts(queue, callback, scope);
bgneal@183 6790 };
bgneal@183 6791
bgneal@183 6792 this.loadScripts = function(scripts, callback, scope) {
bgneal@183 6793 var loadScripts;
bgneal@183 6794
bgneal@183 6795 function execScriptLoadedCallbacks(url) {
bgneal@183 6796 // Execute URL callback functions
bgneal@183 6797 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
bgneal@183 6798 callback.func.call(callback.scope);
bgneal@183 6799 });
bgneal@183 6800
bgneal@183 6801 scriptLoadedCallbacks[url] = undefined;
bgneal@183 6802 };
bgneal@183 6803
bgneal@183 6804 queueLoadedCallbacks.push({
bgneal@183 6805 func : callback,
bgneal@183 6806 scope : scope || this
bgneal@183 6807 });
bgneal@183 6808
bgneal@183 6809 loadScripts = function() {
bgneal@183 6810 var loadingScripts = tinymce.grep(scripts);
bgneal@183 6811
bgneal@183 6812 // Current scripts has been handled
bgneal@183 6813 scripts.length = 0;
bgneal@183 6814
bgneal@183 6815 // Load scripts that needs to be loaded
bgneal@183 6816 tinymce.each(loadingScripts, function(url) {
bgneal@183 6817 // Script is already loaded then execute script callbacks directly
bgneal@183 6818 if (states[url] == LOADED) {
bgneal@183 6819 execScriptLoadedCallbacks(url);
bgneal@183 6820 return;
bgneal@183 6821 }
bgneal@183 6822
bgneal@183 6823 // Is script not loading then start loading it
bgneal@183 6824 if (states[url] != LOADING) {
bgneal@183 6825 states[url] = LOADING;
bgneal@183 6826 loading++;
bgneal@183 6827
bgneal@183 6828 loadScript(url, function() {
bgneal@183 6829 states[url] = LOADED;
bgneal@183 6830 loading--;
bgneal@183 6831
bgneal@183 6832 execScriptLoadedCallbacks(url);
bgneal@183 6833
bgneal@183 6834 // Load more scripts if they where added by the recently loaded script
bgneal@183 6835 loadScripts();
bgneal@183 6836 });
bgneal@183 6837 }
bgneal@183 6838 });
bgneal@183 6839
bgneal@183 6840 // No scripts are currently loading then execute all pending queue loaded callbacks
bgneal@183 6841 if (!loading) {
bgneal@183 6842 tinymce.each(queueLoadedCallbacks, function(callback) {
bgneal@183 6843 callback.func.call(callback.scope);
bgneal@45 6844 });
bgneal@183 6845
bgneal@183 6846 queueLoadedCallbacks.length = 0;
bgneal@183 6847 }
bgneal@45 6848 };
bgneal@45 6849
bgneal@183 6850 loadScripts();
bgneal@183 6851 };
bgneal@183 6852 };
bgneal@45 6853
bgneal@45 6854 // Global script loader
bgneal@45 6855 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
bgneal@45 6856 })(tinymce);
bgneal@183 6857
bgneal@183 6858 tinymce.dom.TreeWalker = function(start_node, root_node) {
bgneal@183 6859 var node = start_node;
bgneal@183 6860
bgneal@183 6861 function findSibling(node, start_name, sibling_name, shallow) {
bgneal@183 6862 var sibling, parent;
bgneal@183 6863
bgneal@183 6864 if (node) {
bgneal@183 6865 // Walk into nodes if it has a start
bgneal@183 6866 if (!shallow && node[start_name])
bgneal@183 6867 return node[start_name];
bgneal@183 6868
bgneal@183 6869 // Return the sibling if it has one
bgneal@183 6870 if (node != root_node) {
bgneal@183 6871 sibling = node[sibling_name];
bgneal@183 6872 if (sibling)
bgneal@183 6873 return sibling;
bgneal@183 6874
bgneal@183 6875 // Walk up the parents to look for siblings
bgneal@183 6876 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
bgneal@183 6877 sibling = parent[sibling_name];
bgneal@183 6878 if (sibling)
bgneal@183 6879 return sibling;
bgneal@183 6880 }
bgneal@183 6881 }
bgneal@183 6882 }
bgneal@183 6883 };
bgneal@183 6884
bgneal@183 6885 this.current = function() {
bgneal@183 6886 return node;
bgneal@183 6887 };
bgneal@183 6888
bgneal@183 6889 this.next = function(shallow) {
bgneal@183 6890 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
bgneal@183 6891 };
bgneal@183 6892
bgneal@183 6893 this.prev = function(shallow) {
bgneal@183 6894 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
bgneal@183 6895 };
bgneal@183 6896 };
bgneal@183 6897
bgneal@183 6898 (function() {
bgneal@183 6899 var transitional = {};
bgneal@183 6900
bgneal@183 6901 function unpack(lookup, data) {
bgneal@183 6902 var key;
bgneal@183 6903
bgneal@183 6904 function replace(value) {
bgneal@183 6905 return value.replace(/[A-Z]+/g, function(key) {
bgneal@183 6906 return replace(lookup[key]);
bgneal@183 6907 });
bgneal@183 6908 };
bgneal@183 6909
bgneal@183 6910 // Unpack lookup
bgneal@183 6911 for (key in lookup) {
bgneal@183 6912 if (lookup.hasOwnProperty(key))
bgneal@183 6913 lookup[key] = replace(lookup[key]);
bgneal@183 6914 }
bgneal@183 6915
bgneal@183 6916 // Unpack and parse data into object map
bgneal@183 6917 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
bgneal@183 6918 var i, map = {};
bgneal@183 6919
bgneal@183 6920 children = children.split(/\|/);
bgneal@183 6921
bgneal@183 6922 for (i = children.length - 1; i >= 0; i--)
bgneal@183 6923 map[children[i]] = 1;
bgneal@183 6924
bgneal@183 6925 transitional[name] = map;
bgneal@183 6926 });
bgneal@183 6927 };
bgneal@183 6928
bgneal@183 6929 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
bgneal@183 6930 // we will later include the attributes here and use it as a default for valid elements but it
bgneal@183 6931 // requires us to rewrite the serializer engine
bgneal@183 6932 unpack({
bgneal@183 6933 Z : '#|H|K|N|O|P',
bgneal@183 6934 Y : '#|X|form|R|Q',
bgneal@183 6935 X : 'p|T|div|U|W|isindex|fieldset|table',
bgneal@183 6936 W : 'pre|hr|blockquote|address|center|noframes',
bgneal@183 6937 U : 'ul|ol|dl|menu|dir',
bgneal@183 6938 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
bgneal@183 6939 T : 'h1|h2|h3|h4|h5|h6',
bgneal@183 6940 ZB : '#|X|S|Q',
bgneal@183 6941 S : 'R|P',
bgneal@183 6942 ZA : '#|a|G|J|M|O|P',
bgneal@183 6943 R : '#|a|H|K|N|O',
bgneal@183 6944 Q : 'noscript|P',
bgneal@183 6945 P : 'ins|del|script',
bgneal@183 6946 O : 'input|select|textarea|label|button',
bgneal@183 6947 N : 'M|L',
bgneal@183 6948 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
bgneal@183 6949 L : 'sub|sup',
bgneal@183 6950 K : 'J|I',
bgneal@183 6951 J : 'tt|i|b|u|s|strike',
bgneal@183 6952 I : 'big|small|font|basefont',
bgneal@183 6953 H : 'G|F',
bgneal@183 6954 G : 'br|span|bdo',
bgneal@183 6955 F : 'object|applet|img|map|iframe'
bgneal@183 6956 }, 'script[]' +
bgneal@183 6957 'style[]' +
bgneal@183 6958 'object[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@183 6959 'param[]' +
bgneal@183 6960 'p[S]' +
bgneal@183 6961 'a[Z]' +
bgneal@183 6962 'br[]' +
bgneal@183 6963 'span[S]' +
bgneal@183 6964 'bdo[S]' +
bgneal@183 6965 'applet[#|param|X|form|a|H|K|N|O|Q]' +
bgneal@183 6966 'h1[S]' +
bgneal@183 6967 'img[]' +
bgneal@183 6968 'map[X|form|Q|area]' +
bgneal@183 6969 'h2[S]' +
bgneal@183 6970 'iframe[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 6971 'h3[S]' +
bgneal@183 6972 'tt[S]' +
bgneal@183 6973 'i[S]' +
bgneal@183 6974 'b[S]' +
bgneal@183 6975 'u[S]' +
bgneal@183 6976 's[S]' +
bgneal@183 6977 'strike[S]' +
bgneal@183 6978 'big[S]' +
bgneal@183 6979 'small[S]' +
bgneal@183 6980 'font[S]' +
bgneal@183 6981 'basefont[]' +
bgneal@183 6982 'em[S]' +
bgneal@183 6983 'strong[S]' +
bgneal@183 6984 'dfn[S]' +
bgneal@183 6985 'code[S]' +
bgneal@183 6986 'q[S]' +
bgneal@183 6987 'samp[S]' +
bgneal@183 6988 'kbd[S]' +
bgneal@183 6989 'var[S]' +
bgneal@183 6990 'cite[S]' +
bgneal@183 6991 'abbr[S]' +
bgneal@183 6992 'acronym[S]' +
bgneal@183 6993 'sub[S]' +
bgneal@183 6994 'sup[S]' +
bgneal@183 6995 'input[]' +
bgneal@183 6996 'select[optgroup|option]' +
bgneal@183 6997 'optgroup[option]' +
bgneal@183 6998 'option[]' +
bgneal@183 6999 'textarea[]' +
bgneal@183 7000 'label[S]' +
bgneal@183 7001 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
bgneal@183 7002 'h4[S]' +
bgneal@183 7003 'ins[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7004 'h5[S]' +
bgneal@183 7005 'del[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7006 'h6[S]' +
bgneal@183 7007 'div[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7008 'ul[li]' +
bgneal@183 7009 'li[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7010 'ol[li]' +
bgneal@183 7011 'dl[dt|dd]' +
bgneal@183 7012 'dt[S]' +
bgneal@183 7013 'dd[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7014 'menu[li]' +
bgneal@183 7015 'dir[li]' +
bgneal@183 7016 'pre[ZA]' +
bgneal@183 7017 'hr[]' +
bgneal@183 7018 'blockquote[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7019 'address[S|p]' +
bgneal@183 7020 'center[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7021 'noframes[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7022 'isindex[]' +
bgneal@183 7023 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
bgneal@183 7024 'legend[S]' +
bgneal@183 7025 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
bgneal@183 7026 'caption[S]' +
bgneal@183 7027 'col[]' +
bgneal@183 7028 'colgroup[col]' +
bgneal@183 7029 'thead[tr]' +
bgneal@183 7030 'tr[th|td]' +
bgneal@183 7031 'th[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7032 'form[#|X|a|H|K|N|O|Q]' +
bgneal@183 7033 'noscript[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7034 'td[#|X|form|a|H|K|N|O|Q]' +
bgneal@183 7035 'tfoot[tr]' +
bgneal@183 7036 'tbody[tr]' +
bgneal@183 7037 'area[]' +
bgneal@183 7038 'base[]' +
bgneal@183 7039 'body[#|X|form|a|H|K|N|O|Q]'
bgneal@183 7040 );
bgneal@183 7041
bgneal@183 7042 tinymce.dom.Schema = function() {
bgneal@183 7043 var t = this, elements = transitional;
bgneal@183 7044
bgneal@183 7045 t.isValid = function(name, child_name) {
bgneal@183 7046 var element = elements[name];
bgneal@183 7047
bgneal@183 7048 return !!(element && (!child_name || element[child_name]));
bgneal@183 7049 };
bgneal@183 7050 };
bgneal@183 7051 })();
bgneal@183 7052 (function(tinymce) {
bgneal@183 7053 tinymce.dom.RangeUtils = function(dom) {
bgneal@183 7054 var INVISIBLE_CHAR = '\uFEFF';
bgneal@183 7055
bgneal@183 7056 this.walk = function(rng, callback) {
bgneal@183 7057 var startContainer = rng.startContainer,
bgneal@183 7058 startOffset = rng.startOffset,
bgneal@183 7059 endContainer = rng.endContainer,
bgneal@183 7060 endOffset = rng.endOffset,
bgneal@183 7061 ancestor, startPoint,
bgneal@183 7062 endPoint, node, parent, siblings, nodes;
bgneal@183 7063
bgneal@183 7064 // Handle table cell selection the table plugin enables
bgneal@183 7065 // you to fake select table cells and perform formatting actions on them
bgneal@183 7066 nodes = dom.select('td.mceSelected,th.mceSelected');
bgneal@183 7067 if (nodes.length > 0) {
bgneal@183 7068 tinymce.each(nodes, function(node) {
bgneal@183 7069 callback([node]);
bgneal@183 7070 });
bgneal@183 7071
bgneal@183 7072 return;
bgneal@183 7073 }
bgneal@183 7074
bgneal@183 7075 function collectSiblings(node, name, end_node) {
bgneal@183 7076 var siblings = [];
bgneal@183 7077
bgneal@183 7078 for (; node && node != end_node; node = node[name])
bgneal@183 7079 siblings.push(node);
bgneal@183 7080
bgneal@183 7081 return siblings;
bgneal@183 7082 };
bgneal@183 7083
bgneal@183 7084 function findEndPoint(node, root) {
bgneal@183 7085 do {
bgneal@183 7086 if (node.parentNode == root)
bgneal@183 7087 return node;
bgneal@183 7088
bgneal@183 7089 node = node.parentNode;
bgneal@183 7090 } while(node);
bgneal@183 7091 };
bgneal@183 7092
bgneal@183 7093 function walkBoundary(start_node, end_node, next) {
bgneal@183 7094 var siblingName = next ? 'nextSibling' : 'previousSibling';
bgneal@183 7095
bgneal@183 7096 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
bgneal@183 7097 parent = node.parentNode;
bgneal@183 7098 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
bgneal@183 7099
bgneal@183 7100 if (siblings.length) {
bgneal@183 7101 if (!next)
bgneal@183 7102 siblings.reverse();
bgneal@183 7103
bgneal@183 7104 callback(siblings);
bgneal@183 7105 }
bgneal@183 7106 }
bgneal@183 7107 };
bgneal@183 7108
bgneal@183 7109 // If index based start position then resolve it
bgneal@183 7110 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
bgneal@183 7111 startContainer = startContainer.childNodes[startOffset];
bgneal@183 7112
bgneal@183 7113 // If index based end position then resolve it
bgneal@183 7114 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
bgneal@183 7115 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
bgneal@183 7116
bgneal@183 7117 // Find common ancestor and end points
bgneal@183 7118 ancestor = dom.findCommonAncestor(startContainer, endContainer);
bgneal@183 7119
bgneal@183 7120 // Same container
bgneal@183 7121 if (startContainer == endContainer)
bgneal@183 7122 return callback([startContainer]);
bgneal@183 7123
bgneal@183 7124 // Process left side
bgneal@183 7125 for (node = startContainer; node; node = node.parentNode) {
bgneal@183 7126 if (node == endContainer)
bgneal@183 7127 return walkBoundary(startContainer, ancestor, true);
bgneal@183 7128
bgneal@183 7129 if (node == ancestor)
bgneal@183 7130 break;
bgneal@183 7131 }
bgneal@183 7132
bgneal@183 7133 // Process right side
bgneal@183 7134 for (node = endContainer; node; node = node.parentNode) {
bgneal@183 7135 if (node == startContainer)
bgneal@183 7136 return walkBoundary(endContainer, ancestor);
bgneal@183 7137
bgneal@183 7138 if (node == ancestor)
bgneal@183 7139 break;
bgneal@183 7140 }
bgneal@183 7141
bgneal@183 7142 // Find start/end point
bgneal@183 7143 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
bgneal@183 7144 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
bgneal@183 7145
bgneal@183 7146 // Walk left leaf
bgneal@183 7147 walkBoundary(startContainer, startPoint, true);
bgneal@183 7148
bgneal@183 7149 // Walk the middle from start to end point
bgneal@183 7150 siblings = collectSiblings(
bgneal@183 7151 startPoint == startContainer ? startPoint : startPoint.nextSibling,
bgneal@183 7152 'nextSibling',
bgneal@183 7153 endPoint == endContainer ? endPoint.nextSibling : endPoint
bgneal@183 7154 );
bgneal@183 7155
bgneal@183 7156 if (siblings.length)
bgneal@183 7157 callback(siblings);
bgneal@183 7158
bgneal@183 7159 // Walk right leaf
bgneal@183 7160 walkBoundary(endContainer, endPoint);
bgneal@183 7161 };
bgneal@183 7162
bgneal@183 7163 /* this.split = function(rng) {
bgneal@183 7164 var startContainer = rng.startContainer,
bgneal@183 7165 startOffset = rng.startOffset,
bgneal@183 7166 endContainer = rng.endContainer,
bgneal@183 7167 endOffset = rng.endOffset;
bgneal@183 7168
bgneal@183 7169 function splitText(node, offset) {
bgneal@183 7170 if (offset == node.nodeValue.length)
bgneal@183 7171 node.appendData(INVISIBLE_CHAR);
bgneal@183 7172
bgneal@183 7173 node = node.splitText(offset);
bgneal@183 7174
bgneal@183 7175 if (node.nodeValue === INVISIBLE_CHAR)
bgneal@183 7176 node.nodeValue = '';
bgneal@183 7177
bgneal@183 7178 return node;
bgneal@183 7179 };
bgneal@183 7180
bgneal@183 7181 // Handle single text node
bgneal@183 7182 if (startContainer == endContainer) {
bgneal@183 7183 if (startContainer.nodeType == 3) {
bgneal@183 7184 if (startOffset != 0)
bgneal@183 7185 startContainer = endContainer = splitText(startContainer, startOffset);
bgneal@183 7186
bgneal@183 7187 if (endOffset - startOffset != startContainer.nodeValue.length)
bgneal@183 7188 splitText(startContainer, endOffset - startOffset);
bgneal@183 7189 }
bgneal@183 7190 } else {
bgneal@183 7191 // Split startContainer text node if needed
bgneal@183 7192 if (startContainer.nodeType == 3 && startOffset != 0) {
bgneal@183 7193 startContainer = splitText(startContainer, startOffset);
bgneal@183 7194 startOffset = 0;
bgneal@183 7195 }
bgneal@183 7196
bgneal@183 7197 // Split endContainer text node if needed
bgneal@183 7198 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
bgneal@183 7199 endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@183 7200 endOffset = endContainer.nodeValue.length;
bgneal@183 7201 }
bgneal@183 7202 }
bgneal@183 7203
bgneal@183 7204 return {
bgneal@183 7205 startContainer : startContainer,
bgneal@183 7206 startOffset : startOffset,
bgneal@183 7207 endContainer : endContainer,
bgneal@183 7208 endOffset : endOffset
bgneal@183 7209 };
bgneal@183 7210 };
bgneal@183 7211 */
bgneal@183 7212 };
bgneal@183 7213 })(tinymce);
bgneal@183 7214
bgneal@45 7215 (function(tinymce) {
bgneal@45 7216 // Shorten class names
bgneal@45 7217 var DOM = tinymce.DOM, is = tinymce.is;
bgneal@45 7218
bgneal@45 7219 tinymce.create('tinymce.ui.Control', {
bgneal@45 7220 Control : function(id, s) {
bgneal@45 7221 this.id = id;
bgneal@45 7222 this.settings = s = s || {};
bgneal@45 7223 this.rendered = false;
bgneal@45 7224 this.onRender = new tinymce.util.Dispatcher(this);
bgneal@45 7225 this.classPrefix = '';
bgneal@45 7226 this.scope = s.scope || this;
bgneal@45 7227 this.disabled = 0;
bgneal@45 7228 this.active = 0;
bgneal@45 7229 },
bgneal@45 7230
bgneal@45 7231 setDisabled : function(s) {
bgneal@45 7232 var e;
bgneal@45 7233
bgneal@45 7234 if (s != this.disabled) {
bgneal@45 7235 e = DOM.get(this.id);
bgneal@45 7236
bgneal@45 7237 // Add accessibility title for unavailable actions
bgneal@45 7238 if (e && this.settings.unavailable_prefix) {
bgneal@45 7239 if (s) {
bgneal@45 7240 this.prevTitle = e.title;
bgneal@45 7241 e.title = this.settings.unavailable_prefix + ": " + e.title;
bgneal@45 7242 } else
bgneal@45 7243 e.title = this.prevTitle;
bgneal@45 7244 }
bgneal@45 7245
bgneal@45 7246 this.setState('Disabled', s);
bgneal@45 7247 this.setState('Enabled', !s);
bgneal@45 7248 this.disabled = s;
bgneal@45 7249 }
bgneal@45 7250 },
bgneal@45 7251
bgneal@45 7252 isDisabled : function() {
bgneal@45 7253 return this.disabled;
bgneal@45 7254 },
bgneal@45 7255
bgneal@45 7256 setActive : function(s) {
bgneal@45 7257 if (s != this.active) {
bgneal@45 7258 this.setState('Active', s);
bgneal@45 7259 this.active = s;
bgneal@45 7260 }
bgneal@45 7261 },
bgneal@45 7262
bgneal@45 7263 isActive : function() {
bgneal@45 7264 return this.active;
bgneal@45 7265 },
bgneal@45 7266
bgneal@45 7267 setState : function(c, s) {
bgneal@45 7268 var n = DOM.get(this.id);
bgneal@45 7269
bgneal@45 7270 c = this.classPrefix + c;
bgneal@45 7271
bgneal@45 7272 if (s)
bgneal@45 7273 DOM.addClass(n, c);
bgneal@45 7274 else
bgneal@45 7275 DOM.removeClass(n, c);
bgneal@45 7276 },
bgneal@45 7277
bgneal@45 7278 isRendered : function() {
bgneal@45 7279 return this.rendered;
bgneal@45 7280 },
bgneal@45 7281
bgneal@45 7282 renderHTML : function() {
bgneal@45 7283 },
bgneal@45 7284
bgneal@45 7285 renderTo : function(n) {
bgneal@45 7286 DOM.setHTML(n, this.renderHTML());
bgneal@45 7287 },
bgneal@45 7288
bgneal@45 7289 postRender : function() {
bgneal@45 7290 var t = this, b;
bgneal@45 7291
bgneal@45 7292 // Set pending states
bgneal@45 7293 if (is(t.disabled)) {
bgneal@45 7294 b = t.disabled;
bgneal@45 7295 t.disabled = -1;
bgneal@45 7296 t.setDisabled(b);
bgneal@45 7297 }
bgneal@45 7298
bgneal@45 7299 if (is(t.active)) {
bgneal@45 7300 b = t.active;
bgneal@45 7301 t.active = -1;
bgneal@45 7302 t.setActive(b);
bgneal@45 7303 }
bgneal@45 7304 },
bgneal@45 7305
bgneal@45 7306 remove : function() {
bgneal@45 7307 DOM.remove(this.id);
bgneal@45 7308 this.destroy();
bgneal@45 7309 },
bgneal@45 7310
bgneal@45 7311 destroy : function() {
bgneal@45 7312 tinymce.dom.Event.clear(this.id);
bgneal@45 7313 }
bgneal@183 7314 });
bgneal@183 7315 })(tinymce);
bgneal@183 7316 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
bgneal@45 7317 Container : function(id, s) {
bgneal@45 7318 this.parent(id, s);
bgneal@183 7319
bgneal@45 7320 this.controls = [];
bgneal@183 7321
bgneal@45 7322 this.lookup = {};
bgneal@45 7323 },
bgneal@45 7324
bgneal@45 7325 add : function(c) {
bgneal@45 7326 this.lookup[c.id] = c;
bgneal@45 7327 this.controls.push(c);
bgneal@45 7328
bgneal@45 7329 return c;
bgneal@45 7330 },
bgneal@45 7331
bgneal@45 7332 get : function(n) {
bgneal@45 7333 return this.lookup[n];
bgneal@45 7334 }
bgneal@183 7335 });
bgneal@183 7336
bgneal@45 7337
bgneal@45 7338 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
bgneal@45 7339 Separator : function(id, s) {
bgneal@45 7340 this.parent(id, s);
bgneal@45 7341 this.classPrefix = 'mceSeparator';
bgneal@45 7342 },
bgneal@45 7343
bgneal@45 7344 renderHTML : function() {
bgneal@45 7345 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
bgneal@45 7346 }
bgneal@183 7347 });
bgneal@183 7348
bgneal@45 7349 (function(tinymce) {
bgneal@45 7350 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 7351
bgneal@45 7352 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
bgneal@45 7353 MenuItem : function(id, s) {
bgneal@45 7354 this.parent(id, s);
bgneal@45 7355 this.classPrefix = 'mceMenuItem';
bgneal@45 7356 },
bgneal@45 7357
bgneal@45 7358 setSelected : function(s) {
bgneal@45 7359 this.setState('Selected', s);
bgneal@45 7360 this.selected = s;
bgneal@45 7361 },
bgneal@45 7362
bgneal@45 7363 isSelected : function() {
bgneal@45 7364 return this.selected;
bgneal@45 7365 },
bgneal@45 7366
bgneal@45 7367 postRender : function() {
bgneal@45 7368 var t = this;
bgneal@45 7369
bgneal@45 7370 t.parent();
bgneal@45 7371
bgneal@45 7372 // Set pending state
bgneal@45 7373 if (is(t.selected))
bgneal@45 7374 t.setSelected(t.selected);
bgneal@45 7375 }
bgneal@183 7376 });
bgneal@45 7377 })(tinymce);
bgneal@183 7378
bgneal@45 7379 (function(tinymce) {
bgneal@45 7380 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@45 7381
bgneal@45 7382 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
bgneal@45 7383 Menu : function(id, s) {
bgneal@45 7384 var t = this;
bgneal@45 7385
bgneal@45 7386 t.parent(id, s);
bgneal@45 7387 t.items = {};
bgneal@45 7388 t.collapsed = false;
bgneal@45 7389 t.menuCount = 0;
bgneal@45 7390 t.onAddItem = new tinymce.util.Dispatcher(this);
bgneal@45 7391 },
bgneal@45 7392
bgneal@45 7393 expand : function(d) {
bgneal@45 7394 var t = this;
bgneal@45 7395
bgneal@45 7396 if (d) {
bgneal@45 7397 walk(t, function(o) {
bgneal@45 7398 if (o.expand)
bgneal@45 7399 o.expand();
bgneal@45 7400 }, 'items', t);
bgneal@45 7401 }
bgneal@45 7402
bgneal@45 7403 t.collapsed = false;
bgneal@45 7404 },
bgneal@45 7405
bgneal@45 7406 collapse : function(d) {
bgneal@45 7407 var t = this;
bgneal@45 7408
bgneal@45 7409 if (d) {
bgneal@45 7410 walk(t, function(o) {
bgneal@45 7411 if (o.collapse)
bgneal@45 7412 o.collapse();
bgneal@45 7413 }, 'items', t);
bgneal@45 7414 }
bgneal@45 7415
bgneal@45 7416 t.collapsed = true;
bgneal@45 7417 },
bgneal@45 7418
bgneal@45 7419 isCollapsed : function() {
bgneal@45 7420 return this.collapsed;
bgneal@45 7421 },
bgneal@45 7422
bgneal@45 7423 add : function(o) {
bgneal@45 7424 if (!o.settings)
bgneal@45 7425 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
bgneal@45 7426
bgneal@45 7427 this.onAddItem.dispatch(this, o);
bgneal@45 7428
bgneal@45 7429 return this.items[o.id] = o;
bgneal@45 7430 },
bgneal@45 7431
bgneal@45 7432 addSeparator : function() {
bgneal@45 7433 return this.add({separator : true});
bgneal@45 7434 },
bgneal@45 7435
bgneal@45 7436 addMenu : function(o) {
bgneal@45 7437 if (!o.collapse)
bgneal@45 7438 o = this.createMenu(o);
bgneal@45 7439
bgneal@45 7440 this.menuCount++;
bgneal@45 7441
bgneal@45 7442 return this.add(o);
bgneal@45 7443 },
bgneal@45 7444
bgneal@45 7445 hasMenus : function() {
bgneal@45 7446 return this.menuCount !== 0;
bgneal@45 7447 },
bgneal@45 7448
bgneal@45 7449 remove : function(o) {
bgneal@45 7450 delete this.items[o.id];
bgneal@45 7451 },
bgneal@45 7452
bgneal@45 7453 removeAll : function() {
bgneal@45 7454 var t = this;
bgneal@45 7455
bgneal@45 7456 walk(t, function(o) {
bgneal@45 7457 if (o.removeAll)
bgneal@45 7458 o.removeAll();
bgneal@45 7459 else
bgneal@45 7460 o.remove();
bgneal@45 7461
bgneal@45 7462 o.destroy();
bgneal@45 7463 }, 'items', t);
bgneal@45 7464
bgneal@45 7465 t.items = {};
bgneal@45 7466 },
bgneal@45 7467
bgneal@45 7468 createMenu : function(o) {
bgneal@45 7469 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
bgneal@45 7470
bgneal@45 7471 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
bgneal@45 7472
bgneal@45 7473 return m;
bgneal@45 7474 }
bgneal@183 7475 });
bgneal@183 7476 })(tinymce);
bgneal@183 7477 (function(tinymce) {
bgneal@45 7478 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
bgneal@45 7479
bgneal@45 7480 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
bgneal@45 7481 DropMenu : function(id, s) {
bgneal@45 7482 s = s || {};
bgneal@45 7483 s.container = s.container || DOM.doc.body;
bgneal@45 7484 s.offset_x = s.offset_x || 0;
bgneal@45 7485 s.offset_y = s.offset_y || 0;
bgneal@45 7486 s.vp_offset_x = s.vp_offset_x || 0;
bgneal@45 7487 s.vp_offset_y = s.vp_offset_y || 0;
bgneal@45 7488
bgneal@45 7489 if (is(s.icons) && !s.icons)
bgneal@45 7490 s['class'] += ' mceNoIcons';
bgneal@45 7491
bgneal@45 7492 this.parent(id, s);
bgneal@45 7493 this.onShowMenu = new tinymce.util.Dispatcher(this);
bgneal@45 7494 this.onHideMenu = new tinymce.util.Dispatcher(this);
bgneal@45 7495 this.classPrefix = 'mceMenu';
bgneal@45 7496 },
bgneal@45 7497
bgneal@45 7498 createMenu : function(s) {
bgneal@45 7499 var t = this, cs = t.settings, m;
bgneal@45 7500
bgneal@45 7501 s.container = s.container || cs.container;
bgneal@45 7502 s.parent = t;
bgneal@45 7503 s.constrain = s.constrain || cs.constrain;
bgneal@45 7504 s['class'] = s['class'] || cs['class'];
bgneal@45 7505 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
bgneal@45 7506 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
bgneal@45 7507 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
bgneal@45 7508
bgneal@45 7509 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
bgneal@45 7510
bgneal@45 7511 return m;
bgneal@45 7512 },
bgneal@45 7513
bgneal@45 7514 update : function() {
bgneal@45 7515 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
bgneal@45 7516
bgneal@45 7517 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
bgneal@45 7518 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
bgneal@45 7519
bgneal@45 7520 if (!DOM.boxModel)
bgneal@45 7521 t.element.setStyles({width : tw + 2, height : th + 2});
bgneal@45 7522 else
bgneal@45 7523 t.element.setStyles({width : tw, height : th});
bgneal@45 7524
bgneal@45 7525 if (s.max_width)
bgneal@45 7526 DOM.setStyle(co, 'width', tw);
bgneal@45 7527
bgneal@45 7528 if (s.max_height) {
bgneal@45 7529 DOM.setStyle(co, 'height', th);
bgneal@45 7530
bgneal@45 7531 if (tb.clientHeight < s.max_height)
bgneal@45 7532 DOM.setStyle(co, 'overflow', 'hidden');
bgneal@45 7533 }
bgneal@45 7534 },
bgneal@45 7535
bgneal@45 7536 showMenu : function(x, y, px) {
bgneal@45 7537 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
bgneal@45 7538
bgneal@45 7539 t.collapse(1);
bgneal@45 7540
bgneal@45 7541 if (t.isMenuVisible)
bgneal@45 7542 return;
bgneal@45 7543
bgneal@45 7544 if (!t.rendered) {
bgneal@45 7545 co = DOM.add(t.settings.container, t.renderNode());
bgneal@45 7546
bgneal@45 7547 each(t.items, function(o) {
bgneal@45 7548 o.postRender();
bgneal@45 7549 });
bgneal@45 7550
bgneal@45 7551 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 7552 } else
bgneal@45 7553 co = DOM.get('menu_' + t.id);
bgneal@45 7554
bgneal@45 7555 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
bgneal@45 7556 if (!tinymce.isOpera)
bgneal@45 7557 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
bgneal@45 7558
bgneal@45 7559 DOM.show(co);
bgneal@45 7560 t.update();
bgneal@45 7561
bgneal@45 7562 x += s.offset_x || 0;
bgneal@45 7563 y += s.offset_y || 0;
bgneal@45 7564 vp.w -= 4;
bgneal@45 7565 vp.h -= 4;
bgneal@45 7566
bgneal@45 7567 // Move inside viewport if not submenu
bgneal@45 7568 if (s.constrain) {
bgneal@45 7569 w = co.clientWidth - ot;
bgneal@45 7570 h = co.clientHeight - ot;
bgneal@45 7571 mx = vp.x + vp.w;
bgneal@45 7572 my = vp.y + vp.h;
bgneal@45 7573
bgneal@45 7574 if ((x + s.vp_offset_x + w) > mx)
bgneal@45 7575 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
bgneal@45 7576
bgneal@45 7577 if ((y + s.vp_offset_y + h) > my)
bgneal@45 7578 y = Math.max(0, (my - s.vp_offset_y) - h);
bgneal@45 7579 }
bgneal@45 7580
bgneal@45 7581 DOM.setStyles(co, {left : x , top : y});
bgneal@45 7582 t.element.update();
bgneal@45 7583
bgneal@45 7584 t.isMenuVisible = 1;
bgneal@45 7585 t.mouseClickFunc = Event.add(co, 'click', function(e) {
bgneal@45 7586 var m;
bgneal@45 7587
bgneal@45 7588 e = e.target;
bgneal@45 7589
bgneal@183 7590 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 7591 m = t.items[e.id];
bgneal@45 7592
bgneal@45 7593 if (m.isDisabled())
bgneal@45 7594 return;
bgneal@45 7595
bgneal@45 7596 dm = t;
bgneal@45 7597
bgneal@45 7598 while (dm) {
bgneal@45 7599 if (dm.hideMenu)
bgneal@45 7600 dm.hideMenu();
bgneal@45 7601
bgneal@45 7602 dm = dm.settings.parent;
bgneal@45 7603 }
bgneal@45 7604
bgneal@45 7605 if (m.settings.onclick)
bgneal@45 7606 m.settings.onclick(e);
bgneal@45 7607
bgneal@45 7608 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@45 7609 }
bgneal@45 7610 });
bgneal@45 7611
bgneal@45 7612 if (t.hasMenus()) {
bgneal@45 7613 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
bgneal@45 7614 var m, r, mi;
bgneal@45 7615
bgneal@45 7616 e = e.target;
bgneal@183 7617 if (e && (e = DOM.getParent(e, 'tr'))) {
bgneal@45 7618 m = t.items[e.id];
bgneal@45 7619
bgneal@45 7620 if (t.lastMenu)
bgneal@45 7621 t.lastMenu.collapse(1);
bgneal@45 7622
bgneal@45 7623 if (m.isDisabled())
bgneal@45 7624 return;
bgneal@45 7625
bgneal@45 7626 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@45 7627 //p = DOM.getPos(s.container);
bgneal@45 7628 r = DOM.getRect(e);
bgneal@45 7629 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
bgneal@45 7630 t.lastMenu = m;
bgneal@45 7631 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
bgneal@45 7632 }
bgneal@45 7633 }
bgneal@45 7634 });
bgneal@45 7635 }
bgneal@45 7636
bgneal@45 7637 t.onShowMenu.dispatch(t);
bgneal@45 7638
bgneal@45 7639 if (s.keyboard_focus) {
bgneal@45 7640 Event.add(co, 'keydown', t._keyHandler, t);
bgneal@45 7641 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
bgneal@45 7642 t._focusIdx = 0;
bgneal@45 7643 }
bgneal@45 7644 },
bgneal@45 7645
bgneal@45 7646 hideMenu : function(c) {
bgneal@45 7647 var t = this, co = DOM.get('menu_' + t.id), e;
bgneal@45 7648
bgneal@45 7649 if (!t.isMenuVisible)
bgneal@45 7650 return;
bgneal@45 7651
bgneal@45 7652 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 7653 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 7654 Event.remove(co, 'keydown', t._keyHandler);
bgneal@45 7655 DOM.hide(co);
bgneal@45 7656 t.isMenuVisible = 0;
bgneal@45 7657
bgneal@45 7658 if (!c)
bgneal@45 7659 t.collapse(1);
bgneal@45 7660
bgneal@45 7661 if (t.element)
bgneal@45 7662 t.element.hide();
bgneal@45 7663
bgneal@45 7664 if (e = DOM.get(t.id))
bgneal@45 7665 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
bgneal@45 7666
bgneal@45 7667 t.onHideMenu.dispatch(t);
bgneal@45 7668 },
bgneal@45 7669
bgneal@45 7670 add : function(o) {
bgneal@45 7671 var t = this, co;
bgneal@45 7672
bgneal@45 7673 o = t.parent(o);
bgneal@45 7674
bgneal@45 7675 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
bgneal@45 7676 t._add(DOM.select('tbody', co)[0], o);
bgneal@45 7677
bgneal@45 7678 return o;
bgneal@45 7679 },
bgneal@45 7680
bgneal@45 7681 collapse : function(d) {
bgneal@45 7682 this.parent(d);
bgneal@45 7683 this.hideMenu(1);
bgneal@45 7684 },
bgneal@45 7685
bgneal@45 7686 remove : function(o) {
bgneal@45 7687 DOM.remove(o.id);
bgneal@45 7688 this.destroy();
bgneal@45 7689
bgneal@45 7690 return this.parent(o);
bgneal@45 7691 },
bgneal@45 7692
bgneal@45 7693 destroy : function() {
bgneal@45 7694 var t = this, co = DOM.get('menu_' + t.id);
bgneal@45 7695
bgneal@45 7696 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@45 7697 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@45 7698
bgneal@45 7699 if (t.element)
bgneal@45 7700 t.element.remove();
bgneal@45 7701
bgneal@45 7702 DOM.remove(co);
bgneal@45 7703 },
bgneal@45 7704
bgneal@45 7705 renderNode : function() {
bgneal@45 7706 var t = this, s = t.settings, n, tb, co, w;
bgneal@45 7707
bgneal@45 7708 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
bgneal@45 7709 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
bgneal@45 7710 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@45 7711
bgneal@45 7712 if (s.menu_line)
bgneal@45 7713 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
bgneal@45 7714
bgneal@45 7715 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
bgneal@45 7716 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
bgneal@45 7717 tb = DOM.add(n, 'tbody');
bgneal@45 7718
bgneal@45 7719 each(t.items, function(o) {
bgneal@45 7720 t._add(tb, o);
bgneal@45 7721 });
bgneal@45 7722
bgneal@45 7723 t.rendered = true;
bgneal@45 7724
bgneal@45 7725 return w;
bgneal@45 7726 },
bgneal@45 7727
bgneal@45 7728 // Internal functions
bgneal@45 7729
bgneal@45 7730 _keyHandler : function(e) {
bgneal@45 7731 var t = this, kc = e.keyCode;
bgneal@45 7732
bgneal@45 7733 function focus(d) {
bgneal@45 7734 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
bgneal@45 7735
bgneal@45 7736 if (e) {
bgneal@45 7737 t._focusIdx = i;
bgneal@45 7738 e.focus();
bgneal@45 7739 }
bgneal@45 7740 };
bgneal@45 7741
bgneal@45 7742 switch (kc) {
bgneal@45 7743 case 38:
bgneal@45 7744 focus(-1); // Select first link
bgneal@45 7745 return;
bgneal@45 7746
bgneal@45 7747 case 40:
bgneal@45 7748 focus(1);
bgneal@45 7749 return;
bgneal@45 7750
bgneal@45 7751 case 13:
bgneal@45 7752 return;
bgneal@45 7753
bgneal@45 7754 case 27:
bgneal@45 7755 return this.hideMenu();
bgneal@45 7756 }
bgneal@45 7757 },
bgneal@45 7758
bgneal@45 7759 _add : function(tb, o) {
bgneal@45 7760 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
bgneal@45 7761
bgneal@45 7762 if (s.separator) {
bgneal@45 7763 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
bgneal@45 7764 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
bgneal@45 7765
bgneal@45 7766 if (n = ro.previousSibling)
bgneal@45 7767 DOM.addClass(n, 'mceLast');
bgneal@45 7768
bgneal@45 7769 return;
bgneal@45 7770 }
bgneal@45 7771
bgneal@45 7772 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
bgneal@45 7773 n = it = DOM.add(n, 'td');
bgneal@45 7774 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
bgneal@45 7775
bgneal@45 7776 DOM.addClass(it, s['class']);
bgneal@45 7777 // n = DOM.add(n, 'span', {'class' : 'item'});
bgneal@45 7778
bgneal@45 7779 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
bgneal@45 7780
bgneal@45 7781 if (s.icon_src)
bgneal@45 7782 DOM.add(ic, 'img', {src : s.icon_src});
bgneal@45 7783
bgneal@45 7784 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
bgneal@45 7785
bgneal@45 7786 if (o.settings.style)
bgneal@45 7787 DOM.setAttrib(n, 'style', o.settings.style);
bgneal@45 7788
bgneal@45 7789 if (tb.childNodes.length == 1)
bgneal@45 7790 DOM.addClass(ro, 'mceFirst');
bgneal@45 7791
bgneal@45 7792 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
bgneal@45 7793 DOM.addClass(ro, 'mceFirst');
bgneal@45 7794
bgneal@45 7795 if (o.collapse)
bgneal@45 7796 DOM.addClass(ro, cp + 'ItemSub');
bgneal@45 7797
bgneal@45 7798 if (n = ro.previousSibling)
bgneal@45 7799 DOM.removeClass(n, 'mceLast');
bgneal@45 7800
bgneal@45 7801 DOM.addClass(ro, 'mceLast');
bgneal@45 7802 }
bgneal@183 7803 });
bgneal@183 7804 })(tinymce);
bgneal@183 7805 (function(tinymce) {
bgneal@45 7806 var DOM = tinymce.DOM;
bgneal@45 7807
bgneal@45 7808 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
bgneal@45 7809 Button : function(id, s) {
bgneal@45 7810 this.parent(id, s);
bgneal@45 7811 this.classPrefix = 'mceButton';
bgneal@45 7812 },
bgneal@45 7813
bgneal@45 7814 renderHTML : function() {
bgneal@45 7815 var cp = this.classPrefix, s = this.settings, h, l;
bgneal@45 7816
bgneal@45 7817 l = DOM.encode(s.label || '');
bgneal@45 7818 h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
bgneal@45 7819
bgneal@45 7820 if (s.image)
bgneal@45 7821 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
bgneal@45 7822 else
bgneal@45 7823 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
bgneal@45 7824
bgneal@45 7825 return h;
bgneal@45 7826 },
bgneal@45 7827
bgneal@45 7828 postRender : function() {
bgneal@45 7829 var t = this, s = t.settings;
bgneal@45 7830
bgneal@45 7831 tinymce.dom.Event.add(t.id, 'click', function(e) {
bgneal@45 7832 if (!t.isDisabled())
bgneal@45 7833 return s.onclick.call(s.scope, e);
bgneal@45 7834 });
bgneal@45 7835 }
bgneal@183 7836 });
bgneal@45 7837 })(tinymce);
bgneal@183 7838
bgneal@45 7839 (function(tinymce) {
bgneal@45 7840 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 7841
bgneal@45 7842 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
bgneal@45 7843 ListBox : function(id, s) {
bgneal@45 7844 var t = this;
bgneal@45 7845
bgneal@45 7846 t.parent(id, s);
bgneal@183 7847
bgneal@45 7848 t.items = [];
bgneal@183 7849
bgneal@45 7850 t.onChange = new Dispatcher(t);
bgneal@183 7851
bgneal@45 7852 t.onPostRender = new Dispatcher(t);
bgneal@183 7853
bgneal@45 7854 t.onAdd = new Dispatcher(t);
bgneal@183 7855
bgneal@45 7856 t.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@183 7857
bgneal@45 7858 t.classPrefix = 'mceListBox';
bgneal@45 7859 },
bgneal@45 7860
bgneal@45 7861 select : function(va) {
bgneal@45 7862 var t = this, fv, f;
bgneal@45 7863
bgneal@45 7864 if (va == undefined)
bgneal@45 7865 return t.selectByIndex(-1);
bgneal@45 7866
bgneal@45 7867 // Is string or number make function selector
bgneal@45 7868 if (va && va.call)
bgneal@45 7869 f = va;
bgneal@45 7870 else {
bgneal@45 7871 f = function(v) {
bgneal@45 7872 return v == va;
bgneal@45 7873 };
bgneal@45 7874 }
bgneal@45 7875
bgneal@45 7876 // Do we need to do something?
bgneal@45 7877 if (va != t.selectedValue) {
bgneal@45 7878 // Find item
bgneal@45 7879 each(t.items, function(o, i) {
bgneal@45 7880 if (f(o.value)) {
bgneal@45 7881 fv = 1;
bgneal@45 7882 t.selectByIndex(i);
bgneal@45 7883 return false;
bgneal@45 7884 }
bgneal@45 7885 });
bgneal@45 7886
bgneal@45 7887 if (!fv)
bgneal@45 7888 t.selectByIndex(-1);
bgneal@45 7889 }
bgneal@45 7890 },
bgneal@45 7891
bgneal@45 7892 selectByIndex : function(idx) {
bgneal@45 7893 var t = this, e, o;
bgneal@45 7894
bgneal@45 7895 if (idx != t.selectedIndex) {
bgneal@45 7896 e = DOM.get(t.id + '_text');
bgneal@45 7897 o = t.items[idx];
bgneal@45 7898
bgneal@45 7899 if (o) {
bgneal@45 7900 t.selectedValue = o.value;
bgneal@45 7901 t.selectedIndex = idx;
bgneal@45 7902 DOM.setHTML(e, DOM.encode(o.title));
bgneal@45 7903 DOM.removeClass(e, 'mceTitle');
bgneal@45 7904 } else {
bgneal@45 7905 DOM.setHTML(e, DOM.encode(t.settings.title));
bgneal@45 7906 DOM.addClass(e, 'mceTitle');
bgneal@45 7907 t.selectedValue = t.selectedIndex = null;
bgneal@45 7908 }
bgneal@45 7909
bgneal@45 7910 e = 0;
bgneal@45 7911 }
bgneal@45 7912 },
bgneal@45 7913
bgneal@45 7914 add : function(n, v, o) {
bgneal@45 7915 var t = this;
bgneal@45 7916
bgneal@45 7917 o = o || {};
bgneal@45 7918 o = tinymce.extend(o, {
bgneal@45 7919 title : n,
bgneal@45 7920 value : v
bgneal@45 7921 });
bgneal@45 7922
bgneal@45 7923 t.items.push(o);
bgneal@45 7924 t.onAdd.dispatch(t, o);
bgneal@45 7925 },
bgneal@45 7926
bgneal@45 7927 getLength : function() {
bgneal@45 7928 return this.items.length;
bgneal@45 7929 },
bgneal@45 7930
bgneal@45 7931 renderHTML : function() {
bgneal@45 7932 var h = '', t = this, s = t.settings, cp = t.classPrefix;
bgneal@45 7933
bgneal@45 7934 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
bgneal@45 7935 h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
bgneal@45 7936 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
bgneal@45 7937 h += '</tr></tbody></table>';
bgneal@45 7938
bgneal@45 7939 return h;
bgneal@45 7940 },
bgneal@45 7941
bgneal@45 7942 showMenu : function() {
bgneal@45 7943 var t = this, p1, p2, e = DOM.get(this.id), m;
bgneal@45 7944
bgneal@45 7945 if (t.isDisabled() || t.items.length == 0)
bgneal@45 7946 return;
bgneal@45 7947
bgneal@45 7948 if (t.menu && t.menu.isMenuVisible)
bgneal@45 7949 return t.hideMenu();
bgneal@45 7950
bgneal@45 7951 if (!t.isMenuRendered) {
bgneal@45 7952 t.renderMenu();
bgneal@45 7953 t.isMenuRendered = true;
bgneal@45 7954 }
bgneal@45 7955
bgneal@45 7956 p1 = DOM.getPos(this.settings.menu_container);
bgneal@45 7957 p2 = DOM.getPos(e);
bgneal@45 7958
bgneal@45 7959 m = t.menu;
bgneal@45 7960 m.settings.offset_x = p2.x;
bgneal@45 7961 m.settings.offset_y = p2.y;
bgneal@45 7962 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
bgneal@45 7963
bgneal@45 7964 // Select in menu
bgneal@45 7965 if (t.oldID)
bgneal@45 7966 m.items[t.oldID].setSelected(0);
bgneal@45 7967
bgneal@45 7968 each(t.items, function(o) {
bgneal@45 7969 if (o.value === t.selectedValue) {
bgneal@45 7970 m.items[o.id].setSelected(1);
bgneal@45 7971 t.oldID = o.id;
bgneal@45 7972 }
bgneal@45 7973 });
bgneal@45 7974
bgneal@45 7975 m.showMenu(0, e.clientHeight);
bgneal@45 7976
bgneal@45 7977 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 7978 DOM.addClass(t.id, t.classPrefix + 'Selected');
bgneal@45 7979
bgneal@45 7980 //DOM.get(t.id + '_text').focus();
bgneal@45 7981 },
bgneal@45 7982
bgneal@45 7983 hideMenu : function(e) {
bgneal@45 7984 var t = this;
bgneal@45 7985
bgneal@183 7986 if (t.menu && t.menu.isMenuVisible) {
bgneal@183 7987 // Prevent double toogles by canceling the mouse click event to the button
bgneal@183 7988 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
bgneal@183 7989 return;
bgneal@183 7990
bgneal@183 7991 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@183 7992 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@183 7993 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 7994 t.menu.hideMenu();
bgneal@183 7995 }
bgneal@45 7996 }
bgneal@45 7997 },
bgneal@45 7998
bgneal@45 7999 renderMenu : function() {
bgneal@45 8000 var t = this, m;
bgneal@45 8001
bgneal@45 8002 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 8003 menu_line : 1,
bgneal@45 8004 'class' : t.classPrefix + 'Menu mceNoIcons',
bgneal@45 8005 max_width : 150,
bgneal@45 8006 max_height : 150
bgneal@45 8007 });
bgneal@45 8008
bgneal@45 8009 m.onHideMenu.add(t.hideMenu, t);
bgneal@45 8010
bgneal@45 8011 m.add({
bgneal@45 8012 title : t.settings.title,
bgneal@45 8013 'class' : 'mceMenuItemTitle',
bgneal@45 8014 onclick : function() {
bgneal@45 8015 if (t.settings.onselect('') !== false)
bgneal@45 8016 t.select(''); // Must be runned after
bgneal@45 8017 }
bgneal@45 8018 });
bgneal@45 8019
bgneal@45 8020 each(t.items, function(o) {
bgneal@183 8021 // No value then treat it as a title
bgneal@183 8022 if (o.value === undefined) {
bgneal@183 8023 m.add({
bgneal@183 8024 title : o.title,
bgneal@183 8025 'class' : 'mceMenuItemTitle',
bgneal@183 8026 onclick : function() {
bgneal@183 8027 if (t.settings.onselect('') !== false)
bgneal@183 8028 t.select(''); // Must be runned after
bgneal@183 8029 }
bgneal@183 8030 });
bgneal@183 8031 } else {
bgneal@183 8032 o.id = DOM.uniqueId();
bgneal@183 8033 o.onclick = function() {
bgneal@183 8034 if (t.settings.onselect(o.value) !== false)
bgneal@183 8035 t.select(o.value); // Must be runned after
bgneal@183 8036 };
bgneal@183 8037
bgneal@183 8038 m.add(o);
bgneal@183 8039 }
bgneal@45 8040 });
bgneal@45 8041
bgneal@45 8042 t.onRenderMenu.dispatch(t, m);
bgneal@45 8043 t.menu = m;
bgneal@45 8044 },
bgneal@45 8045
bgneal@45 8046 postRender : function() {
bgneal@45 8047 var t = this, cp = t.classPrefix;
bgneal@45 8048
bgneal@45 8049 Event.add(t.id, 'click', t.showMenu, t);
bgneal@45 8050 Event.add(t.id + '_text', 'focus', function(e) {
bgneal@45 8051 if (!t._focused) {
bgneal@45 8052 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
bgneal@45 8053 var idx = -1, v, kc = e.keyCode;
bgneal@45 8054
bgneal@45 8055 // Find current index
bgneal@45 8056 each(t.items, function(v, i) {
bgneal@45 8057 if (t.selectedValue == v.value)
bgneal@45 8058 idx = i;
bgneal@45 8059 });
bgneal@45 8060
bgneal@45 8061 // Move up/down
bgneal@45 8062 if (kc == 38)
bgneal@45 8063 v = t.items[idx - 1];
bgneal@45 8064 else if (kc == 40)
bgneal@45 8065 v = t.items[idx + 1];
bgneal@45 8066 else if (kc == 13) {
bgneal@45 8067 // Fake select on enter
bgneal@45 8068 v = t.selectedValue;
bgneal@45 8069 t.selectedValue = null; // Needs to be null to fake change
bgneal@45 8070 t.settings.onselect(v);
bgneal@45 8071 return Event.cancel(e);
bgneal@45 8072 }
bgneal@45 8073
bgneal@45 8074 if (v) {
bgneal@45 8075 t.hideMenu();
bgneal@45 8076 t.select(v.value);
bgneal@45 8077 }
bgneal@45 8078 });
bgneal@45 8079 }
bgneal@45 8080
bgneal@45 8081 t._focused = 1;
bgneal@45 8082 });
bgneal@45 8083 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
bgneal@45 8084
bgneal@45 8085 // Old IE doesn't have hover on all elements
bgneal@45 8086 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 8087 Event.add(t.id, 'mouseover', function() {
bgneal@45 8088 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 8089 DOM.addClass(t.id, cp + 'Hover');
bgneal@45 8090 });
bgneal@45 8091
bgneal@45 8092 Event.add(t.id, 'mouseout', function() {
bgneal@45 8093 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@45 8094 DOM.removeClass(t.id, cp + 'Hover');
bgneal@45 8095 });
bgneal@45 8096 }
bgneal@45 8097
bgneal@45 8098 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 8099 },
bgneal@45 8100
bgneal@45 8101 destroy : function() {
bgneal@45 8102 this.parent();
bgneal@45 8103
bgneal@45 8104 Event.clear(this.id + '_text');
bgneal@183 8105 Event.clear(this.id + '_open');
bgneal@45 8106 }
bgneal@183 8107 });
bgneal@183 8108 })(tinymce);
bgneal@183 8109 (function(tinymce) {
bgneal@45 8110 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@45 8111
bgneal@45 8112 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
bgneal@45 8113 NativeListBox : function(id, s) {
bgneal@45 8114 this.parent(id, s);
bgneal@45 8115 this.classPrefix = 'mceNativeListBox';
bgneal@45 8116 },
bgneal@45 8117
bgneal@45 8118 setDisabled : function(s) {
bgneal@45 8119 DOM.get(this.id).disabled = s;
bgneal@45 8120 },
bgneal@45 8121
bgneal@45 8122 isDisabled : function() {
bgneal@45 8123 return DOM.get(this.id).disabled;
bgneal@45 8124 },
bgneal@45 8125
bgneal@45 8126 select : function(va) {
bgneal@45 8127 var t = this, fv, f;
bgneal@45 8128
bgneal@45 8129 if (va == undefined)
bgneal@45 8130 return t.selectByIndex(-1);
bgneal@45 8131
bgneal@45 8132 // Is string or number make function selector
bgneal@45 8133 if (va && va.call)
bgneal@45 8134 f = va;
bgneal@45 8135 else {
bgneal@45 8136 f = function(v) {
bgneal@45 8137 return v == va;
bgneal@45 8138 };
bgneal@45 8139 }
bgneal@45 8140
bgneal@45 8141 // Do we need to do something?
bgneal@45 8142 if (va != t.selectedValue) {
bgneal@45 8143 // Find item
bgneal@45 8144 each(t.items, function(o, i) {
bgneal@45 8145 if (f(o.value)) {
bgneal@45 8146 fv = 1;
bgneal@45 8147 t.selectByIndex(i);
bgneal@45 8148 return false;
bgneal@45 8149 }
bgneal@45 8150 });
bgneal@45 8151
bgneal@45 8152 if (!fv)
bgneal@45 8153 t.selectByIndex(-1);
bgneal@45 8154 }
bgneal@45 8155 },
bgneal@45 8156
bgneal@45 8157 selectByIndex : function(idx) {
bgneal@45 8158 DOM.get(this.id).selectedIndex = idx + 1;
bgneal@45 8159 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
bgneal@45 8160 },
bgneal@45 8161
bgneal@45 8162 add : function(n, v, a) {
bgneal@45 8163 var o, t = this;
bgneal@45 8164
bgneal@45 8165 a = a || {};
bgneal@45 8166 a.value = v;
bgneal@45 8167
bgneal@45 8168 if (t.isRendered())
bgneal@45 8169 DOM.add(DOM.get(this.id), 'option', a, n);
bgneal@45 8170
bgneal@45 8171 o = {
bgneal@45 8172 title : n,
bgneal@45 8173 value : v,
bgneal@45 8174 attribs : a
bgneal@45 8175 };
bgneal@45 8176
bgneal@45 8177 t.items.push(o);
bgneal@45 8178 t.onAdd.dispatch(t, o);
bgneal@45 8179 },
bgneal@45 8180
bgneal@45 8181 getLength : function() {
bgneal@183 8182 return this.items.length;
bgneal@45 8183 },
bgneal@45 8184
bgneal@45 8185 renderHTML : function() {
bgneal@45 8186 var h, t = this;
bgneal@45 8187
bgneal@45 8188 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
bgneal@45 8189
bgneal@45 8190 each(t.items, function(it) {
bgneal@45 8191 h += DOM.createHTML('option', {value : it.value}, it.title);
bgneal@45 8192 });
bgneal@45 8193
bgneal@45 8194 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
bgneal@45 8195
bgneal@45 8196 return h;
bgneal@45 8197 },
bgneal@45 8198
bgneal@45 8199 postRender : function() {
bgneal@45 8200 var t = this, ch;
bgneal@45 8201
bgneal@45 8202 t.rendered = true;
bgneal@45 8203
bgneal@45 8204 function onChange(e) {
bgneal@45 8205 var v = t.items[e.target.selectedIndex - 1];
bgneal@45 8206
bgneal@45 8207 if (v && (v = v.value)) {
bgneal@45 8208 t.onChange.dispatch(t, v);
bgneal@45 8209
bgneal@45 8210 if (t.settings.onselect)
bgneal@45 8211 t.settings.onselect(v);
bgneal@45 8212 }
bgneal@45 8213 };
bgneal@45 8214
bgneal@45 8215 Event.add(t.id, 'change', onChange);
bgneal@45 8216
bgneal@45 8217 // Accessibility keyhandler
bgneal@45 8218 Event.add(t.id, 'keydown', function(e) {
bgneal@45 8219 var bf;
bgneal@45 8220
bgneal@45 8221 Event.remove(t.id, 'change', ch);
bgneal@45 8222
bgneal@45 8223 bf = Event.add(t.id, 'blur', function() {
bgneal@45 8224 Event.add(t.id, 'change', onChange);
bgneal@45 8225 Event.remove(t.id, 'blur', bf);
bgneal@45 8226 });
bgneal@45 8227
bgneal@45 8228 if (e.keyCode == 13 || e.keyCode == 32) {
bgneal@45 8229 onChange(e);
bgneal@45 8230 return Event.cancel(e);
bgneal@45 8231 }
bgneal@45 8232 });
bgneal@45 8233
bgneal@45 8234 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@45 8235 }
bgneal@183 8236 });
bgneal@183 8237 })(tinymce);
bgneal@183 8238 (function(tinymce) {
bgneal@45 8239 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 8240
bgneal@45 8241 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
bgneal@45 8242 MenuButton : function(id, s) {
bgneal@45 8243 this.parent(id, s);
bgneal@183 8244
bgneal@45 8245 this.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@183 8246
bgneal@45 8247 s.menu_container = s.menu_container || DOM.doc.body;
bgneal@45 8248 },
bgneal@45 8249
bgneal@45 8250 showMenu : function() {
bgneal@45 8251 var t = this, p1, p2, e = DOM.get(t.id), m;
bgneal@45 8252
bgneal@45 8253 if (t.isDisabled())
bgneal@45 8254 return;
bgneal@45 8255
bgneal@45 8256 if (!t.isMenuRendered) {
bgneal@45 8257 t.renderMenu();
bgneal@45 8258 t.isMenuRendered = true;
bgneal@45 8259 }
bgneal@45 8260
bgneal@45 8261 if (t.isMenuVisible)
bgneal@45 8262 return t.hideMenu();
bgneal@45 8263
bgneal@45 8264 p1 = DOM.getPos(t.settings.menu_container);
bgneal@45 8265 p2 = DOM.getPos(e);
bgneal@45 8266
bgneal@45 8267 m = t.menu;
bgneal@45 8268 m.settings.offset_x = p2.x;
bgneal@45 8269 m.settings.offset_y = p2.y;
bgneal@45 8270 m.settings.vp_offset_x = p2.x;
bgneal@45 8271 m.settings.vp_offset_y = p2.y;
bgneal@45 8272 m.settings.keyboard_focus = t._focused;
bgneal@45 8273 m.showMenu(0, e.clientHeight);
bgneal@45 8274
bgneal@45 8275 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8276 t.setState('Selected', 1);
bgneal@45 8277
bgneal@45 8278 t.isMenuVisible = 1;
bgneal@45 8279 },
bgneal@45 8280
bgneal@45 8281 renderMenu : function() {
bgneal@45 8282 var t = this, m;
bgneal@45 8283
bgneal@45 8284 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@45 8285 menu_line : 1,
bgneal@45 8286 'class' : this.classPrefix + 'Menu',
bgneal@45 8287 icons : t.settings.icons
bgneal@45 8288 });
bgneal@45 8289
bgneal@45 8290 m.onHideMenu.add(t.hideMenu, t);
bgneal@45 8291
bgneal@45 8292 t.onRenderMenu.dispatch(t, m);
bgneal@45 8293 t.menu = m;
bgneal@45 8294 },
bgneal@45 8295
bgneal@45 8296 hideMenu : function(e) {
bgneal@45 8297 var t = this;
bgneal@45 8298
bgneal@45 8299 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 8300 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
bgneal@45 8301 return;
bgneal@45 8302
bgneal@45 8303 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@45 8304 t.setState('Selected', 0);
bgneal@45 8305 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8306 if (t.menu)
bgneal@45 8307 t.menu.hideMenu();
bgneal@45 8308 }
bgneal@45 8309
bgneal@45 8310 t.isMenuVisible = 0;
bgneal@45 8311 },
bgneal@45 8312
bgneal@45 8313 postRender : function() {
bgneal@45 8314 var t = this, s = t.settings;
bgneal@45 8315
bgneal@45 8316 Event.add(t.id, 'click', function() {
bgneal@45 8317 if (!t.isDisabled()) {
bgneal@45 8318 if (s.onclick)
bgneal@45 8319 s.onclick(t.value);
bgneal@45 8320
bgneal@45 8321 t.showMenu();
bgneal@45 8322 }
bgneal@45 8323 });
bgneal@45 8324 }
bgneal@183 8325 });
bgneal@45 8326 })(tinymce);
bgneal@183 8327
bgneal@45 8328 (function(tinymce) {
bgneal@45 8329 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@45 8330
bgneal@45 8331 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
bgneal@45 8332 SplitButton : function(id, s) {
bgneal@45 8333 this.parent(id, s);
bgneal@45 8334 this.classPrefix = 'mceSplitButton';
bgneal@45 8335 },
bgneal@45 8336
bgneal@45 8337 renderHTML : function() {
bgneal@45 8338 var h, t = this, s = t.settings, h1;
bgneal@45 8339
bgneal@45 8340 h = '<tbody><tr>';
bgneal@45 8341
bgneal@45 8342 if (s.image)
bgneal@45 8343 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
bgneal@45 8344 else
bgneal@45 8345 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
bgneal@45 8346
bgneal@45 8347 h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 8348
bgneal@45 8349 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
bgneal@45 8350 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@45 8351
bgneal@45 8352 h += '</tr></tbody>';
bgneal@45 8353
bgneal@45 8354 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
bgneal@45 8355 },
bgneal@45 8356
bgneal@45 8357 postRender : function() {
bgneal@45 8358 var t = this, s = t.settings;
bgneal@45 8359
bgneal@45 8360 if (s.onclick) {
bgneal@45 8361 Event.add(t.id + '_action', 'click', function() {
bgneal@45 8362 if (!t.isDisabled())
bgneal@45 8363 s.onclick(t.value);
bgneal@45 8364 });
bgneal@45 8365 }
bgneal@45 8366
bgneal@45 8367 Event.add(t.id + '_open', 'click', t.showMenu, t);
bgneal@45 8368 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
bgneal@45 8369 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
bgneal@45 8370
bgneal@45 8371 // Old IE doesn't have hover on all elements
bgneal@45 8372 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@45 8373 Event.add(t.id, 'mouseover', function() {
bgneal@45 8374 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 8375 DOM.addClass(t.id, 'mceSplitButtonHover');
bgneal@45 8376 });
bgneal@45 8377
bgneal@45 8378 Event.add(t.id, 'mouseout', function() {
bgneal@45 8379 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@45 8380 DOM.removeClass(t.id, 'mceSplitButtonHover');
bgneal@45 8381 });
bgneal@45 8382 }
bgneal@45 8383 },
bgneal@45 8384
bgneal@45 8385 destroy : function() {
bgneal@45 8386 this.parent();
bgneal@45 8387
bgneal@45 8388 Event.clear(this.id + '_action');
bgneal@45 8389 Event.clear(this.id + '_open');
bgneal@45 8390 }
bgneal@183 8391 });
bgneal@45 8392 })(tinymce);
bgneal@183 8393
bgneal@45 8394 (function(tinymce) {
bgneal@45 8395 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
bgneal@45 8396
bgneal@45 8397 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
bgneal@45 8398 ColorSplitButton : function(id, s) {
bgneal@45 8399 var t = this;
bgneal@45 8400
bgneal@45 8401 t.parent(id, s);
bgneal@45 8402
bgneal@45 8403 t.settings = s = tinymce.extend({
bgneal@45 8404 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
bgneal@45 8405 grid_width : 8,
bgneal@45 8406 default_color : '#888888'
bgneal@45 8407 }, t.settings);
bgneal@45 8408
bgneal@45 8409 t.onShowMenu = new tinymce.util.Dispatcher(t);
bgneal@183 8410
bgneal@45 8411 t.onHideMenu = new tinymce.util.Dispatcher(t);
bgneal@45 8412
bgneal@45 8413 t.value = s.default_color;
bgneal@45 8414 },
bgneal@45 8415
bgneal@45 8416 showMenu : function() {
bgneal@45 8417 var t = this, r, p, e, p2;
bgneal@45 8418
bgneal@45 8419 if (t.isDisabled())
bgneal@45 8420 return;
bgneal@45 8421
bgneal@45 8422 if (!t.isMenuRendered) {
bgneal@45 8423 t.renderMenu();
bgneal@45 8424 t.isMenuRendered = true;
bgneal@45 8425 }
bgneal@45 8426
bgneal@45 8427 if (t.isMenuVisible)
bgneal@45 8428 return t.hideMenu();
bgneal@45 8429
bgneal@45 8430 e = DOM.get(t.id);
bgneal@45 8431 DOM.show(t.id + '_menu');
bgneal@45 8432 DOM.addClass(e, 'mceSplitButtonSelected');
bgneal@45 8433 p2 = DOM.getPos(e);
bgneal@45 8434 DOM.setStyles(t.id + '_menu', {
bgneal@45 8435 left : p2.x,
bgneal@45 8436 top : p2.y + e.clientHeight,
bgneal@45 8437 zIndex : 200000
bgneal@45 8438 });
bgneal@45 8439 e = 0;
bgneal@45 8440
bgneal@45 8441 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@183 8442 t.onShowMenu.dispatch(t);
bgneal@45 8443
bgneal@45 8444 if (t._focused) {
bgneal@45 8445 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
bgneal@45 8446 if (e.keyCode == 27)
bgneal@45 8447 t.hideMenu();
bgneal@45 8448 });
bgneal@45 8449
bgneal@45 8450 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
bgneal@45 8451 }
bgneal@45 8452
bgneal@45 8453 t.isMenuVisible = 1;
bgneal@45 8454 },
bgneal@45 8455
bgneal@45 8456 hideMenu : function(e) {
bgneal@45 8457 var t = this;
bgneal@45 8458
bgneal@45 8459 // Prevent double toogles by canceling the mouse click event to the button
bgneal@45 8460 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
bgneal@45 8461 return;
bgneal@45 8462
bgneal@45 8463 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
bgneal@45 8464 DOM.removeClass(t.id, 'mceSplitButtonSelected');
bgneal@45 8465 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@45 8466 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
bgneal@45 8467 DOM.hide(t.id + '_menu');
bgneal@45 8468 }
bgneal@45 8469
bgneal@45 8470 t.onHideMenu.dispatch(t);
bgneal@45 8471
bgneal@45 8472 t.isMenuVisible = 0;
bgneal@45 8473 },
bgneal@45 8474
bgneal@45 8475 renderMenu : function() {
bgneal@45 8476 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
bgneal@45 8477
bgneal@45 8478 w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
bgneal@45 8479 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
bgneal@45 8480 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
bgneal@45 8481
bgneal@45 8482 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
bgneal@45 8483 tb = DOM.add(n, 'tbody');
bgneal@45 8484
bgneal@45 8485 // Generate color grid
bgneal@45 8486 i = 0;
bgneal@45 8487 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
bgneal@45 8488 c = c.replace(/^#/, '');
bgneal@45 8489
bgneal@45 8490 if (!i--) {
bgneal@45 8491 tr = DOM.add(tb, 'tr');
bgneal@45 8492 i = s.grid_width - 1;
bgneal@45 8493 }
bgneal@45 8494
bgneal@45 8495 n = DOM.add(tr, 'td');
bgneal@45 8496
bgneal@45 8497 n = DOM.add(n, 'a', {
bgneal@45 8498 href : 'javascript:;',
bgneal@45 8499 style : {
bgneal@45 8500 backgroundColor : '#' + c
bgneal@45 8501 },
bgneal@183 8502 _mce_color : '#' + c
bgneal@45 8503 });
bgneal@45 8504 });
bgneal@45 8505
bgneal@45 8506 if (s.more_colors_func) {
bgneal@45 8507 n = DOM.add(tb, 'tr');
bgneal@45 8508 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
bgneal@45 8509 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
bgneal@45 8510
bgneal@45 8511 Event.add(n, 'click', function(e) {
bgneal@45 8512 s.more_colors_func.call(s.more_colors_scope || this);
bgneal@45 8513 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@45 8514 });
bgneal@45 8515 }
bgneal@45 8516
bgneal@45 8517 DOM.addClass(m, 'mceColorSplitMenu');
bgneal@45 8518
bgneal@45 8519 Event.add(t.id + '_menu', 'click', function(e) {
bgneal@45 8520 var c;
bgneal@45 8521
bgneal@45 8522 e = e.target;
bgneal@45 8523
bgneal@183 8524 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
bgneal@45 8525 t.setColor(c);
bgneal@45 8526
bgneal@45 8527 return Event.cancel(e); // Prevent IE auto save warning
bgneal@45 8528 });
bgneal@45 8529
bgneal@45 8530 return w;
bgneal@45 8531 },
bgneal@45 8532
bgneal@45 8533 setColor : function(c) {
bgneal@45 8534 var t = this;
bgneal@45 8535
bgneal@45 8536 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
bgneal@45 8537
bgneal@45 8538 t.value = c;
bgneal@45 8539 t.hideMenu();
bgneal@45 8540 t.settings.onselect(c);
bgneal@45 8541 },
bgneal@45 8542
bgneal@45 8543 postRender : function() {
bgneal@45 8544 var t = this, id = t.id;
bgneal@45 8545
bgneal@45 8546 t.parent();
bgneal@45 8547 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
bgneal@45 8548 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
bgneal@45 8549 },
bgneal@45 8550
bgneal@45 8551 destroy : function() {
bgneal@45 8552 this.parent();
bgneal@45 8553
bgneal@45 8554 Event.clear(this.id + '_menu');
bgneal@45 8555 Event.clear(this.id + '_more');
bgneal@45 8556 DOM.remove(this.id + '_menu');
bgneal@45 8557 }
bgneal@183 8558 });
bgneal@45 8559 })(tinymce);
bgneal@183 8560
bgneal@45 8561 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
bgneal@45 8562 renderHTML : function() {
bgneal@45 8563 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
bgneal@45 8564
bgneal@45 8565 cl = t.controls;
bgneal@45 8566 for (i=0; i<cl.length; i++) {
bgneal@45 8567 // Get current control, prev control, next control and if the control is a list box or not
bgneal@45 8568 co = cl[i];
bgneal@45 8569 pr = cl[i - 1];
bgneal@45 8570 nx = cl[i + 1];
bgneal@45 8571
bgneal@45 8572 // Add toolbar start
bgneal@45 8573 if (i === 0) {
bgneal@45 8574 c = 'mceToolbarStart';
bgneal@45 8575
bgneal@45 8576 if (co.Button)
bgneal@45 8577 c += ' mceToolbarStartButton';
bgneal@45 8578 else if (co.SplitButton)
bgneal@45 8579 c += ' mceToolbarStartSplitButton';
bgneal@45 8580 else if (co.ListBox)
bgneal@45 8581 c += ' mceToolbarStartListBox';
bgneal@45 8582
bgneal@45 8583 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8584 }
bgneal@45 8585
bgneal@45 8586 // Add toolbar end before list box and after the previous button
bgneal@45 8587 // This is to fix the o2k7 editor skins
bgneal@45 8588 if (pr && co.ListBox) {
bgneal@45 8589 if (pr.Button || pr.SplitButton)
bgneal@45 8590 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8591 }
bgneal@45 8592
bgneal@45 8593 // Render control HTML
bgneal@45 8594
bgneal@45 8595 // IE 8 quick fix, needed to propertly generate a hit area for anchors
bgneal@45 8596 if (dom.stdMode)
bgneal@45 8597 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
bgneal@45 8598 else
bgneal@45 8599 h += '<td>' + co.renderHTML() + '</td>';
bgneal@45 8600
bgneal@45 8601 // Add toolbar start after list box and before the next button
bgneal@45 8602 // This is to fix the o2k7 editor skins
bgneal@45 8603 if (nx && co.ListBox) {
bgneal@45 8604 if (nx.Button || nx.SplitButton)
bgneal@45 8605 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8606 }
bgneal@45 8607 }
bgneal@45 8608
bgneal@45 8609 c = 'mceToolbarEnd';
bgneal@45 8610
bgneal@45 8611 if (co.Button)
bgneal@45 8612 c += ' mceToolbarEndButton';
bgneal@45 8613 else if (co.SplitButton)
bgneal@45 8614 c += ' mceToolbarEndSplitButton';
bgneal@45 8615 else if (co.ListBox)
bgneal@45 8616 c += ' mceToolbarEndListBox';
bgneal@45 8617
bgneal@45 8618 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@45 8619
bgneal@45 8620 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
bgneal@45 8621 }
bgneal@183 8622 });
bgneal@183 8623
bgneal@45 8624 (function(tinymce) {
bgneal@45 8625 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
bgneal@45 8626
bgneal@45 8627 tinymce.create('tinymce.AddOnManager', {
bgneal@45 8628 items : [],
bgneal@45 8629 urls : {},
bgneal@45 8630 lookup : {},
bgneal@183 8631
bgneal@45 8632 onAdd : new Dispatcher(this),
bgneal@45 8633
bgneal@45 8634 get : function(n) {
bgneal@45 8635 return this.lookup[n];
bgneal@45 8636 },
bgneal@45 8637
bgneal@45 8638 requireLangPack : function(n) {
bgneal@183 8639 var s = tinymce.settings;
bgneal@183 8640
bgneal@183 8641 if (s && s.language)
bgneal@183 8642 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
bgneal@45 8643 },
bgneal@45 8644
bgneal@45 8645 add : function(id, o) {
bgneal@45 8646 this.items.push(o);
bgneal@45 8647 this.lookup[id] = o;
bgneal@45 8648 this.onAdd.dispatch(this, id, o);
bgneal@45 8649
bgneal@45 8650 return o;
bgneal@45 8651 },
bgneal@45 8652
bgneal@45 8653 load : function(n, u, cb, s) {
bgneal@45 8654 var t = this;
bgneal@45 8655
bgneal@45 8656 if (t.urls[n])
bgneal@45 8657 return;
bgneal@45 8658
bgneal@45 8659 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
bgneal@45 8660 u = tinymce.baseURL + '/' + u;
bgneal@45 8661
bgneal@45 8662 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
bgneal@45 8663 tinymce.ScriptLoader.add(u, cb, s);
bgneal@45 8664 }
bgneal@183 8665 });
bgneal@45 8666
bgneal@45 8667 // Create plugin and theme managers
bgneal@45 8668 tinymce.PluginManager = new tinymce.AddOnManager();
bgneal@45 8669 tinymce.ThemeManager = new tinymce.AddOnManager();
bgneal@183 8670 }(tinymce));
bgneal@183 8671
bgneal@183 8672 (function(tinymce) {
bgneal@45 8673 // Shorten names
bgneal@183 8674 var each = tinymce.each, extend = tinymce.extend,
bgneal@183 8675 DOM = tinymce.DOM, Event = tinymce.dom.Event,
bgneal@183 8676 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@183 8677 explode = tinymce.explode,
bgneal@183 8678 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
bgneal@183 8679
bgneal@183 8680 // Setup some URLs where the editor API is located and where the document is
bgneal@183 8681 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
bgneal@183 8682 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
bgneal@183 8683 tinymce.documentBaseURL += '/';
bgneal@183 8684
bgneal@183 8685 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
bgneal@183 8686
bgneal@183 8687 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
bgneal@183 8688
bgneal@183 8689 // Add before unload listener
bgneal@183 8690 // This was required since IE was leaking memory if you added and removed beforeunload listeners
bgneal@183 8691 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
bgneal@183 8692 tinymce.onBeforeUnload = new Dispatcher(tinymce);
bgneal@183 8693
bgneal@183 8694 // Must be on window or IE will leak if the editor is placed in frame or iframe
bgneal@183 8695 Event.add(window, 'beforeunload', function(e) {
bgneal@183 8696 tinymce.onBeforeUnload.dispatch(tinymce, e);
bgneal@183 8697 });
bgneal@183 8698
bgneal@183 8699 tinymce.onAddEditor = new Dispatcher(tinymce);
bgneal@183 8700
bgneal@183 8701 tinymce.onRemoveEditor = new Dispatcher(tinymce);
bgneal@183 8702
bgneal@183 8703 tinymce.EditorManager = extend(tinymce, {
bgneal@183 8704 editors : [],
bgneal@183 8705
bgneal@45 8706 i18n : {},
bgneal@183 8707
bgneal@45 8708 activeEditor : null,
bgneal@45 8709
bgneal@45 8710 init : function(s) {
bgneal@183 8711 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
bgneal@45 8712
bgneal@45 8713 function execCallback(se, n, s) {
bgneal@45 8714 var f = se[n];
bgneal@45 8715
bgneal@45 8716 if (!f)
bgneal@45 8717 return;
bgneal@45 8718
bgneal@45 8719 if (tinymce.is(f, 'string')) {
bgneal@45 8720 s = f.replace(/\.\w+$/, '');
bgneal@45 8721 s = s ? tinymce.resolve(s) : 0;
bgneal@45 8722 f = tinymce.resolve(f);
bgneal@45 8723 }
bgneal@45 8724
bgneal@45 8725 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
bgneal@45 8726 };
bgneal@45 8727
bgneal@45 8728 s = extend({
bgneal@45 8729 theme : "simple",
bgneal@183 8730 language : "en"
bgneal@45 8731 }, s);
bgneal@45 8732
bgneal@45 8733 t.settings = s;
bgneal@45 8734
bgneal@45 8735 // Legacy call
bgneal@45 8736 Event.add(document, 'init', function() {
bgneal@45 8737 var l, co;
bgneal@45 8738
bgneal@45 8739 execCallback(s, 'onpageload');
bgneal@45 8740
bgneal@45 8741 switch (s.mode) {
bgneal@45 8742 case "exact":
bgneal@45 8743 l = s.elements || '';
bgneal@45 8744
bgneal@45 8745 if(l.length > 0) {
bgneal@45 8746 each(explode(l), function(v) {
bgneal@45 8747 if (DOM.get(v)) {
bgneal@45 8748 ed = new tinymce.Editor(v, s);
bgneal@45 8749 el.push(ed);
bgneal@45 8750 ed.render(1);
bgneal@45 8751 } else {
bgneal@45 8752 each(document.forms, function(f) {
bgneal@45 8753 each(f.elements, function(e) {
bgneal@45 8754 if (e.name === v) {
bgneal@183 8755 v = 'mce_editor_' + instanceCounter++;
bgneal@45 8756 DOM.setAttrib(e, 'id', v);
bgneal@45 8757
bgneal@45 8758 ed = new tinymce.Editor(v, s);
bgneal@45 8759 el.push(ed);
bgneal@45 8760 ed.render(1);
bgneal@45 8761 }
bgneal@45 8762 });
bgneal@45 8763 });
bgneal@45 8764 }
bgneal@45 8765 });
bgneal@45 8766 }
bgneal@45 8767 break;
bgneal@45 8768
bgneal@45 8769 case "textareas":
bgneal@45 8770 case "specific_textareas":
bgneal@45 8771 function hasClass(n, c) {
bgneal@45 8772 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
bgneal@45 8773 };
bgneal@45 8774
bgneal@45 8775 each(DOM.select('textarea'), function(v) {
bgneal@45 8776 if (s.editor_deselector && hasClass(v, s.editor_deselector))
bgneal@45 8777 return;
bgneal@45 8778
bgneal@45 8779 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
bgneal@45 8780 // Can we use the name
bgneal@45 8781 e = DOM.get(v.name);
bgneal@45 8782 if (!v.id && !e)
bgneal@45 8783 v.id = v.name;
bgneal@45 8784
bgneal@45 8785 // Generate unique name if missing or already exists
bgneal@45 8786 if (!v.id || t.get(v.id))
bgneal@45 8787 v.id = DOM.uniqueId();
bgneal@45 8788
bgneal@45 8789 ed = new tinymce.Editor(v.id, s);
bgneal@45 8790 el.push(ed);
bgneal@45 8791 ed.render(1);
bgneal@45 8792 }
bgneal@45 8793 });
bgneal@45 8794 break;
bgneal@45 8795 }
bgneal@45 8796
bgneal@45 8797 // Call onInit when all editors are initialized
bgneal@45 8798 if (s.oninit) {
bgneal@45 8799 l = co = 0;
bgneal@45 8800
bgneal@183 8801 each(el, function(ed) {
bgneal@45 8802 co++;
bgneal@45 8803
bgneal@45 8804 if (!ed.initialized) {
bgneal@45 8805 // Wait for it
bgneal@45 8806 ed.onInit.add(function() {
bgneal@45 8807 l++;
bgneal@45 8808
bgneal@45 8809 // All done
bgneal@45 8810 if (l == co)
bgneal@45 8811 execCallback(s, 'oninit');
bgneal@45 8812 });
bgneal@45 8813 } else
bgneal@45 8814 l++;
bgneal@45 8815
bgneal@45 8816 // All done
bgneal@45 8817 if (l == co)
bgneal@45 8818 execCallback(s, 'oninit');
bgneal@45 8819 });
bgneal@45 8820 }
bgneal@45 8821 });
bgneal@45 8822 },
bgneal@45 8823
bgneal@45 8824 get : function(id) {
bgneal@183 8825 if (id === undefined)
bgneal@183 8826 return this.editors;
bgneal@183 8827
bgneal@45 8828 return this.editors[id];
bgneal@45 8829 },
bgneal@45 8830
bgneal@45 8831 getInstanceById : function(id) {
bgneal@45 8832 return this.get(id);
bgneal@45 8833 },
bgneal@45 8834
bgneal@183 8835 add : function(editor) {
bgneal@183 8836 var self = this, editors = self.editors;
bgneal@183 8837
bgneal@183 8838 // Add named and index editor instance
bgneal@183 8839 editors[editor.id] = editor;
bgneal@183 8840 editors.push(editor);
bgneal@183 8841
bgneal@183 8842 self._setActive(editor);
bgneal@183 8843 self.onAddEditor.dispatch(self, editor);
bgneal@183 8844
bgneal@183 8845
bgneal@183 8846 return editor;
bgneal@183 8847 },
bgneal@183 8848
bgneal@183 8849 remove : function(editor) {
bgneal@183 8850 var t = this, i, editors = t.editors;
bgneal@45 8851
bgneal@45 8852 // Not in the collection
bgneal@183 8853 if (!editors[editor.id])
bgneal@45 8854 return null;
bgneal@45 8855
bgneal@183 8856 delete editors[editor.id];
bgneal@183 8857
bgneal@183 8858 for (i = 0; i < editors.length; i++) {
bgneal@183 8859 if (editors[i] == editor) {
bgneal@183 8860 editors.splice(i, 1);
bgneal@183 8861 break;
bgneal@183 8862 }
bgneal@183 8863 }
bgneal@45 8864
bgneal@45 8865 // Select another editor since the active one was removed
bgneal@183 8866 if (t.activeEditor == editor)
bgneal@183 8867 t._setActive(editors[0]);
bgneal@183 8868
bgneal@183 8869 editor.destroy();
bgneal@183 8870 t.onRemoveEditor.dispatch(t, editor);
bgneal@183 8871
bgneal@183 8872 return editor;
bgneal@45 8873 },
bgneal@45 8874
bgneal@45 8875 execCommand : function(c, u, v) {
bgneal@45 8876 var t = this, ed = t.get(v), w;
bgneal@45 8877
bgneal@45 8878 // Manager commands
bgneal@45 8879 switch (c) {
bgneal@45 8880 case "mceFocus":
bgneal@45 8881 ed.focus();
bgneal@45 8882 return true;
bgneal@45 8883
bgneal@45 8884 case "mceAddEditor":
bgneal@45 8885 case "mceAddControl":
bgneal@45 8886 if (!t.get(v))
bgneal@45 8887 new tinymce.Editor(v, t.settings).render();
bgneal@45 8888
bgneal@45 8889 return true;
bgneal@45 8890
bgneal@45 8891 case "mceAddFrameControl":
bgneal@45 8892 w = v.window;
bgneal@45 8893
bgneal@45 8894 // Add tinyMCE global instance and tinymce namespace to specified window
bgneal@45 8895 w.tinyMCE = tinyMCE;
bgneal@45 8896 w.tinymce = tinymce;
bgneal@45 8897
bgneal@45 8898 tinymce.DOM.doc = w.document;
bgneal@45 8899 tinymce.DOM.win = w;
bgneal@45 8900
bgneal@45 8901 ed = new tinymce.Editor(v.element_id, v);
bgneal@45 8902 ed.render();
bgneal@45 8903
bgneal@45 8904 // Fix IE memory leaks
bgneal@45 8905 if (tinymce.isIE) {
bgneal@45 8906 function clr() {
bgneal@45 8907 ed.destroy();
bgneal@45 8908 w.detachEvent('onunload', clr);
bgneal@45 8909 w = w.tinyMCE = w.tinymce = null; // IE leak
bgneal@45 8910 };
bgneal@45 8911
bgneal@45 8912 w.attachEvent('onunload', clr);
bgneal@45 8913 }
bgneal@45 8914
bgneal@45 8915 v.page_window = null;
bgneal@45 8916
bgneal@45 8917 return true;
bgneal@45 8918
bgneal@45 8919 case "mceRemoveEditor":
bgneal@45 8920 case "mceRemoveControl":
bgneal@45 8921 if (ed)
bgneal@45 8922 ed.remove();
bgneal@45 8923
bgneal@45 8924 return true;
bgneal@45 8925
bgneal@45 8926 case 'mceToggleEditor':
bgneal@45 8927 if (!ed) {
bgneal@45 8928 t.execCommand('mceAddControl', 0, v);
bgneal@45 8929 return true;
bgneal@45 8930 }
bgneal@45 8931
bgneal@45 8932 if (ed.isHidden())
bgneal@45 8933 ed.show();
bgneal@45 8934 else
bgneal@45 8935 ed.hide();
bgneal@45 8936
bgneal@45 8937 return true;
bgneal@45 8938 }
bgneal@45 8939
bgneal@45 8940 // Run command on active editor
bgneal@45 8941 if (t.activeEditor)
bgneal@45 8942 return t.activeEditor.execCommand(c, u, v);
bgneal@45 8943
bgneal@45 8944 return false;
bgneal@45 8945 },
bgneal@45 8946
bgneal@45 8947 execInstanceCommand : function(id, c, u, v) {
bgneal@45 8948 var ed = this.get(id);
bgneal@45 8949
bgneal@45 8950 if (ed)
bgneal@45 8951 return ed.execCommand(c, u, v);
bgneal@45 8952
bgneal@45 8953 return false;
bgneal@45 8954 },
bgneal@45 8955
bgneal@45 8956 triggerSave : function() {
bgneal@45 8957 each(this.editors, function(e) {
bgneal@45 8958 e.save();
bgneal@45 8959 });
bgneal@45 8960 },
bgneal@45 8961
bgneal@45 8962 addI18n : function(p, o) {
bgneal@45 8963 var lo, i18n = this.i18n;
bgneal@45 8964
bgneal@45 8965 if (!tinymce.is(p, 'string')) {
bgneal@45 8966 each(p, function(o, lc) {
bgneal@45 8967 each(o, function(o, g) {
bgneal@45 8968 each(o, function(o, k) {
bgneal@45 8969 if (g === 'common')
bgneal@45 8970 i18n[lc + '.' + k] = o;
bgneal@45 8971 else
bgneal@45 8972 i18n[lc + '.' + g + '.' + k] = o;
bgneal@45 8973 });
bgneal@45 8974 });
bgneal@45 8975 });
bgneal@45 8976 } else {
bgneal@45 8977 each(o, function(o, k) {
bgneal@45 8978 i18n[p + '.' + k] = o;
bgneal@45 8979 });
bgneal@45 8980 }
bgneal@45 8981 },
bgneal@45 8982
bgneal@45 8983 // Private methods
bgneal@45 8984
bgneal@183 8985 _setActive : function(editor) {
bgneal@183 8986 this.selectedInstance = this.activeEditor = editor;
bgneal@45 8987 }
bgneal@183 8988 });
bgneal@45 8989 })(tinymce);
bgneal@45 8990
bgneal@45 8991 (function(tinymce) {
bgneal@183 8992 // Shorten these names
bgneal@183 8993 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
bgneal@183 8994 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
bgneal@183 8995 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
bgneal@183 8996 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@183 8997 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
bgneal@45 8998
bgneal@45 8999 tinymce.create('tinymce.Editor', {
bgneal@45 9000 Editor : function(id, s) {
bgneal@45 9001 var t = this;
bgneal@45 9002
bgneal@45 9003 t.id = t.editorId = id;
bgneal@183 9004
bgneal@45 9005 t.execCommands = {};
bgneal@45 9006 t.queryStateCommands = {};
bgneal@45 9007 t.queryValueCommands = {};
bgneal@183 9008
bgneal@183 9009 t.isNotDirty = false;
bgneal@183 9010
bgneal@45 9011 t.plugins = {};
bgneal@45 9012
bgneal@45 9013 // Add events to the editor
bgneal@45 9014 each([
bgneal@45 9015 'onPreInit',
bgneal@183 9016
bgneal@45 9017 'onBeforeRenderUI',
bgneal@183 9018
bgneal@45 9019 'onPostRender',
bgneal@183 9020
bgneal@45 9021 'onInit',
bgneal@183 9022
bgneal@45 9023 'onRemove',
bgneal@183 9024
bgneal@45 9025 'onActivate',
bgneal@183 9026
bgneal@45 9027 'onDeactivate',
bgneal@183 9028
bgneal@45 9029 'onClick',
bgneal@183 9030
bgneal@45 9031 'onEvent',
bgneal@183 9032
bgneal@45 9033 'onMouseUp',
bgneal@183 9034
bgneal@45 9035 'onMouseDown',
bgneal@183 9036
bgneal@45 9037 'onDblClick',
bgneal@183 9038
bgneal@45 9039 'onKeyDown',
bgneal@183 9040
bgneal@45 9041 'onKeyUp',
bgneal@183 9042
bgneal@45 9043 'onKeyPress',
bgneal@183 9044
bgneal@45 9045 'onContextMenu',
bgneal@183 9046
bgneal@45 9047 'onSubmit',
bgneal@183 9048
bgneal@45 9049 'onReset',
bgneal@183 9050
bgneal@45 9051 'onPaste',
bgneal@183 9052
bgneal@45 9053 'onPreProcess',
bgneal@183 9054
bgneal@45 9055 'onPostProcess',
bgneal@183 9056
bgneal@45 9057 'onBeforeSetContent',
bgneal@183 9058
bgneal@45 9059 'onBeforeGetContent',
bgneal@183 9060
bgneal@45 9061 'onSetContent',
bgneal@183 9062
bgneal@45 9063 'onGetContent',
bgneal@183 9064
bgneal@45 9065 'onLoadContent',
bgneal@183 9066
bgneal@45 9067 'onSaveContent',
bgneal@183 9068
bgneal@45 9069 'onNodeChange',
bgneal@183 9070
bgneal@45 9071 'onChange',
bgneal@183 9072
bgneal@45 9073 'onBeforeExecCommand',
bgneal@183 9074
bgneal@45 9075 'onExecCommand',
bgneal@183 9076
bgneal@45 9077 'onUndo',
bgneal@183 9078
bgneal@45 9079 'onRedo',
bgneal@183 9080
bgneal@45 9081 'onVisualAid',
bgneal@183 9082
bgneal@45 9083 'onSetProgressState'
bgneal@45 9084 ], function(e) {
bgneal@45 9085 t[e] = new Dispatcher(t);
bgneal@45 9086 });
bgneal@45 9087
bgneal@45 9088 t.settings = s = extend({
bgneal@45 9089 id : id,
bgneal@45 9090 language : 'en',
bgneal@45 9091 docs_language : 'en',
bgneal@45 9092 theme : 'simple',
bgneal@45 9093 skin : 'default',
bgneal@45 9094 delta_width : 0,
bgneal@45 9095 delta_height : 0,
bgneal@45 9096 popup_css : '',
bgneal@45 9097 plugins : '',
bgneal@45 9098 document_base_url : tinymce.documentBaseURL,
bgneal@45 9099 add_form_submit_trigger : 1,
bgneal@45 9100 submit_patch : 1,
bgneal@45 9101 add_unload_trigger : 1,
bgneal@45 9102 convert_urls : 1,
bgneal@45 9103 relative_urls : 1,
bgneal@45 9104 remove_script_host : 1,
bgneal@45 9105 table_inline_editing : 0,
bgneal@45 9106 object_resizing : 1,
bgneal@45 9107 cleanup : 1,
bgneal@45 9108 accessibility_focus : 1,
bgneal@45 9109 custom_shortcuts : 1,
bgneal@45 9110 custom_undo_redo_keyboard_shortcuts : 1,
bgneal@45 9111 custom_undo_redo_restore_selection : 1,
bgneal@45 9112 custom_undo_redo : 1,
bgneal@183 9113 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
bgneal@45 9114 visual_table_class : 'mceItemTable',
bgneal@45 9115 visual : 1,
bgneal@45 9116 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
bgneal@45 9117 apply_source_formatting : 1,
bgneal@45 9118 directionality : 'ltr',
bgneal@45 9119 forced_root_block : 'p',
bgneal@183 9120 valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
bgneal@45 9121 hidden_input : 1,
bgneal@45 9122 padd_empty_editor : 1,
bgneal@45 9123 render_ui : 1,
bgneal@45 9124 init_theme : 1,
bgneal@45 9125 force_p_newlines : 1,
bgneal@45 9126 indentation : '30px',
bgneal@45 9127 keep_styles : 1,
bgneal@45 9128 fix_table_elements : 1,
bgneal@183 9129 inline_styles : 1,
bgneal@183 9130 convert_fonts_to_spans : true
bgneal@45 9131 }, s);
bgneal@45 9132
bgneal@45 9133 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
bgneal@45 9134 base_uri : tinyMCE.baseURI
bgneal@45 9135 });
bgneal@183 9136
bgneal@183 9137 t.baseURI = tinymce.baseURI;
bgneal@45 9138
bgneal@45 9139 // Call setup
bgneal@45 9140 t.execCallback('setup', t);
bgneal@45 9141 },
bgneal@45 9142
bgneal@45 9143 render : function(nst) {
bgneal@45 9144 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
bgneal@45 9145
bgneal@45 9146 // Page is not loaded yet, wait for it
bgneal@45 9147 if (!Event.domLoaded) {
bgneal@45 9148 Event.add(document, 'init', function() {
bgneal@45 9149 t.render();
bgneal@45 9150 });
bgneal@45 9151 return;
bgneal@45 9152 }
bgneal@45 9153
bgneal@183 9154 tinyMCE.settings = s;
bgneal@45 9155
bgneal@45 9156 // Element not found, then skip initialization
bgneal@45 9157 if (!t.getElement())
bgneal@45 9158 return;
bgneal@45 9159
bgneal@45 9160 // Add hidden input for non input elements inside form elements
bgneal@45 9161 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
bgneal@45 9162 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
bgneal@45 9163
bgneal@45 9164 if (tinymce.WindowManager)
bgneal@45 9165 t.windowManager = new tinymce.WindowManager(t);
bgneal@45 9166
bgneal@45 9167 if (s.encoding == 'xml') {
bgneal@45 9168 t.onGetContent.add(function(ed, o) {
bgneal@45 9169 if (o.save)
bgneal@45 9170 o.content = DOM.encode(o.content);
bgneal@45 9171 });
bgneal@45 9172 }
bgneal@45 9173
bgneal@45 9174 if (s.add_form_submit_trigger) {
bgneal@45 9175 t.onSubmit.addToTop(function() {
bgneal@45 9176 if (t.initialized) {
bgneal@45 9177 t.save();
bgneal@45 9178 t.isNotDirty = 1;
bgneal@45 9179 }
bgneal@45 9180 });
bgneal@45 9181 }
bgneal@45 9182
bgneal@45 9183 if (s.add_unload_trigger) {
bgneal@45 9184 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
bgneal@45 9185 if (t.initialized && !t.destroyed && !t.isHidden())
bgneal@45 9186 t.save({format : 'raw', no_events : true});
bgneal@45 9187 });
bgneal@45 9188 }
bgneal@45 9189
bgneal@45 9190 tinymce.addUnload(t.destroy, t);
bgneal@45 9191
bgneal@45 9192 if (s.submit_patch) {
bgneal@45 9193 t.onBeforeRenderUI.add(function() {
bgneal@45 9194 var n = t.getElement().form;
bgneal@45 9195
bgneal@45 9196 if (!n)
bgneal@45 9197 return;
bgneal@45 9198
bgneal@45 9199 // Already patched
bgneal@45 9200 if (n._mceOldSubmit)
bgneal@45 9201 return;
bgneal@45 9202
bgneal@45 9203 // Check page uses id="submit" or name="submit" for it's submit button
bgneal@45 9204 if (!n.submit.nodeType && !n.submit.length) {
bgneal@45 9205 t.formElement = n;
bgneal@45 9206 n._mceOldSubmit = n.submit;
bgneal@45 9207 n.submit = function() {
bgneal@45 9208 // Save all instances
bgneal@183 9209 tinymce.triggerSave();
bgneal@45 9210 t.isNotDirty = 1;
bgneal@45 9211
bgneal@183 9212 return t.formElement._mceOldSubmit(t.formElement);
bgneal@45 9213 };
bgneal@45 9214 }
bgneal@45 9215
bgneal@45 9216 n = null;
bgneal@45 9217 });
bgneal@45 9218 }
bgneal@45 9219
bgneal@45 9220 // Load scripts
bgneal@45 9221 function loadScripts() {
bgneal@45 9222 if (s.language)
bgneal@45 9223 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
bgneal@45 9224
bgneal@45 9225 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
bgneal@45 9226 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
bgneal@45 9227
bgneal@45 9228 each(explode(s.plugins), function(p) {
bgneal@45 9229 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
bgneal@183 9230 // Skip safari plugin, since it is removed as of 3.3b1
bgneal@183 9231 if (p == 'safari')
bgneal@45 9232 return;
bgneal@45 9233
bgneal@45 9234 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
bgneal@45 9235 }
bgneal@45 9236 });
bgneal@45 9237
bgneal@45 9238 // Init when que is loaded
bgneal@45 9239 sl.loadQueue(function() {
bgneal@45 9240 if (!t.removed)
bgneal@45 9241 t.init();
bgneal@45 9242 });
bgneal@45 9243 };
bgneal@45 9244
bgneal@183 9245 loadScripts();
bgneal@45 9246 },
bgneal@45 9247
bgneal@45 9248 init : function() {
bgneal@45 9249 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
bgneal@45 9250
bgneal@183 9251 tinymce.add(t);
bgneal@183 9252
bgneal@45 9253 if (s.theme) {
bgneal@45 9254 s.theme = s.theme.replace(/-/, '');
bgneal@45 9255 o = ThemeManager.get(s.theme);
bgneal@45 9256 t.theme = new o();
bgneal@45 9257
bgneal@45 9258 if (t.theme.init && s.init_theme)
bgneal@45 9259 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
bgneal@45 9260 }
bgneal@45 9261
bgneal@45 9262 // Create all plugins
bgneal@45 9263 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
bgneal@45 9264 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
bgneal@45 9265
bgneal@45 9266 if (c) {
bgneal@45 9267 po = new c(t, u);
bgneal@45 9268
bgneal@45 9269 t.plugins[p] = po;
bgneal@45 9270
bgneal@45 9271 if (po.init)
bgneal@45 9272 po.init(t, u);
bgneal@45 9273 }
bgneal@45 9274 });
bgneal@45 9275
bgneal@45 9276 // Setup popup CSS path(s)
bgneal@45 9277 if (s.popup_css !== false) {
bgneal@45 9278 if (s.popup_css)
bgneal@45 9279 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
bgneal@45 9280 else
bgneal@45 9281 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
bgneal@45 9282 }
bgneal@45 9283
bgneal@45 9284 if (s.popup_css_add)
bgneal@45 9285 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
bgneal@45 9286
bgneal@45 9287 t.controlManager = new tinymce.ControlManager(t);
bgneal@45 9288
bgneal@45 9289 if (s.custom_undo_redo) {
bgneal@183 9290 // Add initial undo level
bgneal@183 9291 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@183 9292 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
bgneal@183 9293 if (!t.undoManager.hasUndo())
bgneal@183 9294 t.undoManager.add();
bgneal@183 9295 }
bgneal@183 9296 });
bgneal@183 9297
bgneal@45 9298 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@45 9299 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@45 9300 t.undoManager.add();
bgneal@45 9301 });
bgneal@45 9302 }
bgneal@45 9303
bgneal@45 9304 t.onExecCommand.add(function(ed, c) {
bgneal@45 9305 // Don't refresh the select lists until caret move
bgneal@45 9306 if (!/^(FontName|FontSize)$/.test(c))
bgneal@45 9307 t.nodeChanged();
bgneal@45 9308 });
bgneal@45 9309
bgneal@45 9310 // Remove ghost selections on images and tables in Gecko
bgneal@45 9311 if (isGecko) {
bgneal@45 9312 function repaint(a, o) {
bgneal@45 9313 if (!o || !o.initial)
bgneal@45 9314 t.execCommand('mceRepaint');
bgneal@45 9315 };
bgneal@45 9316
bgneal@45 9317 t.onUndo.add(repaint);
bgneal@45 9318 t.onRedo.add(repaint);
bgneal@45 9319 t.onSetContent.add(repaint);
bgneal@45 9320 }
bgneal@45 9321
bgneal@45 9322 // Enables users to override the control factory
bgneal@45 9323 t.onBeforeRenderUI.dispatch(t, t.controlManager);
bgneal@45 9324
bgneal@45 9325 // Measure box
bgneal@45 9326 if (s.render_ui) {
bgneal@45 9327 w = s.width || e.style.width || e.offsetWidth;
bgneal@45 9328 h = s.height || e.style.height || e.offsetHeight;
bgneal@45 9329 t.orgDisplay = e.style.display;
bgneal@45 9330 re = /^[0-9\.]+(|px)$/i;
bgneal@45 9331
bgneal@45 9332 if (re.test('' + w))
bgneal@45 9333 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
bgneal@45 9334
bgneal@45 9335 if (re.test('' + h))
bgneal@45 9336 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
bgneal@45 9337
bgneal@45 9338 // Render UI
bgneal@45 9339 o = t.theme.renderUI({
bgneal@45 9340 targetNode : e,
bgneal@45 9341 width : w,
bgneal@45 9342 height : h,
bgneal@45 9343 deltaWidth : s.delta_width,
bgneal@45 9344 deltaHeight : s.delta_height
bgneal@45 9345 });
bgneal@45 9346
bgneal@45 9347 t.editorContainer = o.editorContainer;
bgneal@45 9348 }
bgneal@45 9349
bgneal@45 9350
bgneal@183 9351 // User specified a document.domain value
bgneal@183 9352 if (document.domain && location.hostname != document.domain)
bgneal@183 9353 tinymce.relaxedDomain = document.domain;
bgneal@183 9354
bgneal@45 9355 // Resize editor
bgneal@45 9356 DOM.setStyles(o.sizeContainer || o.editorContainer, {
bgneal@45 9357 width : w,
bgneal@45 9358 height : h
bgneal@45 9359 });
bgneal@45 9360
bgneal@45 9361 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
bgneal@45 9362 if (h < 100)
bgneal@45 9363 h = 100;
bgneal@45 9364
bgneal@183 9365 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
bgneal@183 9366
bgneal@183 9367 // We only need to override paths if we have to
bgneal@183 9368 // IE has a bug where it remove site absolute urls to relative ones if this is specified
bgneal@183 9369 if (s.document_base_url != tinymce.documentBaseURL)
bgneal@183 9370 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
bgneal@183 9371
bgneal@183 9372 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
bgneal@45 9373
bgneal@45 9374 if (tinymce.relaxedDomain)
bgneal@45 9375 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
bgneal@45 9376
bgneal@45 9377 bi = s.body_id || 'tinymce';
bgneal@45 9378 if (bi.indexOf('=') != -1) {
bgneal@45 9379 bi = t.getParam('body_id', '', 'hash');
bgneal@45 9380 bi = bi[t.id] || bi;
bgneal@45 9381 }
bgneal@45 9382
bgneal@45 9383 bc = s.body_class || '';
bgneal@45 9384 if (bc.indexOf('=') != -1) {
bgneal@45 9385 bc = t.getParam('body_class', '', 'hash');
bgneal@45 9386 bc = bc[t.id] || '';
bgneal@45 9387 }
bgneal@45 9388
bgneal@45 9389 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
bgneal@45 9390
bgneal@45 9391 // Domain relaxing enabled, then set document domain
bgneal@45 9392 if (tinymce.relaxedDomain) {
bgneal@45 9393 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
bgneal@45 9394 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
bgneal@45 9395 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
bgneal@45 9396 else if (tinymce.isOpera)
bgneal@45 9397 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
bgneal@45 9398 }
bgneal@45 9399
bgneal@45 9400 // Create iframe
bgneal@45 9401 n = DOM.add(o.iframeContainer, 'iframe', {
bgneal@45 9402 id : t.id + "_ifr",
bgneal@45 9403 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
bgneal@45 9404 frameBorder : '0',
bgneal@45 9405 style : {
bgneal@45 9406 width : '100%',
bgneal@45 9407 height : h
bgneal@45 9408 }
bgneal@45 9409 });
bgneal@45 9410
bgneal@45 9411 t.contentAreaContainer = o.iframeContainer;
bgneal@45 9412 DOM.get(o.editorContainer).style.display = t.orgDisplay;
bgneal@45 9413 DOM.get(t.id).style.display = 'none';
bgneal@45 9414
bgneal@45 9415 if (!isIE || !tinymce.relaxedDomain)
bgneal@45 9416 t.setupIframe();
bgneal@45 9417
bgneal@45 9418 e = n = o = null; // Cleanup
bgneal@45 9419 },
bgneal@45 9420
bgneal@45 9421 setupIframe : function() {
bgneal@45 9422 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
bgneal@45 9423
bgneal@45 9424 // Setup iframe body
bgneal@45 9425 if (!isIE || !tinymce.relaxedDomain) {
bgneal@45 9426 d.open();
bgneal@45 9427 d.write(t.iframeHTML);
bgneal@45 9428 d.close();
bgneal@45 9429 }
bgneal@45 9430
bgneal@45 9431 // Design mode needs to be added here Ctrl+A will fail otherwise
bgneal@45 9432 if (!isIE) {
bgneal@45 9433 try {
bgneal@45 9434 if (!s.readonly)
bgneal@45 9435 d.designMode = 'On';
bgneal@45 9436 } catch (ex) {
bgneal@45 9437 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@45 9438 // The design mode will be set ones the editor is focused
bgneal@45 9439 }
bgneal@45 9440 }
bgneal@45 9441
bgneal@45 9442 // IE needs to use contentEditable or it will display non secure items for HTTPS
bgneal@45 9443 if (isIE) {
bgneal@45 9444 // It will not steal focus if we hide it while setting contentEditable
bgneal@45 9445 b = t.getBody();
bgneal@45 9446 DOM.hide(b);
bgneal@45 9447
bgneal@45 9448 if (!s.readonly)
bgneal@45 9449 b.contentEditable = true;
bgneal@45 9450
bgneal@45 9451 DOM.show(b);
bgneal@45 9452 }
bgneal@45 9453
bgneal@183 9454 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
bgneal@45 9455 keep_values : true,
bgneal@45 9456 url_converter : t.convertURL,
bgneal@45 9457 url_converter_scope : t,
bgneal@45 9458 hex_colors : s.force_hex_style_colors,
bgneal@45 9459 class_filter : s.class_filter,
bgneal@45 9460 update_styles : 1,
bgneal@183 9461 fix_ie_paragraphs : 1,
bgneal@183 9462 valid_styles : s.valid_styles
bgneal@183 9463 });
bgneal@183 9464
bgneal@183 9465 t.schema = new tinymce.dom.Schema();
bgneal@183 9466
bgneal@183 9467 t.serializer = new tinymce.dom.Serializer(extend(s, {
bgneal@45 9468 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
bgneal@183 9469 dom : t.dom,
bgneal@183 9470 schema : t.schema
bgneal@183 9471 }));
bgneal@45 9472
bgneal@45 9473 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
bgneal@183 9474
bgneal@183 9475 t.formatter = new tinymce.Formatter(this);
bgneal@183 9476
bgneal@183 9477 // Register default formats
bgneal@183 9478 t.formatter.register({
bgneal@183 9479 alignleft : [
bgneal@183 9480 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
bgneal@183 9481 {selector : 'img,table', styles : {'float' : 'left'}}
bgneal@183 9482 ],
bgneal@183 9483
bgneal@183 9484 aligncenter : [
bgneal@183 9485 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
bgneal@183 9486 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
bgneal@183 9487 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
bgneal@183 9488 ],
bgneal@183 9489
bgneal@183 9490 alignright : [
bgneal@183 9491 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
bgneal@183 9492 {selector : 'img,table', styles : {'float' : 'right'}}
bgneal@183 9493 ],
bgneal@183 9494
bgneal@183 9495 alignfull : [
bgneal@183 9496 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
bgneal@183 9497 ],
bgneal@183 9498
bgneal@183 9499 bold : [
bgneal@183 9500 {inline : 'strong'},
bgneal@183 9501 {inline : 'span', styles : {fontWeight : 'bold'}},
bgneal@183 9502 {inline : 'b'}
bgneal@183 9503 ],
bgneal@183 9504
bgneal@183 9505 italic : [
bgneal@183 9506 {inline : 'em'},
bgneal@183 9507 {inline : 'span', styles : {fontStyle : 'italic'}},
bgneal@183 9508 {inline : 'i'}
bgneal@183 9509 ],
bgneal@183 9510
bgneal@183 9511 underline : [
bgneal@183 9512 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
bgneal@183 9513 {inline : 'u'}
bgneal@183 9514 ],
bgneal@183 9515
bgneal@183 9516 strikethrough : [
bgneal@183 9517 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
bgneal@183 9518 {inline : 'u'}
bgneal@183 9519 ],
bgneal@183 9520
bgneal@183 9521 forecolor : {inline : 'span', styles : {color : '%value'}},
bgneal@183 9522 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
bgneal@183 9523 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
bgneal@183 9524 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
bgneal@183 9525 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
bgneal@183 9526
bgneal@183 9527 removeformat : [
bgneal@183 9528 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
bgneal@183 9529 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
bgneal@183 9530 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
bgneal@183 9531 ]
bgneal@183 9532 });
bgneal@183 9533
bgneal@183 9534 // Register default block formats
bgneal@183 9535 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
bgneal@183 9536 t.formatter.register(name, {block : name, remove : 'all'});
bgneal@183 9537 });
bgneal@183 9538
bgneal@183 9539 // Register user defined formats
bgneal@183 9540 t.formatter.register(t.settings.formats);
bgneal@183 9541
bgneal@183 9542 t.undoManager = new tinymce.UndoManager(t);
bgneal@183 9543
bgneal@183 9544 // Pass through
bgneal@183 9545 t.undoManager.onAdd.add(function(um, l) {
bgneal@183 9546 if (!l.initial)
bgneal@183 9547 return t.onChange.dispatch(t, l, um);
bgneal@183 9548 });
bgneal@183 9549
bgneal@183 9550 t.undoManager.onUndo.add(function(um, l) {
bgneal@183 9551 return t.onUndo.dispatch(t, l, um);
bgneal@183 9552 });
bgneal@183 9553
bgneal@183 9554 t.undoManager.onRedo.add(function(um, l) {
bgneal@183 9555 return t.onRedo.dispatch(t, l, um);
bgneal@183 9556 });
bgneal@183 9557
bgneal@45 9558 t.forceBlocks = new tinymce.ForceBlocks(t, {
bgneal@45 9559 forced_root_block : s.forced_root_block
bgneal@45 9560 });
bgneal@183 9561
bgneal@45 9562 t.editorCommands = new tinymce.EditorCommands(t);
bgneal@45 9563
bgneal@45 9564 // Pass through
bgneal@45 9565 t.serializer.onPreProcess.add(function(se, o) {
bgneal@45 9566 return t.onPreProcess.dispatch(t, o, se);
bgneal@45 9567 });
bgneal@45 9568
bgneal@45 9569 t.serializer.onPostProcess.add(function(se, o) {
bgneal@45 9570 return t.onPostProcess.dispatch(t, o, se);
bgneal@45 9571 });
bgneal@45 9572
bgneal@45 9573 t.onPreInit.dispatch(t);
bgneal@45 9574
bgneal@45 9575 if (!s.gecko_spellcheck)
bgneal@45 9576 t.getBody().spellcheck = 0;
bgneal@45 9577
bgneal@45 9578 if (!s.readonly)
bgneal@45 9579 t._addEvents();
bgneal@45 9580
bgneal@45 9581 t.controlManager.onPostRender.dispatch(t, t.controlManager);
bgneal@45 9582 t.onPostRender.dispatch(t);
bgneal@45 9583
bgneal@45 9584 if (s.directionality)
bgneal@45 9585 t.getBody().dir = s.directionality;
bgneal@45 9586
bgneal@45 9587 if (s.nowrap)
bgneal@45 9588 t.getBody().style.whiteSpace = "nowrap";
bgneal@45 9589
bgneal@45 9590 if (s.custom_elements) {
bgneal@45 9591 function handleCustom(ed, o) {
bgneal@45 9592 each(explode(s.custom_elements), function(v) {
bgneal@45 9593 var n;
bgneal@45 9594
bgneal@45 9595 if (v.indexOf('~') === 0) {
bgneal@45 9596 v = v.substring(1);
bgneal@45 9597 n = 'span';
bgneal@45 9598 } else
bgneal@45 9599 n = 'div';
bgneal@45 9600
bgneal@183 9601 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
bgneal@45 9602 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
bgneal@45 9603 });
bgneal@45 9604 };
bgneal@45 9605
bgneal@45 9606 t.onBeforeSetContent.add(handleCustom);
bgneal@45 9607 t.onPostProcess.add(function(ed, o) {
bgneal@45 9608 if (o.set)
bgneal@183 9609 handleCustom(ed, o);
bgneal@45 9610 });
bgneal@45 9611 }
bgneal@45 9612
bgneal@45 9613 if (s.handle_node_change_callback) {
bgneal@45 9614 t.onNodeChange.add(function(ed, cm, n) {
bgneal@45 9615 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
bgneal@45 9616 });
bgneal@45 9617 }
bgneal@45 9618
bgneal@45 9619 if (s.save_callback) {
bgneal@45 9620 t.onSaveContent.add(function(ed, o) {
bgneal@45 9621 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 9622
bgneal@45 9623 if (h)
bgneal@45 9624 o.content = h;
bgneal@45 9625 });
bgneal@45 9626 }
bgneal@45 9627
bgneal@45 9628 if (s.onchange_callback) {
bgneal@45 9629 t.onChange.add(function(ed, l) {
bgneal@45 9630 t.execCallback('onchange_callback', t, l);
bgneal@45 9631 });
bgneal@45 9632 }
bgneal@45 9633
bgneal@45 9634 if (s.convert_newlines_to_brs) {
bgneal@45 9635 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9636 if (o.initial)
bgneal@45 9637 o.content = o.content.replace(/\r?\n/g, '<br />');
bgneal@45 9638 });
bgneal@45 9639 }
bgneal@45 9640
bgneal@45 9641 if (s.fix_nesting && isIE) {
bgneal@45 9642 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9643 o.content = t._fixNesting(o.content);
bgneal@45 9644 });
bgneal@45 9645 }
bgneal@45 9646
bgneal@45 9647 if (s.preformatted) {
bgneal@45 9648 t.onPostProcess.add(function(ed, o) {
bgneal@45 9649 o.content = o.content.replace(/^\s*<pre.*?>/, '');
bgneal@45 9650 o.content = o.content.replace(/<\/pre>\s*$/, '');
bgneal@45 9651
bgneal@45 9652 if (o.set)
bgneal@45 9653 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
bgneal@45 9654 });
bgneal@45 9655 }
bgneal@45 9656
bgneal@45 9657 if (s.verify_css_classes) {
bgneal@45 9658 t.serializer.attribValueFilter = function(n, v) {
bgneal@45 9659 var s, cl;
bgneal@45 9660
bgneal@45 9661 if (n == 'class') {
bgneal@45 9662 // Build regexp for classes
bgneal@45 9663 if (!t.classesRE) {
bgneal@45 9664 cl = t.dom.getClasses();
bgneal@45 9665
bgneal@45 9666 if (cl.length > 0) {
bgneal@45 9667 s = '';
bgneal@45 9668
bgneal@45 9669 each (cl, function(o) {
bgneal@45 9670 s += (s ? '|' : '') + o['class'];
bgneal@45 9671 });
bgneal@45 9672
bgneal@45 9673 t.classesRE = new RegExp('(' + s + ')', 'gi');
bgneal@45 9674 }
bgneal@45 9675 }
bgneal@45 9676
bgneal@45 9677 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
bgneal@45 9678 }
bgneal@45 9679
bgneal@45 9680 return v;
bgneal@45 9681 };
bgneal@45 9682 }
bgneal@45 9683
bgneal@45 9684 if (s.cleanup_callback) {
bgneal@45 9685 t.onBeforeSetContent.add(function(ed, o) {
bgneal@45 9686 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 9687 });
bgneal@45 9688
bgneal@45 9689 t.onPreProcess.add(function(ed, o) {
bgneal@45 9690 if (o.set)
bgneal@45 9691 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
bgneal@45 9692
bgneal@45 9693 if (o.get)
bgneal@45 9694 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
bgneal@45 9695 });
bgneal@45 9696
bgneal@45 9697 t.onPostProcess.add(function(ed, o) {
bgneal@45 9698 if (o.set)
bgneal@45 9699 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@45 9700
bgneal@45 9701 if (o.get)
bgneal@45 9702 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
bgneal@45 9703 });
bgneal@45 9704 }
bgneal@45 9705
bgneal@45 9706 if (s.save_callback) {
bgneal@45 9707 t.onGetContent.add(function(ed, o) {
bgneal@45 9708 if (o.save)
bgneal@45 9709 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@45 9710 });
bgneal@45 9711 }
bgneal@45 9712
bgneal@45 9713 if (s.handle_event_callback) {
bgneal@45 9714 t.onEvent.add(function(ed, e, o) {
bgneal@45 9715 if (t.execCallback('handle_event_callback', e, ed, o) === false)
bgneal@45 9716 Event.cancel(e);
bgneal@45 9717 });
bgneal@45 9718 }
bgneal@45 9719
bgneal@45 9720 // Add visual aids when new contents is added
bgneal@45 9721 t.onSetContent.add(function() {
bgneal@45 9722 t.addVisual(t.getBody());
bgneal@45 9723 });
bgneal@45 9724
bgneal@45 9725 // Remove empty contents
bgneal@45 9726 if (s.padd_empty_editor) {
bgneal@45 9727 t.onPostProcess.add(function(ed, o) {
bgneal@45 9728 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
bgneal@45 9729 });
bgneal@45 9730 }
bgneal@45 9731
bgneal@45 9732 if (isGecko) {
bgneal@183 9733 // Fix gecko link bug, when a link is placed at the end of block elements there is
bgneal@183 9734 // no way to move the caret behind the link. This fix adds a bogus br element after the link
bgneal@45 9735 function fixLinks(ed, o) {
bgneal@45 9736 each(ed.dom.select('a'), function(n) {
bgneal@45 9737 var pn = n.parentNode;
bgneal@45 9738
bgneal@45 9739 if (ed.dom.isBlock(pn) && pn.lastChild === n)
bgneal@183 9740 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
bgneal@45 9741 });
bgneal@45 9742 };
bgneal@45 9743
bgneal@45 9744 t.onExecCommand.add(function(ed, cmd) {
bgneal@45 9745 if (cmd === 'CreateLink')
bgneal@45 9746 fixLinks(ed);
bgneal@45 9747 });
bgneal@45 9748
bgneal@45 9749 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
bgneal@183 9750
bgneal@183 9751 if (!s.readonly) {
bgneal@183 9752 try {
bgneal@183 9753 // Design mode must be set here once again to fix a bug where
bgneal@183 9754 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
bgneal@183 9755 d.designMode = 'Off';
bgneal@183 9756 d.designMode = 'On';
bgneal@183 9757 } catch (ex) {
bgneal@183 9758 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@183 9759 // The design mode will be set ones the editor is focused
bgneal@183 9760 }
bgneal@45 9761 }
bgneal@45 9762 }
bgneal@45 9763
bgneal@45 9764 // A small timeout was needed since firefox will remove. Bug: #1838304
bgneal@45 9765 setTimeout(function () {
bgneal@45 9766 if (t.removed)
bgneal@45 9767 return;
bgneal@45 9768
bgneal@45 9769 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
bgneal@45 9770 t.startContent = t.getContent({format : 'raw'});
bgneal@45 9771 t.initialized = true;
bgneal@45 9772
bgneal@45 9773 t.onInit.dispatch(t);
bgneal@45 9774 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
bgneal@45 9775 t.execCallback('init_instance_callback', t);
bgneal@45 9776 t.focus(true);
bgneal@45 9777 t.nodeChanged({initial : 1});
bgneal@45 9778
bgneal@45 9779 // Load specified content CSS last
bgneal@45 9780 if (s.content_css) {
bgneal@45 9781 tinymce.each(explode(s.content_css), function(u) {
bgneal@45 9782 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
bgneal@45 9783 });
bgneal@45 9784 }
bgneal@45 9785
bgneal@45 9786 // Handle auto focus
bgneal@45 9787 if (s.auto_focus) {
bgneal@45 9788 setTimeout(function () {
bgneal@183 9789 var ed = tinymce.get(s.auto_focus);
bgneal@45 9790
bgneal@45 9791 ed.selection.select(ed.getBody(), 1);
bgneal@45 9792 ed.selection.collapse(1);
bgneal@45 9793 ed.getWin().focus();
bgneal@45 9794 }, 100);
bgneal@45 9795 }
bgneal@45 9796 }, 1);
bgneal@45 9797
bgneal@45 9798 e = null;
bgneal@45 9799 },
bgneal@45 9800
bgneal@45 9801
bgneal@45 9802 focus : function(sf) {
bgneal@45 9803 var oed, t = this, ce = t.settings.content_editable;
bgneal@45 9804
bgneal@45 9805 if (!sf) {
bgneal@183 9806 // Is not content editable
bgneal@183 9807 if (!ce)
bgneal@45 9808 t.getWin().focus();
bgneal@45 9809
bgneal@45 9810 }
bgneal@45 9811
bgneal@183 9812 if (tinymce.activeEditor != t) {
bgneal@183 9813 if ((oed = tinymce.activeEditor) != null)
bgneal@45 9814 oed.onDeactivate.dispatch(oed, t);
bgneal@45 9815
bgneal@45 9816 t.onActivate.dispatch(t, oed);
bgneal@45 9817 }
bgneal@45 9818
bgneal@183 9819 tinymce._setActive(t);
bgneal@45 9820 },
bgneal@45 9821
bgneal@45 9822 execCallback : function(n) {
bgneal@45 9823 var t = this, f = t.settings[n], s;
bgneal@45 9824
bgneal@45 9825 if (!f)
bgneal@45 9826 return;
bgneal@45 9827
bgneal@45 9828 // Look through lookup
bgneal@45 9829 if (t.callbackLookup && (s = t.callbackLookup[n])) {
bgneal@45 9830 f = s.func;
bgneal@45 9831 s = s.scope;
bgneal@45 9832 }
bgneal@45 9833
bgneal@45 9834 if (is(f, 'string')) {
bgneal@45 9835 s = f.replace(/\.\w+$/, '');
bgneal@45 9836 s = s ? tinymce.resolve(s) : 0;
bgneal@45 9837 f = tinymce.resolve(f);
bgneal@45 9838 t.callbackLookup = t.callbackLookup || {};
bgneal@45 9839 t.callbackLookup[n] = {func : f, scope : s};
bgneal@45 9840 }
bgneal@45 9841
bgneal@45 9842 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
bgneal@45 9843 },
bgneal@45 9844
bgneal@45 9845 translate : function(s) {
bgneal@183 9846 var c = this.settings.language || 'en', i18n = tinymce.i18n;
bgneal@45 9847
bgneal@45 9848 if (!s)
bgneal@45 9849 return '';
bgneal@45 9850
bgneal@45 9851 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
bgneal@45 9852 return i18n[c + '.' + b] || '{#' + b + '}';
bgneal@45 9853 });
bgneal@45 9854 },
bgneal@45 9855
bgneal@45 9856 getLang : function(n, dv) {
bgneal@183 9857 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
bgneal@45 9858 },
bgneal@45 9859
bgneal@45 9860 getParam : function(n, dv, ty) {
bgneal@45 9861 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
bgneal@45 9862
bgneal@45 9863 if (ty === 'hash') {
bgneal@45 9864 o = {};
bgneal@45 9865
bgneal@45 9866 if (is(v, 'string')) {
bgneal@45 9867 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
bgneal@45 9868 v = v.split('=');
bgneal@45 9869
bgneal@45 9870 if (v.length > 1)
bgneal@45 9871 o[tr(v[0])] = tr(v[1]);
bgneal@45 9872 else
bgneal@45 9873 o[tr(v[0])] = tr(v);
bgneal@45 9874 });
bgneal@45 9875 } else
bgneal@45 9876 o = v;
bgneal@45 9877
bgneal@45 9878 return o;
bgneal@45 9879 }
bgneal@45 9880
bgneal@45 9881 return v;
bgneal@45 9882 },
bgneal@45 9883
bgneal@45 9884 nodeChanged : function(o) {
bgneal@183 9885 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
bgneal@45 9886
bgneal@45 9887 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
bgneal@45 9888 if (t.initialized) {
bgneal@183 9889 o = o || {};
bgneal@183 9890 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
bgneal@183 9891
bgneal@183 9892 // Get parents and add them to object
bgneal@183 9893 o.parents = [];
bgneal@183 9894 t.dom.getParent(n, function(node) {
bgneal@183 9895 if (node.nodeName == 'BODY')
bgneal@183 9896 return true;
bgneal@183 9897
bgneal@183 9898 o.parents.push(node);
bgneal@183 9899 });
bgneal@183 9900
bgneal@45 9901 t.onNodeChange.dispatch(
bgneal@45 9902 t,
bgneal@45 9903 o ? o.controlManager || t.controlManager : t.controlManager,
bgneal@183 9904 n,
bgneal@45 9905 s.isCollapsed(),
bgneal@45 9906 o
bgneal@45 9907 );
bgneal@45 9908 }
bgneal@45 9909 },
bgneal@45 9910
bgneal@45 9911 addButton : function(n, s) {
bgneal@45 9912 var t = this;
bgneal@45 9913
bgneal@45 9914 t.buttons = t.buttons || {};
bgneal@45 9915 t.buttons[n] = s;
bgneal@45 9916 },
bgneal@45 9917
bgneal@45 9918 addCommand : function(n, f, s) {
bgneal@45 9919 this.execCommands[n] = {func : f, scope : s || this};
bgneal@45 9920 },
bgneal@45 9921
bgneal@45 9922 addQueryStateHandler : function(n, f, s) {
bgneal@45 9923 this.queryStateCommands[n] = {func : f, scope : s || this};
bgneal@45 9924 },
bgneal@45 9925
bgneal@45 9926 addQueryValueHandler : function(n, f, s) {
bgneal@45 9927 this.queryValueCommands[n] = {func : f, scope : s || this};
bgneal@45 9928 },
bgneal@45 9929
bgneal@45 9930 addShortcut : function(pa, desc, cmd_func, sc) {
bgneal@45 9931 var t = this, c;
bgneal@45 9932
bgneal@45 9933 if (!t.settings.custom_shortcuts)
bgneal@45 9934 return false;
bgneal@45 9935
bgneal@45 9936 t.shortcuts = t.shortcuts || {};
bgneal@45 9937
bgneal@45 9938 if (is(cmd_func, 'string')) {
bgneal@45 9939 c = cmd_func;
bgneal@45 9940
bgneal@45 9941 cmd_func = function() {
bgneal@45 9942 t.execCommand(c, false, null);
bgneal@45 9943 };
bgneal@45 9944 }
bgneal@45 9945
bgneal@45 9946 if (is(cmd_func, 'object')) {
bgneal@45 9947 c = cmd_func;
bgneal@45 9948
bgneal@45 9949 cmd_func = function() {
bgneal@45 9950 t.execCommand(c[0], c[1], c[2]);
bgneal@45 9951 };
bgneal@45 9952 }
bgneal@45 9953
bgneal@45 9954 each(explode(pa), function(pa) {
bgneal@45 9955 var o = {
bgneal@45 9956 func : cmd_func,
bgneal@45 9957 scope : sc || this,
bgneal@45 9958 desc : desc,
bgneal@45 9959 alt : false,
bgneal@45 9960 ctrl : false,
bgneal@45 9961 shift : false
bgneal@45 9962 };
bgneal@45 9963
bgneal@45 9964 each(explode(pa, '+'), function(v) {
bgneal@45 9965 switch (v) {
bgneal@45 9966 case 'alt':
bgneal@45 9967 case 'ctrl':
bgneal@45 9968 case 'shift':
bgneal@45 9969 o[v] = true;
bgneal@45 9970 break;
bgneal@45 9971
bgneal@45 9972 default:
bgneal@45 9973 o.charCode = v.charCodeAt(0);
bgneal@45 9974 o.keyCode = v.toUpperCase().charCodeAt(0);
bgneal@45 9975 }
bgneal@45 9976 });
bgneal@45 9977
bgneal@45 9978 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
bgneal@45 9979 });
bgneal@45 9980
bgneal@45 9981 return true;
bgneal@45 9982 },
bgneal@45 9983
bgneal@45 9984 execCommand : function(cmd, ui, val, a) {
bgneal@45 9985 var t = this, s = 0, o, st;
bgneal@45 9986
bgneal@45 9987 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
bgneal@45 9988 t.focus();
bgneal@45 9989
bgneal@45 9990 o = {};
bgneal@45 9991 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
bgneal@45 9992 if (o.terminate)
bgneal@45 9993 return false;
bgneal@45 9994
bgneal@45 9995 // Command callback
bgneal@45 9996 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
bgneal@45 9997 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 9998 return true;
bgneal@45 9999 }
bgneal@45 10000
bgneal@45 10001 // Registred commands
bgneal@45 10002 if (o = t.execCommands[cmd]) {
bgneal@45 10003 st = o.func.call(o.scope, ui, val);
bgneal@45 10004
bgneal@45 10005 // Fall through on true
bgneal@45 10006 if (st !== true) {
bgneal@45 10007 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10008 return st;
bgneal@45 10009 }
bgneal@45 10010 }
bgneal@45 10011
bgneal@45 10012 // Plugin commands
bgneal@45 10013 each(t.plugins, function(p) {
bgneal@45 10014 if (p.execCommand && p.execCommand(cmd, ui, val)) {
bgneal@45 10015 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10016 s = 1;
bgneal@45 10017 return false;
bgneal@45 10018 }
bgneal@45 10019 });
bgneal@45 10020
bgneal@45 10021 if (s)
bgneal@45 10022 return true;
bgneal@45 10023
bgneal@45 10024 // Theme commands
bgneal@45 10025 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
bgneal@45 10026 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10027 return true;
bgneal@45 10028 }
bgneal@45 10029
bgneal@45 10030 // Execute global commands
bgneal@45 10031 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
bgneal@45 10032 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10033 return true;
bgneal@45 10034 }
bgneal@45 10035
bgneal@45 10036 // Editor commands
bgneal@45 10037 if (t.editorCommands.execCommand(cmd, ui, val)) {
bgneal@45 10038 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10039 return true;
bgneal@45 10040 }
bgneal@45 10041
bgneal@45 10042 // Browser commands
bgneal@45 10043 t.getDoc().execCommand(cmd, ui, val);
bgneal@45 10044 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@45 10045 },
bgneal@45 10046
bgneal@183 10047 queryCommandState : function(cmd) {
bgneal@45 10048 var t = this, o, s;
bgneal@45 10049
bgneal@45 10050 // Is hidden then return undefined
bgneal@45 10051 if (t._isHidden())
bgneal@45 10052 return;
bgneal@45 10053
bgneal@45 10054 // Registred commands
bgneal@183 10055 if (o = t.queryStateCommands[cmd]) {
bgneal@45 10056 s = o.func.call(o.scope);
bgneal@45 10057
bgneal@45 10058 // Fall though on true
bgneal@45 10059 if (s !== true)
bgneal@45 10060 return s;
bgneal@45 10061 }
bgneal@45 10062
bgneal@45 10063 // Registred commands
bgneal@183 10064 o = t.editorCommands.queryCommandState(cmd);
bgneal@45 10065 if (o !== -1)
bgneal@45 10066 return o;
bgneal@45 10067
bgneal@45 10068 // Browser commands
bgneal@45 10069 try {
bgneal@183 10070 return this.getDoc().queryCommandState(cmd);
bgneal@45 10071 } catch (ex) {
bgneal@45 10072 // Fails sometimes see bug: 1896577
bgneal@45 10073 }
bgneal@45 10074 },
bgneal@45 10075
bgneal@45 10076 queryCommandValue : function(c) {
bgneal@45 10077 var t = this, o, s;
bgneal@45 10078
bgneal@45 10079 // Is hidden then return undefined
bgneal@45 10080 if (t._isHidden())
bgneal@45 10081 return;
bgneal@45 10082
bgneal@45 10083 // Registred commands
bgneal@45 10084 if (o = t.queryValueCommands[c]) {
bgneal@45 10085 s = o.func.call(o.scope);
bgneal@45 10086
bgneal@45 10087 // Fall though on true
bgneal@45 10088 if (s !== true)
bgneal@45 10089 return s;
bgneal@45 10090 }
bgneal@45 10091
bgneal@45 10092 // Registred commands
bgneal@45 10093 o = t.editorCommands.queryCommandValue(c);
bgneal@45 10094 if (is(o))
bgneal@45 10095 return o;
bgneal@45 10096
bgneal@45 10097 // Browser commands
bgneal@45 10098 try {
bgneal@45 10099 return this.getDoc().queryCommandValue(c);
bgneal@45 10100 } catch (ex) {
bgneal@45 10101 // Fails sometimes see bug: 1896577
bgneal@45 10102 }
bgneal@45 10103 },
bgneal@45 10104
bgneal@45 10105 show : function() {
bgneal@45 10106 var t = this;
bgneal@45 10107
bgneal@45 10108 DOM.show(t.getContainer());
bgneal@45 10109 DOM.hide(t.id);
bgneal@45 10110 t.load();
bgneal@45 10111 },
bgneal@45 10112
bgneal@45 10113 hide : function() {
bgneal@45 10114 var t = this, d = t.getDoc();
bgneal@45 10115
bgneal@45 10116 // Fixed bug where IE has a blinking cursor left from the editor
bgneal@45 10117 if (isIE && d)
bgneal@45 10118 d.execCommand('SelectAll');
bgneal@45 10119
bgneal@45 10120 // We must save before we hide so Safari doesn't crash
bgneal@45 10121 t.save();
bgneal@45 10122 DOM.hide(t.getContainer());
bgneal@45 10123 DOM.setStyle(t.id, 'display', t.orgDisplay);
bgneal@45 10124 },
bgneal@45 10125
bgneal@45 10126 isHidden : function() {
bgneal@45 10127 return !DOM.isHidden(this.id);
bgneal@45 10128 },
bgneal@45 10129
bgneal@45 10130 setProgressState : function(b, ti, o) {
bgneal@45 10131 this.onSetProgressState.dispatch(this, b, ti, o);
bgneal@45 10132
bgneal@45 10133 return b;
bgneal@45 10134 },
bgneal@45 10135
bgneal@45 10136 load : function(o) {
bgneal@45 10137 var t = this, e = t.getElement(), h;
bgneal@45 10138
bgneal@45 10139 if (e) {
bgneal@45 10140 o = o || {};
bgneal@45 10141 o.load = true;
bgneal@45 10142
bgneal@45 10143 // Double encode existing entities in the value
bgneal@45 10144 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
bgneal@45 10145 o.element = e;
bgneal@45 10146
bgneal@45 10147 if (!o.no_events)
bgneal@45 10148 t.onLoadContent.dispatch(t, o);
bgneal@45 10149
bgneal@45 10150 o.element = e = null;
bgneal@45 10151
bgneal@45 10152 return h;
bgneal@45 10153 }
bgneal@45 10154 },
bgneal@45 10155
bgneal@45 10156 save : function(o) {
bgneal@45 10157 var t = this, e = t.getElement(), h, f;
bgneal@45 10158
bgneal@45 10159 if (!e || !t.initialized)
bgneal@45 10160 return;
bgneal@45 10161
bgneal@45 10162 o = o || {};
bgneal@45 10163 o.save = true;
bgneal@45 10164
bgneal@45 10165 // Add undo level will trigger onchange event
bgneal@45 10166 if (!o.no_events) {
bgneal@45 10167 t.undoManager.typing = 0;
bgneal@45 10168 t.undoManager.add();
bgneal@45 10169 }
bgneal@45 10170
bgneal@45 10171 o.element = e;
bgneal@45 10172 h = o.content = t.getContent(o);
bgneal@45 10173
bgneal@45 10174 if (!o.no_events)
bgneal@45 10175 t.onSaveContent.dispatch(t, o);
bgneal@45 10176
bgneal@45 10177 h = o.content;
bgneal@45 10178
bgneal@45 10179 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
bgneal@45 10180 e.innerHTML = h;
bgneal@45 10181
bgneal@45 10182 // Update hidden form element
bgneal@45 10183 if (f = DOM.getParent(t.id, 'form')) {
bgneal@45 10184 each(f.elements, function(e) {
bgneal@45 10185 if (e.name == t.id) {
bgneal@45 10186 e.value = h;
bgneal@45 10187 return false;
bgneal@45 10188 }
bgneal@45 10189 });
bgneal@45 10190 }
bgneal@45 10191 } else
bgneal@45 10192 e.value = h;
bgneal@45 10193
bgneal@45 10194 o.element = e = null;
bgneal@45 10195
bgneal@45 10196 return h;
bgneal@45 10197 },
bgneal@45 10198
bgneal@45 10199 setContent : function(h, o) {
bgneal@45 10200 var t = this;
bgneal@45 10201
bgneal@45 10202 o = o || {};
bgneal@45 10203 o.format = o.format || 'html';
bgneal@45 10204 o.set = true;
bgneal@45 10205 o.content = h;
bgneal@45 10206
bgneal@45 10207 if (!o.no_events)
bgneal@45 10208 t.onBeforeSetContent.dispatch(t, o);
bgneal@45 10209
bgneal@45 10210 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
bgneal@45 10211 // It will also be impossible to place the caret in the editor unless there is a BR element present
bgneal@45 10212 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
bgneal@183 10213 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
bgneal@45 10214 o.format = 'raw';
bgneal@45 10215 }
bgneal@45 10216
bgneal@45 10217 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
bgneal@45 10218
bgneal@45 10219 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@45 10220 o.getInner = true;
bgneal@45 10221 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
bgneal@45 10222 }
bgneal@45 10223
bgneal@45 10224 if (!o.no_events)
bgneal@45 10225 t.onSetContent.dispatch(t, o);
bgneal@45 10226
bgneal@45 10227 return o.content;
bgneal@45 10228 },
bgneal@45 10229
bgneal@45 10230 getContent : function(o) {
bgneal@45 10231 var t = this, h;
bgneal@45 10232
bgneal@45 10233 o = o || {};
bgneal@45 10234 o.format = o.format || 'html';
bgneal@45 10235 o.get = true;
bgneal@45 10236
bgneal@45 10237 if (!o.no_events)
bgneal@45 10238 t.onBeforeGetContent.dispatch(t, o);
bgneal@45 10239
bgneal@45 10240 if (o.format != 'raw' && t.settings.cleanup) {
bgneal@45 10241 o.getInner = true;
bgneal@45 10242 h = t.serializer.serialize(t.getBody(), o);
bgneal@45 10243 } else
bgneal@45 10244 h = t.getBody().innerHTML;
bgneal@45 10245
bgneal@45 10246 h = h.replace(/^\s*|\s*$/g, '');
bgneal@45 10247 o.content = h;
bgneal@45 10248
bgneal@45 10249 if (!o.no_events)
bgneal@45 10250 t.onGetContent.dispatch(t, o);
bgneal@45 10251
bgneal@45 10252 return o.content;
bgneal@45 10253 },
bgneal@45 10254
bgneal@45 10255 isDirty : function() {
bgneal@45 10256 var t = this;
bgneal@45 10257
bgneal@45 10258 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
bgneal@45 10259 },
bgneal@45 10260
bgneal@45 10261 getContainer : function() {
bgneal@45 10262 var t = this;
bgneal@45 10263
bgneal@45 10264 if (!t.container)
bgneal@45 10265 t.container = DOM.get(t.editorContainer || t.id + '_parent');
bgneal@45 10266
bgneal@45 10267 return t.container;
bgneal@45 10268 },
bgneal@45 10269
bgneal@45 10270 getContentAreaContainer : function() {
bgneal@45 10271 return this.contentAreaContainer;
bgneal@45 10272 },
bgneal@45 10273
bgneal@45 10274 getElement : function() {
bgneal@45 10275 return DOM.get(this.settings.content_element || this.id);
bgneal@45 10276 },
bgneal@45 10277
bgneal@45 10278 getWin : function() {
bgneal@45 10279 var t = this, e;
bgneal@45 10280
bgneal@45 10281 if (!t.contentWindow) {
bgneal@45 10282 e = DOM.get(t.id + "_ifr");
bgneal@45 10283
bgneal@45 10284 if (e)
bgneal@45 10285 t.contentWindow = e.contentWindow;
bgneal@45 10286 }
bgneal@45 10287
bgneal@45 10288 return t.contentWindow;
bgneal@45 10289 },
bgneal@45 10290
bgneal@45 10291 getDoc : function() {
bgneal@45 10292 var t = this, w;
bgneal@45 10293
bgneal@45 10294 if (!t.contentDocument) {
bgneal@45 10295 w = t.getWin();
bgneal@45 10296
bgneal@45 10297 if (w)
bgneal@45 10298 t.contentDocument = w.document;
bgneal@45 10299 }
bgneal@45 10300
bgneal@45 10301 return t.contentDocument;
bgneal@45 10302 },
bgneal@45 10303
bgneal@45 10304 getBody : function() {
bgneal@45 10305 return this.bodyElement || this.getDoc().body;
bgneal@45 10306 },
bgneal@45 10307
bgneal@45 10308 convertURL : function(u, n, e) {
bgneal@45 10309 var t = this, s = t.settings;
bgneal@45 10310
bgneal@45 10311 // Use callback instead
bgneal@45 10312 if (s.urlconverter_callback)
bgneal@45 10313 return t.execCallback('urlconverter_callback', u, e, true, n);
bgneal@45 10314
bgneal@45 10315 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
bgneal@45 10316 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
bgneal@45 10317 return u;
bgneal@45 10318
bgneal@45 10319 // Convert to relative
bgneal@45 10320 if (s.relative_urls)
bgneal@45 10321 return t.documentBaseURI.toRelative(u);
bgneal@45 10322
bgneal@45 10323 // Convert to absolute
bgneal@45 10324 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
bgneal@45 10325
bgneal@45 10326 return u;
bgneal@45 10327 },
bgneal@45 10328
bgneal@45 10329 addVisual : function(e) {
bgneal@45 10330 var t = this, s = t.settings;
bgneal@45 10331
bgneal@45 10332 e = e || t.getBody();
bgneal@45 10333
bgneal@45 10334 if (!is(t.hasVisual))
bgneal@45 10335 t.hasVisual = s.visual;
bgneal@45 10336
bgneal@45 10337 each(t.dom.select('table,a', e), function(e) {
bgneal@45 10338 var v;
bgneal@45 10339
bgneal@45 10340 switch (e.nodeName) {
bgneal@45 10341 case 'TABLE':
bgneal@45 10342 v = t.dom.getAttrib(e, 'border');
bgneal@45 10343
bgneal@45 10344 if (!v || v == '0') {
bgneal@45 10345 if (t.hasVisual)
bgneal@45 10346 t.dom.addClass(e, s.visual_table_class);
bgneal@45 10347 else
bgneal@45 10348 t.dom.removeClass(e, s.visual_table_class);
bgneal@45 10349 }
bgneal@45 10350
bgneal@45 10351 return;
bgneal@45 10352
bgneal@45 10353 case 'A':
bgneal@45 10354 v = t.dom.getAttrib(e, 'name');
bgneal@45 10355
bgneal@45 10356 if (v) {
bgneal@45 10357 if (t.hasVisual)
bgneal@45 10358 t.dom.addClass(e, 'mceItemAnchor');
bgneal@45 10359 else
bgneal@45 10360 t.dom.removeClass(e, 'mceItemAnchor');
bgneal@45 10361 }
bgneal@45 10362
bgneal@45 10363 return;
bgneal@45 10364 }
bgneal@45 10365 });
bgneal@45 10366
bgneal@45 10367 t.onVisualAid.dispatch(t, e, t.hasVisual);
bgneal@45 10368 },
bgneal@45 10369
bgneal@45 10370 remove : function() {
bgneal@45 10371 var t = this, e = t.getContainer();
bgneal@45 10372
bgneal@45 10373 t.removed = 1; // Cancels post remove event execution
bgneal@45 10374 t.hide();
bgneal@45 10375
bgneal@45 10376 t.execCallback('remove_instance_callback', t);
bgneal@45 10377 t.onRemove.dispatch(t);
bgneal@45 10378
bgneal@45 10379 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
bgneal@45 10380 t.onExecCommand.listeners = [];
bgneal@45 10381
bgneal@183 10382 tinymce.remove(t);
bgneal@45 10383 DOM.remove(e);
bgneal@45 10384 },
bgneal@45 10385
bgneal@45 10386 destroy : function(s) {
bgneal@45 10387 var t = this;
bgneal@45 10388
bgneal@45 10389 // One time is enough
bgneal@45 10390 if (t.destroyed)
bgneal@45 10391 return;
bgneal@45 10392
bgneal@45 10393 if (!s) {
bgneal@45 10394 tinymce.removeUnload(t.destroy);
bgneal@45 10395 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
bgneal@45 10396
bgneal@45 10397 // Manual destroy
bgneal@45 10398 if (t.theme && t.theme.destroy)
bgneal@45 10399 t.theme.destroy();
bgneal@45 10400
bgneal@45 10401 // Destroy controls, selection and dom
bgneal@45 10402 t.controlManager.destroy();
bgneal@45 10403 t.selection.destroy();
bgneal@45 10404 t.dom.destroy();
bgneal@45 10405
bgneal@45 10406 // Remove all events
bgneal@45 10407
bgneal@45 10408 // Don't clear the window or document if content editable
bgneal@45 10409 // is enabled since other instances might still be present
bgneal@45 10410 if (!t.settings.content_editable) {
bgneal@45 10411 Event.clear(t.getWin());
bgneal@45 10412 Event.clear(t.getDoc());
bgneal@45 10413 }
bgneal@45 10414
bgneal@45 10415 Event.clear(t.getBody());
bgneal@45 10416 Event.clear(t.formElement);
bgneal@45 10417 }
bgneal@45 10418
bgneal@45 10419 if (t.formElement) {
bgneal@45 10420 t.formElement.submit = t.formElement._mceOldSubmit;
bgneal@45 10421 t.formElement._mceOldSubmit = null;
bgneal@45 10422 }
bgneal@45 10423
bgneal@45 10424 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
bgneal@45 10425
bgneal@45 10426 if (t.selection)
bgneal@45 10427 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
bgneal@45 10428
bgneal@45 10429 t.destroyed = 1;
bgneal@45 10430 },
bgneal@45 10431
bgneal@45 10432 // Internal functions
bgneal@45 10433
bgneal@45 10434 _addEvents : function() {
bgneal@45 10435 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
bgneal@45 10436 var t = this, i, s = t.settings, lo = {
bgneal@45 10437 mouseup : 'onMouseUp',
bgneal@45 10438 mousedown : 'onMouseDown',
bgneal@45 10439 click : 'onClick',
bgneal@45 10440 keyup : 'onKeyUp',
bgneal@45 10441 keydown : 'onKeyDown',
bgneal@45 10442 keypress : 'onKeyPress',
bgneal@45 10443 submit : 'onSubmit',
bgneal@45 10444 reset : 'onReset',
bgneal@45 10445 contextmenu : 'onContextMenu',
bgneal@45 10446 dblclick : 'onDblClick',
bgneal@45 10447 paste : 'onPaste' // Doesn't work in all browsers yet
bgneal@45 10448 };
bgneal@45 10449
bgneal@45 10450 function eventHandler(e, o) {
bgneal@45 10451 var ty = e.type;
bgneal@45 10452
bgneal@45 10453 // Don't fire events when it's removed
bgneal@45 10454 if (t.removed)
bgneal@45 10455 return;
bgneal@45 10456
bgneal@45 10457 // Generic event handler
bgneal@45 10458 if (t.onEvent.dispatch(t, e, o) !== false) {
bgneal@45 10459 // Specific event handler
bgneal@45 10460 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
bgneal@45 10461 }
bgneal@45 10462 };
bgneal@45 10463
bgneal@45 10464 // Add DOM events
bgneal@45 10465 each(lo, function(v, k) {
bgneal@45 10466 switch (k) {
bgneal@45 10467 case 'contextmenu':
bgneal@45 10468 if (tinymce.isOpera) {
bgneal@45 10469 // Fake contextmenu on Opera
bgneal@183 10470 t.dom.bind(t.getBody(), 'mousedown', function(e) {
bgneal@45 10471 if (e.ctrlKey) {
bgneal@45 10472 e.fakeType = 'contextmenu';
bgneal@45 10473 eventHandler(e);
bgneal@45 10474 }
bgneal@45 10475 });
bgneal@45 10476 } else
bgneal@183 10477 t.dom.bind(t.getBody(), k, eventHandler);
bgneal@45 10478 break;
bgneal@45 10479
bgneal@45 10480 case 'paste':
bgneal@183 10481 t.dom.bind(t.getBody(), k, function(e) {
bgneal@183 10482 eventHandler(e);
bgneal@45 10483 });
bgneal@45 10484 break;
bgneal@45 10485
bgneal@45 10486 case 'submit':
bgneal@45 10487 case 'reset':
bgneal@183 10488 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
bgneal@45 10489 break;
bgneal@45 10490
bgneal@45 10491 default:
bgneal@183 10492 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
bgneal@183 10493 }
bgneal@183 10494 });
bgneal@183 10495
bgneal@183 10496 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
bgneal@45 10497 t.focus(true);
bgneal@45 10498 });
bgneal@45 10499
bgneal@45 10500
bgneal@45 10501 // Fixes bug where a specified document_base_uri could result in broken images
bgneal@45 10502 // This will also fix drag drop of images in Gecko
bgneal@45 10503 if (tinymce.isGecko) {
bgneal@45 10504 // Convert all images to absolute URLs
bgneal@45 10505 /* t.onSetContent.add(function(ed, o) {
bgneal@45 10506 each(ed.dom.select('img'), function(e) {
bgneal@45 10507 var v;
bgneal@45 10508
bgneal@183 10509 if (v = e.getAttribute('_mce_src'))
bgneal@45 10510 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@45 10511 })
bgneal@45 10512 });*/
bgneal@45 10513
bgneal@183 10514 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
bgneal@45 10515 var v;
bgneal@45 10516
bgneal@45 10517 e = e.target;
bgneal@45 10518
bgneal@183 10519 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
bgneal@45 10520 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@45 10521 });
bgneal@45 10522 }
bgneal@45 10523
bgneal@45 10524 // Set various midas options in Gecko
bgneal@45 10525 if (isGecko) {
bgneal@45 10526 function setOpts() {
bgneal@45 10527 var t = this, d = t.getDoc(), s = t.settings;
bgneal@45 10528
bgneal@45 10529 if (isGecko && !s.readonly) {
bgneal@45 10530 if (t._isHidden()) {
bgneal@45 10531 try {
bgneal@45 10532 if (!s.content_editable)
bgneal@45 10533 d.designMode = 'On';
bgneal@45 10534 } catch (ex) {
bgneal@45 10535 // Fails if it's hidden
bgneal@45 10536 }
bgneal@45 10537 }
bgneal@45 10538
bgneal@45 10539 try {
bgneal@45 10540 // Try new Gecko method
bgneal@45 10541 d.execCommand("styleWithCSS", 0, false);
bgneal@45 10542 } catch (ex) {
bgneal@45 10543 // Use old method
bgneal@45 10544 if (!t._isHidden())
bgneal@45 10545 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
bgneal@45 10546 }
bgneal@45 10547
bgneal@45 10548 if (!s.table_inline_editing)
bgneal@45 10549 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
bgneal@45 10550
bgneal@45 10551 if (!s.object_resizing)
bgneal@45 10552 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
bgneal@45 10553 }
bgneal@45 10554 };
bgneal@45 10555
bgneal@45 10556 t.onBeforeExecCommand.add(setOpts);
bgneal@45 10557 t.onMouseDown.add(setOpts);
bgneal@45 10558 }
bgneal@45 10559
bgneal@183 10560 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
bgneal@183 10561 // WebKit can't even do simple things like selecting an image
bgneal@183 10562 // This also fixes so it's possible to select mceItemAnchors
bgneal@183 10563 if (tinymce.isWebKit) {
bgneal@183 10564 t.onClick.add(function(ed, e) {
bgneal@183 10565 e = e.target;
bgneal@183 10566
bgneal@183 10567 // Needs tobe the setBaseAndExtend or it will fail to select floated images
bgneal@183 10568 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
bgneal@183 10569 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
bgneal@183 10570 });
bgneal@183 10571 }
bgneal@183 10572
bgneal@45 10573 // Add node change handlers
bgneal@45 10574 t.onMouseUp.add(t.nodeChanged);
bgneal@45 10575 t.onClick.add(t.nodeChanged);
bgneal@45 10576 t.onKeyUp.add(function(ed, e) {
bgneal@45 10577 var c = e.keyCode;
bgneal@45 10578
bgneal@45 10579 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
bgneal@45 10580 t.nodeChanged();
bgneal@45 10581 });
bgneal@45 10582
bgneal@45 10583 // Add reset handler
bgneal@45 10584 t.onReset.add(function() {
bgneal@45 10585 t.setContent(t.startContent, {format : 'raw'});
bgneal@45 10586 });
bgneal@45 10587
bgneal@45 10588 // Add shortcuts
bgneal@45 10589 if (s.custom_shortcuts) {
bgneal@45 10590 if (s.custom_undo_redo_keyboard_shortcuts) {
bgneal@45 10591 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
bgneal@45 10592 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
bgneal@45 10593 }
bgneal@45 10594
bgneal@45 10595 // Add default shortcuts for gecko
bgneal@45 10596 if (isGecko) {
bgneal@45 10597 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
bgneal@45 10598 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
bgneal@45 10599 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
bgneal@45 10600 }
bgneal@45 10601
bgneal@45 10602 // BlockFormat shortcuts keys
bgneal@45 10603 for (i=1; i<=6; i++)
bgneal@183 10604 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
bgneal@45 10605
bgneal@45 10606 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
bgneal@45 10607 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
bgneal@45 10608 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
bgneal@45 10609
bgneal@45 10610 function find(e) {
bgneal@45 10611 var v = null;
bgneal@45 10612
bgneal@45 10613 if (!e.altKey && !e.ctrlKey && !e.metaKey)
bgneal@45 10614 return v;
bgneal@45 10615
bgneal@45 10616 each(t.shortcuts, function(o) {
bgneal@45 10617 if (tinymce.isMac && o.ctrl != e.metaKey)
bgneal@45 10618 return;
bgneal@45 10619 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
bgneal@45 10620 return;
bgneal@45 10621
bgneal@45 10622 if (o.alt != e.altKey)
bgneal@45 10623 return;
bgneal@45 10624
bgneal@45 10625 if (o.shift != e.shiftKey)
bgneal@45 10626 return;
bgneal@45 10627
bgneal@45 10628 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
bgneal@45 10629 v = o;
bgneal@45 10630 return false;
bgneal@45 10631 }
bgneal@45 10632 });
bgneal@45 10633
bgneal@45 10634 return v;
bgneal@45 10635 };
bgneal@45 10636
bgneal@45 10637 t.onKeyUp.add(function(ed, e) {
bgneal@45 10638 var o = find(e);
bgneal@45 10639
bgneal@45 10640 if (o)
bgneal@45 10641 return Event.cancel(e);
bgneal@45 10642 });
bgneal@45 10643
bgneal@45 10644 t.onKeyPress.add(function(ed, e) {
bgneal@45 10645 var o = find(e);
bgneal@45 10646
bgneal@45 10647 if (o)
bgneal@45 10648 return Event.cancel(e);
bgneal@45 10649 });
bgneal@45 10650
bgneal@45 10651 t.onKeyDown.add(function(ed, e) {
bgneal@45 10652 var o = find(e);
bgneal@45 10653
bgneal@45 10654 if (o) {
bgneal@45 10655 o.func.call(o.scope);
bgneal@45 10656 return Event.cancel(e);
bgneal@45 10657 }
bgneal@45 10658 });
bgneal@45 10659 }
bgneal@45 10660
bgneal@45 10661 if (tinymce.isIE) {
bgneal@45 10662 // Fix so resize will only update the width and height attributes not the styles of an image
bgneal@45 10663 // It will also block mceItemNoResize items
bgneal@183 10664 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
bgneal@45 10665 var re = t.resizeInfo, cb;
bgneal@45 10666
bgneal@45 10667 e = e.target;
bgneal@45 10668
bgneal@45 10669 // Don't do this action for non image elements
bgneal@45 10670 if (e.nodeName !== 'IMG')
bgneal@45 10671 return;
bgneal@45 10672
bgneal@45 10673 if (re)
bgneal@183 10674 t.dom.unbind(re.node, re.ev, re.cb);
bgneal@45 10675
bgneal@45 10676 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
bgneal@45 10677 ev = 'resizeend';
bgneal@183 10678 cb = t.dom.bind(e, ev, function(e) {
bgneal@45 10679 var v;
bgneal@45 10680
bgneal@45 10681 e = e.target;
bgneal@45 10682
bgneal@45 10683 if (v = t.dom.getStyle(e, 'width')) {
bgneal@45 10684 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
bgneal@45 10685 t.dom.setStyle(e, 'width', '');
bgneal@45 10686 }
bgneal@45 10687
bgneal@45 10688 if (v = t.dom.getStyle(e, 'height')) {
bgneal@45 10689 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
bgneal@45 10690 t.dom.setStyle(e, 'height', '');
bgneal@45 10691 }
bgneal@45 10692 });
bgneal@45 10693 } else {
bgneal@45 10694 ev = 'resizestart';
bgneal@183 10695 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
bgneal@45 10696 }
bgneal@45 10697
bgneal@45 10698 re = t.resizeInfo = {
bgneal@45 10699 node : e,
bgneal@45 10700 ev : ev,
bgneal@45 10701 cb : cb
bgneal@45 10702 };
bgneal@45 10703 });
bgneal@45 10704
bgneal@45 10705 t.onKeyDown.add(function(ed, e) {
bgneal@45 10706 switch (e.keyCode) {
bgneal@45 10707 case 8:
bgneal@45 10708 // Fix IE control + backspace browser bug
bgneal@45 10709 if (t.selection.getRng().item) {
bgneal@183 10710 ed.dom.remove(t.selection.getRng().item(0));
bgneal@45 10711 return Event.cancel(e);
bgneal@45 10712 }
bgneal@45 10713 }
bgneal@45 10714 });
bgneal@183 10715
bgneal@183 10716 /*if (t.dom.boxModel) {
bgneal@183 10717 t.getBody().style.height = '100%';
bgneal@183 10718
bgneal@183 10719 Event.add(t.getWin(), 'resize', function(e) {
bgneal@183 10720 var docElm = t.getDoc().documentElement;
bgneal@183 10721
bgneal@183 10722 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
bgneal@183 10723 });
bgneal@183 10724 }*/
bgneal@45 10725 }
bgneal@45 10726
bgneal@45 10727 if (tinymce.isOpera) {
bgneal@45 10728 t.onClick.add(function(ed, e) {
bgneal@45 10729 Event.prevent(e);
bgneal@45 10730 });
bgneal@45 10731 }
bgneal@45 10732
bgneal@45 10733 // Add custom undo/redo handlers
bgneal@45 10734 if (s.custom_undo_redo) {
bgneal@45 10735 function addUndo() {
bgneal@45 10736 t.undoManager.typing = 0;
bgneal@45 10737 t.undoManager.add();
bgneal@45 10738 };
bgneal@45 10739
bgneal@183 10740 t.dom.bind(t.getDoc(), 'focusout', function(e) {
bgneal@183 10741 if (!t.removed && t.undoManager.typing)
bgneal@183 10742 addUndo();
bgneal@183 10743 });
bgneal@45 10744
bgneal@45 10745 t.onKeyUp.add(function(ed, e) {
bgneal@183 10746 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
bgneal@183 10747 addUndo();
bgneal@45 10748 });
bgneal@45 10749
bgneal@45 10750 t.onKeyDown.add(function(ed, e) {
bgneal@45 10751 // Is caracter positon keys
bgneal@45 10752 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
bgneal@183 10753 if (t.undoManager.typing)
bgneal@183 10754 addUndo();
bgneal@45 10755
bgneal@45 10756 return;
bgneal@45 10757 }
bgneal@45 10758
bgneal@45 10759 if (!t.undoManager.typing) {
bgneal@45 10760 t.undoManager.add();
bgneal@45 10761 t.undoManager.typing = 1;
bgneal@45 10762 }
bgneal@45 10763 });
bgneal@183 10764
bgneal@183 10765 t.onMouseDown.add(function() {
bgneal@183 10766 if (t.undoManager.typing)
bgneal@183 10767 addUndo();
bgneal@183 10768 });
bgneal@183 10769 }
bgneal@45 10770 },
bgneal@45 10771
bgneal@45 10772 _isHidden : function() {
bgneal@45 10773 var s;
bgneal@45 10774
bgneal@45 10775 if (!isGecko)
bgneal@45 10776 return 0;
bgneal@45 10777
bgneal@45 10778 // Weird, wheres that cursor selection?
bgneal@45 10779 s = this.selection.getSel();
bgneal@45 10780 return (!s || !s.rangeCount || s.rangeCount == 0);
bgneal@45 10781 },
bgneal@45 10782
bgneal@45 10783 // Fix for bug #1867292
bgneal@45 10784 _fixNesting : function(s) {
bgneal@45 10785 var d = [], i;
bgneal@45 10786
bgneal@45 10787 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
bgneal@45 10788 var e;
bgneal@45 10789
bgneal@45 10790 // Handle end element
bgneal@45 10791 if (b === '/') {
bgneal@45 10792 if (!d.length)
bgneal@45 10793 return '';
bgneal@45 10794
bgneal@45 10795 if (c !== d[d.length - 1].tag) {
bgneal@45 10796 for (i=d.length - 1; i>=0; i--) {
bgneal@45 10797 if (d[i].tag === c) {
bgneal@45 10798 d[i].close = 1;
bgneal@45 10799 break;
bgneal@45 10800 }
bgneal@45 10801 }
bgneal@45 10802
bgneal@45 10803 return '';
bgneal@45 10804 } else {
bgneal@45 10805 d.pop();
bgneal@45 10806
bgneal@45 10807 if (d.length && d[d.length - 1].close) {
bgneal@45 10808 a = a + '</' + d[d.length - 1].tag + '>';
bgneal@45 10809 d.pop();
bgneal@45 10810 }
bgneal@45 10811 }
bgneal@45 10812 } else {
bgneal@45 10813 // Ignore these
bgneal@45 10814 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
bgneal@45 10815 return a;
bgneal@45 10816
bgneal@45 10817 // Ignore closed ones
bgneal@45 10818 if (/\/>$/.test(a))
bgneal@45 10819 return a;
bgneal@45 10820
bgneal@45 10821 d.push({tag : c}); // Push start element
bgneal@45 10822 }
bgneal@45 10823
bgneal@45 10824 return a;
bgneal@45 10825 });
bgneal@45 10826
bgneal@45 10827 // End all open tags
bgneal@45 10828 for (i=d.length - 1; i>=0; i--)
bgneal@45 10829 s += '</' + d[i].tag + '>';
bgneal@45 10830
bgneal@45 10831 return s;
bgneal@45 10832 }
bgneal@183 10833 });
bgneal@183 10834 })(tinymce);
bgneal@183 10835
bgneal@183 10836 (function(tinymce) {
bgneal@183 10837 // Added for compression purposes
bgneal@183 10838 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
bgneal@183 10839
bgneal@183 10840 tinymce.EditorCommands = function(editor) {
bgneal@183 10841 var dom = editor.dom,
bgneal@183 10842 selection = editor.selection,
bgneal@183 10843 commands = {state: {}, exec : {}, value : {}},
bgneal@183 10844 settings = editor.settings,
bgneal@183 10845 bookmark;
bgneal@183 10846
bgneal@183 10847 function execCommand(command, ui, value) {
bgneal@183 10848 var func;
bgneal@183 10849
bgneal@183 10850 command = command.toLowerCase();
bgneal@183 10851 if (func = commands.exec[command]) {
bgneal@183 10852 func(command, ui, value);
bgneal@183 10853 return TRUE;
bgneal@183 10854 }
bgneal@183 10855
bgneal@183 10856 return FALSE;
bgneal@183 10857 };
bgneal@183 10858
bgneal@183 10859 function queryCommandState(command) {
bgneal@183 10860 var func;
bgneal@183 10861
bgneal@183 10862 command = command.toLowerCase();
bgneal@183 10863 if (func = commands.state[command])
bgneal@183 10864 return func(command);
bgneal@183 10865
bgneal@183 10866 return -1;
bgneal@183 10867 };
bgneal@183 10868
bgneal@183 10869 function queryCommandValue(command) {
bgneal@183 10870 var func;
bgneal@183 10871
bgneal@183 10872 command = command.toLowerCase();
bgneal@183 10873 if (func = commands.value[command])
bgneal@183 10874 return func(command);
bgneal@183 10875
bgneal@183 10876 return FALSE;
bgneal@183 10877 };
bgneal@183 10878
bgneal@183 10879 function addCommands(command_list, type) {
bgneal@183 10880 type = type || 'exec';
bgneal@183 10881
bgneal@183 10882 each(command_list, function(callback, command) {
bgneal@183 10883 each(command.toLowerCase().split(','), function(command) {
bgneal@183 10884 commands[type][command] = callback;
bgneal@183 10885 });
bgneal@183 10886 });
bgneal@183 10887 };
bgneal@183 10888
bgneal@183 10889 // Expose public methods
bgneal@183 10890 tinymce.extend(this, {
bgneal@183 10891 execCommand : execCommand,
bgneal@183 10892 queryCommandState : queryCommandState,
bgneal@183 10893 queryCommandValue : queryCommandValue,
bgneal@183 10894 addCommands : addCommands
bgneal@45 10895 });
bgneal@183 10896
bgneal@183 10897 // Private methods
bgneal@183 10898
bgneal@183 10899 function execNativeCommand(command, ui, value) {
bgneal@183 10900 if (ui === undefined)
bgneal@183 10901 ui = FALSE;
bgneal@183 10902
bgneal@183 10903 if (value === undefined)
bgneal@183 10904 value = null;
bgneal@183 10905
bgneal@183 10906 return editor.getDoc().execCommand(command, ui, value);
bgneal@183 10907 };
bgneal@183 10908
bgneal@183 10909 function isFormatMatch(name) {
bgneal@183 10910 return editor.formatter.match(name);
bgneal@183 10911 };
bgneal@183 10912
bgneal@183 10913 function toggleFormat(name, value) {
bgneal@183 10914 editor.formatter.toggle(name, value ? {value : value} : undefined);
bgneal@183 10915 };
bgneal@183 10916
bgneal@183 10917 function storeSelection(type) {
bgneal@183 10918 bookmark = selection.getBookmark(type);
bgneal@183 10919 };
bgneal@183 10920
bgneal@183 10921 function restoreSelection() {
bgneal@183 10922 selection.moveToBookmark(bookmark);
bgneal@183 10923 };
bgneal@183 10924
bgneal@183 10925 // Add execCommand overrides
bgneal@183 10926 addCommands({
bgneal@183 10927 // Ignore these, added for compatibility
bgneal@183 10928 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
bgneal@183 10929
bgneal@183 10930 // Add undo manager logic
bgneal@183 10931 'mceEndUndoLevel,mceAddUndoLevel' : function() {
bgneal@183 10932 editor.undoManager.add();
bgneal@183 10933 },
bgneal@183 10934
bgneal@183 10935 'Cut,Copy,Paste' : function(command) {
bgneal@183 10936 var doc = editor.getDoc(), failed;
bgneal@183 10937
bgneal@183 10938 // Try executing the native command
bgneal@183 10939 try {
bgneal@183 10940 execNativeCommand(command);
bgneal@183 10941 } catch (ex) {
bgneal@183 10942 // Command failed
bgneal@183 10943 failed = TRUE;
bgneal@183 10944 }
bgneal@183 10945
bgneal@183 10946 // Present alert message about clipboard access not being available
bgneal@183 10947 if (failed || !doc.queryCommandEnabled(command)) {
bgneal@183 10948 if (tinymce.isGecko) {
bgneal@183 10949 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
bgneal@183 10950 if (state)
bgneal@183 10951 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
bgneal@183 10952 });
bgneal@183 10953 } else
bgneal@183 10954 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
bgneal@183 10955 }
bgneal@183 10956 },
bgneal@183 10957
bgneal@183 10958 // Override unlink command
bgneal@183 10959 unlink : function(command) {
bgneal@183 10960 if (selection.isCollapsed())
bgneal@183 10961 selection.select(selection.getNode());
bgneal@183 10962
bgneal@183 10963 execNativeCommand(command);
bgneal@183 10964 selection.collapse(FALSE);
bgneal@183 10965 },
bgneal@183 10966
bgneal@183 10967 // Override justify commands to use the text formatter engine
bgneal@183 10968 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@183 10969 var align = command.substring(7);
bgneal@183 10970
bgneal@183 10971 // Remove all other alignments first
bgneal@183 10972 each('left,center,right,full'.split(','), function(name) {
bgneal@183 10973 if (align != name)
bgneal@183 10974 editor.formatter.remove('align' + name);
bgneal@183 10975 });
bgneal@183 10976
bgneal@183 10977 toggleFormat('align' + align);
bgneal@183 10978 },
bgneal@183 10979
bgneal@183 10980 // Override list commands to fix WebKit bug
bgneal@183 10981 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@183 10982 var listElm, listParent;
bgneal@183 10983
bgneal@183 10984 execNativeCommand(command);
bgneal@183 10985
bgneal@183 10986 // WebKit produces lists within block elements so we need to split them
bgneal@183 10987 // we will replace the native list creation logic to custom logic later on
bgneal@183 10988 // TODO: Remove this when the list creation logic is removed
bgneal@183 10989 listElm = dom.getParent(selection.getNode(), 'ol,ul');
bgneal@183 10990 if (listElm) {
bgneal@183 10991 listParent = listElm.parentNode;
bgneal@183 10992
bgneal@183 10993 // If list is within a text block then split that block
bgneal@183 10994 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
bgneal@183 10995 storeSelection();
bgneal@183 10996 dom.split(listParent, listElm);
bgneal@183 10997 restoreSelection();
bgneal@183 10998 }
bgneal@183 10999 }
bgneal@183 11000 },
bgneal@183 11001
bgneal@183 11002 // Override commands to use the text formatter engine
bgneal@183 11003 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@183 11004 toggleFormat(command);
bgneal@183 11005 },
bgneal@183 11006
bgneal@183 11007 // Override commands to use the text formatter engine
bgneal@183 11008 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
bgneal@183 11009 toggleFormat(command, value);
bgneal@183 11010 },
bgneal@183 11011
bgneal@183 11012 FontSize : function(command, ui, value) {
bgneal@183 11013 var fontClasses, fontSizes;
bgneal@183 11014
bgneal@183 11015 // Convert font size 1-7 to styles
bgneal@183 11016 if (value >= 1 && value <= 7) {
bgneal@183 11017 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@183 11018 fontClasses = tinymce.explode(settings.font_size_classes);
bgneal@183 11019
bgneal@183 11020 if (fontClasses)
bgneal@183 11021 value = fontClasses[value - 1] || value;
bgneal@183 11022 else
bgneal@183 11023 value = fontSizes[value - 1] || value;
bgneal@183 11024 }
bgneal@183 11025
bgneal@183 11026 toggleFormat(command, value);
bgneal@183 11027 },
bgneal@183 11028
bgneal@183 11029 RemoveFormat : function(command) {
bgneal@183 11030 editor.formatter.remove(command);
bgneal@183 11031 },
bgneal@183 11032
bgneal@183 11033 mceBlockQuote : function(command) {
bgneal@183 11034 toggleFormat('blockquote');
bgneal@183 11035 },
bgneal@183 11036
bgneal@183 11037 FormatBlock : function(command, ui, value) {
bgneal@183 11038 return toggleFormat(value);
bgneal@183 11039 },
bgneal@183 11040
bgneal@183 11041 mceCleanup : function() {
bgneal@183 11042 storeSelection();
bgneal@183 11043 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
bgneal@183 11044 restoreSelection();
bgneal@183 11045 },
bgneal@183 11046
bgneal@183 11047 mceRemoveNode : function(command, ui, value) {
bgneal@183 11048 var node = value || selection.getNode();
bgneal@183 11049
bgneal@183 11050 // Make sure that the body node isn't removed
bgneal@183 11051 if (node != ed.getBody()) {
bgneal@183 11052 storeSelection();
bgneal@183 11053 editor.dom.remove(node, TRUE);
bgneal@183 11054 restoreSelection();
bgneal@183 11055 }
bgneal@183 11056 },
bgneal@183 11057
bgneal@183 11058 mceSelectNodeDepth : function(command, ui, value) {
bgneal@183 11059 var counter = 0;
bgneal@183 11060
bgneal@183 11061 dom.getParent(selection.getNode(), function(node) {
bgneal@183 11062 if (node.nodeType == 1 && counter++ == value) {
bgneal@183 11063 selection.select(node);
bgneal@183 11064 return FALSE;
bgneal@183 11065 }
bgneal@183 11066 }, editor.getBody());
bgneal@183 11067 },
bgneal@183 11068
bgneal@183 11069 mceSelectNode : function(command, ui, value) {
bgneal@183 11070 selection.select(value);
bgneal@183 11071 },
bgneal@183 11072
bgneal@183 11073 mceInsertContent : function(command, ui, value) {
bgneal@183 11074 selection.setContent(value);
bgneal@183 11075 },
bgneal@183 11076
bgneal@183 11077 mceInsertRawHTML : function(command, ui, value) {
bgneal@183 11078 selection.setContent('tiny_mce_marker');
bgneal@183 11079 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
bgneal@183 11080 },
bgneal@183 11081
bgneal@183 11082 mceSetContent : function(command, ui, value) {
bgneal@183 11083 editor.setContent(value);
bgneal@183 11084 },
bgneal@183 11085
bgneal@183 11086 'Indent,Outdent' : function(command) {
bgneal@183 11087 var intentValue, indentUnit, value;
bgneal@183 11088
bgneal@183 11089 // Setup indent level
bgneal@183 11090 intentValue = settings.indentation;
bgneal@183 11091 indentUnit = /[a-z%]+$/i.exec(intentValue);
bgneal@183 11092 intentValue = parseInt(intentValue);
bgneal@183 11093
bgneal@183 11094 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
bgneal@183 11095 each(selection.getSelectedBlocks(), function(element) {
bgneal@183 11096 if (command == 'outdent') {
bgneal@183 11097 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
bgneal@183 11098 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
bgneal@183 11099 } else
bgneal@183 11100 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
bgneal@183 11101 });
bgneal@183 11102 } else
bgneal@183 11103 execNativeCommand(command);
bgneal@183 11104 },
bgneal@183 11105
bgneal@183 11106 mceRepaint : function() {
bgneal@183 11107 var bookmark;
bgneal@183 11108
bgneal@183 11109 if (tinymce.isGecko) {
bgneal@183 11110 try {
bgneal@183 11111 storeSelection(TRUE);
bgneal@183 11112
bgneal@183 11113 if (selection.getSel())
bgneal@183 11114 selection.getSel().selectAllChildren(editor.getBody());
bgneal@183 11115
bgneal@183 11116 selection.collapse(TRUE);
bgneal@183 11117 restoreSelection();
bgneal@183 11118 } catch (ex) {
bgneal@183 11119 // Ignore
bgneal@183 11120 }
bgneal@183 11121 }
bgneal@183 11122 },
bgneal@183 11123
bgneal@183 11124 mceToggleFormat : function(command, ui, value) {
bgneal@183 11125 editor.formatter.toggle(value);
bgneal@183 11126 },
bgneal@183 11127
bgneal@183 11128 InsertHorizontalRule : function() {
bgneal@183 11129 selection.setContent('<hr />');
bgneal@183 11130 },
bgneal@183 11131
bgneal@183 11132 mceToggleVisualAid : function() {
bgneal@183 11133 editor.hasVisual = !editor.hasVisual;
bgneal@183 11134 editor.addVisual();
bgneal@183 11135 },
bgneal@183 11136
bgneal@183 11137 mceReplaceContent : function(command, ui, value) {
bgneal@183 11138 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
bgneal@183 11139 },
bgneal@183 11140
bgneal@183 11141 mceInsertLink : function(command, ui, value) {
bgneal@183 11142 var link = dom.getParent(selection.getNode(), 'a');
bgneal@183 11143
bgneal@183 11144 if (tinymce.is(value, 'string'))
bgneal@183 11145 value = {href : value};
bgneal@183 11146
bgneal@183 11147 if (!link) {
bgneal@183 11148 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
bgneal@183 11149 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
bgneal@183 11150 dom.setAttribs(link, value);
bgneal@183 11151 });
bgneal@183 11152 } else {
bgneal@183 11153 if (value.href)
bgneal@183 11154 dom.setAttribs(link, value);
bgneal@183 11155 else
bgneal@183 11156 ed.dom.remove(link, TRUE);
bgneal@183 11157 }
bgneal@183 11158 }
bgneal@183 11159 });
bgneal@183 11160
bgneal@183 11161 // Add queryCommandState overrides
bgneal@183 11162 addCommands({
bgneal@183 11163 // Override justify commands
bgneal@183 11164 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@183 11165 return isFormatMatch('align' + command.substring(7));
bgneal@183 11166 },
bgneal@183 11167
bgneal@183 11168 'Bold,Italic,Underline,Strikethrough' : function(command) {
bgneal@183 11169 return isFormatMatch(command);
bgneal@183 11170 },
bgneal@183 11171
bgneal@183 11172 mceBlockQuote : function() {
bgneal@183 11173 return isFormatMatch('blockquote');
bgneal@183 11174 },
bgneal@183 11175
bgneal@183 11176 Outdent : function() {
bgneal@183 11177 var node;
bgneal@183 11178
bgneal@183 11179 if (settings.inline_styles) {
bgneal@183 11180 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@183 11181 return TRUE;
bgneal@183 11182
bgneal@183 11183 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@183 11184 return TRUE;
bgneal@183 11185 }
bgneal@183 11186
bgneal@183 11187 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
bgneal@183 11188 },
bgneal@183 11189
bgneal@183 11190 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@183 11191 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
bgneal@183 11192 }
bgneal@183 11193 }, 'state');
bgneal@183 11194
bgneal@183 11195 // Add queryCommandValue overrides
bgneal@183 11196 addCommands({
bgneal@183 11197 'FontSize,FontName' : function(command) {
bgneal@183 11198 var value = 0, parent;
bgneal@183 11199
bgneal@183 11200 if (parent = dom.getParent(selection.getNode(), 'span')) {
bgneal@183 11201 if (command == 'fontsize')
bgneal@183 11202 value = parent.style.fontSize;
bgneal@183 11203 else
bgneal@183 11204 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
bgneal@183 11205 }
bgneal@183 11206
bgneal@183 11207 return value;
bgneal@183 11208 }
bgneal@183 11209 }, 'value');
bgneal@183 11210
bgneal@183 11211 // Add undo manager logic
bgneal@183 11212 if (settings.custom_undo_redo) {
bgneal@183 11213 addCommands({
bgneal@183 11214 Undo : function() {
bgneal@183 11215 editor.undoManager.undo();
bgneal@183 11216 },
bgneal@183 11217
bgneal@183 11218 Redo : function() {
bgneal@183 11219 editor.undoManager.redo();
bgneal@183 11220 }
bgneal@183 11221 });
bgneal@183 11222 }
bgneal@183 11223 };
bgneal@45 11224 })(tinymce);
bgneal@45 11225 (function(tinymce) {
bgneal@183 11226 var Dispatcher = tinymce.util.Dispatcher;
bgneal@183 11227
bgneal@183 11228 tinymce.UndoManager = function(editor) {
bgneal@183 11229 var self, index = 0, data = [];
bgneal@183 11230
bgneal@183 11231 function getContent() {
bgneal@183 11232 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
bgneal@183 11233 };
bgneal@183 11234
bgneal@183 11235 return self = {
bgneal@183 11236 typing : 0,
bgneal@183 11237
bgneal@183 11238 onAdd : new Dispatcher(self),
bgneal@183 11239 onUndo : new Dispatcher(self),
bgneal@183 11240 onRedo : new Dispatcher(self),
bgneal@183 11241
bgneal@183 11242 add : function(level) {
bgneal@183 11243 var i, settings = editor.settings, lastLevel;
bgneal@183 11244
bgneal@183 11245 level = level || {};
bgneal@183 11246 level.content = getContent();
bgneal@183 11247
bgneal@183 11248 // Add undo level if needed
bgneal@183 11249 lastLevel = data[index];
bgneal@183 11250 if (lastLevel && lastLevel.content == level.content) {
bgneal@183 11251 if (index > 0 || data.length == 1)
bgneal@183 11252 return null;
bgneal@183 11253 }
bgneal@183 11254
bgneal@183 11255 // Time to compress
bgneal@183 11256 if (settings.custom_undo_redo_levels) {
bgneal@183 11257 if (data.length > settings.custom_undo_redo_levels) {
bgneal@183 11258 for (i = 0; i < data.length - 1; i++)
bgneal@183 11259 data[i] = data[i + 1];
bgneal@183 11260
bgneal@183 11261 data.length--;
bgneal@183 11262 index = data.length;
bgneal@183 11263 }
bgneal@183 11264 }
bgneal@183 11265
bgneal@183 11266 // Get a non intrusive normalized bookmark
bgneal@183 11267 level.bookmark = editor.selection.getBookmark(2, true);
bgneal@183 11268
bgneal@183 11269 // Crop array if needed
bgneal@183 11270 if (index < data.length - 1) {
bgneal@183 11271 // Treat first level as initial
bgneal@183 11272 if (index == 0)
bgneal@183 11273 data = [];
bgneal@183 11274 else
bgneal@183 11275 data.length = index + 1;
bgneal@183 11276 }
bgneal@183 11277
bgneal@183 11278 data.push(level);
bgneal@183 11279 index = data.length - 1;
bgneal@183 11280
bgneal@183 11281 self.onAdd.dispatch(self, level);
bgneal@183 11282 editor.isNotDirty = 0;
bgneal@183 11283
bgneal@183 11284 return level;
bgneal@183 11285 },
bgneal@183 11286
bgneal@183 11287 undo : function() {
bgneal@183 11288 var level, i;
bgneal@183 11289
bgneal@183 11290 if (self.typing) {
bgneal@183 11291 self.add();
bgneal@183 11292 self.typing = 0;
bgneal@183 11293 }
bgneal@183 11294
bgneal@183 11295 if (index > 0) {
bgneal@183 11296 level = data[--index];
bgneal@183 11297
bgneal@183 11298 editor.setContent(level.content, {format : 'raw'});
bgneal@183 11299 editor.selection.moveToBookmark(level.bookmark);
bgneal@183 11300
bgneal@183 11301 self.onUndo.dispatch(self, level);
bgneal@183 11302 }
bgneal@183 11303
bgneal@183 11304 return level;
bgneal@183 11305 },
bgneal@183 11306
bgneal@183 11307 redo : function() {
bgneal@183 11308 var level;
bgneal@183 11309
bgneal@183 11310 if (index < data.length - 1) {
bgneal@183 11311 level = data[++index];
bgneal@183 11312
bgneal@183 11313 editor.setContent(level.content, {format : 'raw'});
bgneal@183 11314 editor.selection.moveToBookmark(level.bookmark);
bgneal@183 11315
bgneal@183 11316 self.onRedo.dispatch(self, level);
bgneal@183 11317 }
bgneal@183 11318
bgneal@183 11319 return level;
bgneal@183 11320 },
bgneal@183 11321
bgneal@183 11322 clear : function() {
bgneal@183 11323 data = [];
bgneal@183 11324 index = self.typing = 0;
bgneal@183 11325 },
bgneal@183 11326
bgneal@183 11327 hasUndo : function() {
bgneal@183 11328 return index > 0 || self.typing;
bgneal@183 11329 },
bgneal@183 11330
bgneal@183 11331 hasRedo : function() {
bgneal@183 11332 return index < data.length - 1;
bgneal@183 11333 }
bgneal@183 11334 };
bgneal@183 11335 };
bgneal@45 11336 })(tinymce);
bgneal@183 11337
bgneal@45 11338 (function(tinymce) {
bgneal@45 11339 // Shorten names
bgneal@183 11340 var Event = tinymce.dom.Event,
bgneal@183 11341 isIE = tinymce.isIE,
bgneal@183 11342 isGecko = tinymce.isGecko,
bgneal@183 11343 isOpera = tinymce.isOpera,
bgneal@183 11344 each = tinymce.each,
bgneal@183 11345 extend = tinymce.extend,
bgneal@183 11346 TRUE = true,
bgneal@183 11347 FALSE = false;
bgneal@183 11348
bgneal@183 11349 // Checks if the selection/caret is at the end of the specified block element
bgneal@183 11350 function isAtEnd(rng, par) {
bgneal@183 11351 var rng2 = par.ownerDocument.createRange();
bgneal@183 11352
bgneal@183 11353 rng2.setStart(rng.endContainer, rng.endOffset);
bgneal@183 11354 rng2.setEndAfter(par);
bgneal@183 11355
bgneal@183 11356 // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
bgneal@183 11357 return rng2.cloneContents().textContent.length == 0;
bgneal@183 11358 };
bgneal@183 11359
bgneal@183 11360 function isEmpty(n) {
bgneal@183 11361 n = n.innerHTML;
bgneal@183 11362
bgneal@183 11363 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
bgneal@183 11364 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
bgneal@183 11365
bgneal@183 11366 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
bgneal@183 11367 };
bgneal@183 11368
bgneal@183 11369 function splitList(selection, dom, li) {
bgneal@183 11370 var listBlock, block;
bgneal@183 11371
bgneal@183 11372 if (isEmpty(li)) {
bgneal@183 11373 listBlock = dom.getParent(li, 'ul,ol');
bgneal@183 11374
bgneal@183 11375 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
bgneal@183 11376 dom.split(listBlock, li);
bgneal@183 11377 block = dom.create('p', 0, '<br _mce_bogus="1" />');
bgneal@183 11378 dom.replace(block, li);
bgneal@183 11379 selection.select(block, 1);
bgneal@183 11380 }
bgneal@183 11381
bgneal@183 11382 return FALSE;
bgneal@183 11383 }
bgneal@183 11384
bgneal@183 11385 return TRUE;
bgneal@183 11386 };
bgneal@45 11387
bgneal@45 11388 tinymce.create('tinymce.ForceBlocks', {
bgneal@45 11389 ForceBlocks : function(ed) {
bgneal@45 11390 var t = this, s = ed.settings, elm;
bgneal@45 11391
bgneal@45 11392 t.editor = ed;
bgneal@45 11393 t.dom = ed.dom;
bgneal@45 11394 elm = (s.forced_root_block || 'p').toLowerCase();
bgneal@45 11395 s.element = elm.toUpperCase();
bgneal@45 11396
bgneal@45 11397 ed.onPreInit.add(t.setup, t);
bgneal@45 11398
bgneal@45 11399 t.reOpera = new RegExp('(\\u00a0|&#160;|&nbsp;)<\/' + elm + '>', 'gi');
bgneal@45 11400 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11401 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11402 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>(&nbsp;|&#160;)<\\\/%p>|<%p>(&nbsp;|&#160;)<\\\/%p>'.replace(/%p/g, elm), 'gi');
bgneal@45 11403 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
bgneal@45 11404
bgneal@45 11405 function padd(ed, o) {
bgneal@45 11406 if (isOpera)
bgneal@45 11407 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
bgneal@45 11408
bgneal@45 11409 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
bgneal@45 11410
bgneal@45 11411 if (!isIE && !isOpera && o.set) {
bgneal@45 11412 // Use &nbsp; instead of BR in padded paragraphs
bgneal@45 11413 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@45 11414 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
bgneal@183 11415 } else
bgneal@45 11416 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
bgneal@45 11417 };
bgneal@45 11418
bgneal@45 11419 ed.onBeforeSetContent.add(padd);
bgneal@45 11420 ed.onPostProcess.add(padd);
bgneal@45 11421
bgneal@45 11422 if (s.forced_root_block) {
bgneal@45 11423 ed.onInit.add(t.forceRoots, t);
bgneal@45 11424 ed.onSetContent.add(t.forceRoots, t);
bgneal@45 11425 ed.onBeforeGetContent.add(t.forceRoots, t);
bgneal@45 11426 }
bgneal@45 11427 },
bgneal@45 11428
bgneal@45 11429 setup : function() {
bgneal@183 11430 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
bgneal@45 11431
bgneal@45 11432 // Force root blocks when typing and when getting output
bgneal@45 11433 if (s.forced_root_block) {
bgneal@183 11434 ed.onBeforeExecCommand.add(t.forceRoots, t);
bgneal@45 11435 ed.onKeyUp.add(t.forceRoots, t);
bgneal@45 11436 ed.onPreProcess.add(t.forceRoots, t);
bgneal@45 11437 }
bgneal@45 11438
bgneal@45 11439 if (s.force_br_newlines) {
bgneal@45 11440 // Force IE to produce BRs on enter
bgneal@45 11441 if (isIE) {
bgneal@45 11442 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11443 var n;
bgneal@183 11444
bgneal@183 11445 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
bgneal@183 11446 selection.setContent('<br id="__" /> ', {format : 'raw'});
bgneal@183 11447 n = dom.get('__');
bgneal@45 11448 n.removeAttribute('id');
bgneal@183 11449 selection.select(n);
bgneal@183 11450 selection.collapse();
bgneal@45 11451 return Event.cancel(e);
bgneal@45 11452 }
bgneal@45 11453 });
bgneal@45 11454 }
bgneal@45 11455 }
bgneal@45 11456
bgneal@45 11457 if (!isIE && s.force_p_newlines) {
bgneal@45 11458 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11459 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
bgneal@183 11460 Event.cancel(e);
bgneal@45 11461 });
bgneal@45 11462
bgneal@45 11463 if (isGecko) {
bgneal@45 11464 ed.onKeyDown.add(function(ed, e) {
bgneal@45 11465 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
bgneal@45 11466 t.backspaceDelete(e, e.keyCode == 8);
bgneal@45 11467 });
bgneal@45 11468 }
bgneal@45 11469 }
bgneal@45 11470
bgneal@183 11471 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
bgneal@183 11472 if (tinymce.isWebKit) {
bgneal@183 11473 function insertBr(ed) {
bgneal@183 11474 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
bgneal@183 11475
bgneal@183 11476 // Insert BR element
bgneal@183 11477 rng.insertNode(br = dom.create('br'));
bgneal@183 11478
bgneal@183 11479 // Place caret after BR
bgneal@183 11480 rng.setStartAfter(br);
bgneal@183 11481 rng.setEndAfter(br);
bgneal@183 11482 selection.setRng(rng);
bgneal@183 11483
bgneal@183 11484 // Could not place caret after BR then insert an nbsp entity and move the caret
bgneal@183 11485 if (selection.getSel().focusNode == br.previousSibling) {
bgneal@183 11486 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
bgneal@183 11487 selection.collapse(TRUE);
bgneal@183 11488 }
bgneal@183 11489
bgneal@183 11490 // Create a temporary DIV after the BR and get the position as it
bgneal@183 11491 // seems like getPos() returns 0 for text nodes and BR elements.
bgneal@183 11492 dom.insertAfter(div, br);
bgneal@183 11493 divYPos = dom.getPos(div).y;
bgneal@183 11494 dom.remove(div);
bgneal@183 11495
bgneal@183 11496 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
bgneal@183 11497 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
bgneal@183 11498 ed.getWin().scrollTo(0, divYPos);
bgneal@183 11499 };
bgneal@183 11500
bgneal@45 11501 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11502 if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) {
bgneal@183 11503 insertBr(ed);
bgneal@183 11504 Event.cancel(e);
bgneal@183 11505 }
bgneal@183 11506 });
bgneal@183 11507 }
bgneal@183 11508
bgneal@183 11509 // Padd empty inline elements within block elements
bgneal@183 11510 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
bgneal@183 11511 ed.onPreProcess.add(function(ed, o) {
bgneal@183 11512 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
bgneal@183 11513 if (isEmpty(p)) {
bgneal@183 11514 each(dom.select('span,em,strong,b,i', o.node), function(n) {
bgneal@183 11515 if (!n.hasChildNodes()) {
bgneal@183 11516 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
bgneal@183 11517 return FALSE; // Break the loop one padding is enough
bgneal@183 11518 }
bgneal@183 11519 });
bgneal@183 11520 }
bgneal@183 11521 });
bgneal@183 11522 });
bgneal@183 11523
bgneal@183 11524 // IE specific fixes
bgneal@183 11525 if (isIE) {
bgneal@183 11526 // Replaces IE:s auto generated paragraphs with the specified element name
bgneal@183 11527 if (s.element != 'P') {
bgneal@183 11528 ed.onKeyPress.add(function(ed, e) {
bgneal@183 11529 t.lastElm = selection.getNode().nodeName;
bgneal@183 11530 });
bgneal@183 11531
bgneal@183 11532 ed.onKeyUp.add(function(ed, e) {
bgneal@183 11533 var bl, n = selection.getNode(), b = ed.getBody();
bgneal@183 11534
bgneal@183 11535 if (b.childNodes.length === 1 && n.nodeName == 'P') {
bgneal@183 11536 n = dom.rename(n, s.element);
bgneal@183 11537 selection.select(n);
bgneal@183 11538 selection.collapse();
bgneal@45 11539 ed.nodeChanged();
bgneal@183 11540 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bgneal@183 11541 bl = dom.getParent(n, 'p');
bgneal@183 11542
bgneal@183 11543 if (bl) {
bgneal@183 11544 dom.rename(bl, s.element);
bgneal@183 11545 ed.nodeChanged();
bgneal@183 11546 }
bgneal@183 11547 }
bgneal@183 11548 });
bgneal@183 11549 }
bgneal@45 11550 }
bgneal@45 11551 },
bgneal@45 11552
bgneal@45 11553 find : function(n, t, s) {
bgneal@183 11554 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
bgneal@45 11555
bgneal@45 11556 while (n = w.nextNode()) {
bgneal@45 11557 c++;
bgneal@45 11558
bgneal@45 11559 // Index by node
bgneal@45 11560 if (t == 0 && n == s)
bgneal@45 11561 return c;
bgneal@45 11562
bgneal@45 11563 // Node by index
bgneal@45 11564 if (t == 1 && c == s)
bgneal@45 11565 return n;
bgneal@45 11566 }
bgneal@45 11567
bgneal@45 11568 return -1;
bgneal@45 11569 },
bgneal@45 11570
bgneal@45 11571 forceRoots : function(ed, e) {
bgneal@45 11572 var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
bgneal@45 11573 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
bgneal@45 11574
bgneal@45 11575 // Fix for bug #1863847
bgneal@45 11576 //if (e && e.keyCode == 13)
bgneal@183 11577 // return TRUE;
bgneal@45 11578
bgneal@45 11579 // Wrap non blocks into blocks
bgneal@45 11580 for (i = nl.length - 1; i >= 0; i--) {
bgneal@45 11581 nx = nl[i];
bgneal@45 11582
bgneal@183 11583 // Ignore internal elements
bgneal@183 11584 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
bgneal@183 11585 bl = null;
bgneal@183 11586 continue;
bgneal@183 11587 }
bgneal@183 11588
bgneal@45 11589 // Is text or non block element
bgneal@183 11590 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
bgneal@45 11591 if (!bl) {
bgneal@45 11592 // Create new block but ignore whitespace
bgneal@45 11593 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
bgneal@45 11594 // Store selection
bgneal@45 11595 if (si == -2 && r) {
bgneal@45 11596 if (!isIE) {
bgneal@45 11597 // If selection is element then mark it
bgneal@45 11598 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
bgneal@45 11599 // Save the id of the selected element
bgneal@45 11600 eid = n.getAttribute("id");
bgneal@45 11601 n.setAttribute("id", "__mce");
bgneal@45 11602 } else {
bgneal@45 11603 // If element is inside body, might not be the case in contentEdiable mode
bgneal@45 11604 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
bgneal@45 11605 so = r.startOffset;
bgneal@45 11606 eo = r.endOffset;
bgneal@45 11607 si = t.find(b, 0, r.startContainer);
bgneal@45 11608 ei = t.find(b, 0, r.endContainer);
bgneal@45 11609 }
bgneal@45 11610 }
bgneal@45 11611 } else {
bgneal@45 11612 tr = d.body.createTextRange();
bgneal@45 11613 tr.moveToElementText(b);
bgneal@45 11614 tr.collapse(1);
bgneal@45 11615 bp = tr.move('character', c) * -1;
bgneal@45 11616
bgneal@45 11617 tr = r.duplicate();
bgneal@45 11618 tr.collapse(1);
bgneal@45 11619 sp = tr.move('character', c) * -1;
bgneal@45 11620
bgneal@45 11621 tr = r.duplicate();
bgneal@45 11622 tr.collapse(0);
bgneal@45 11623 le = (tr.move('character', c) * -1) - sp;
bgneal@45 11624
bgneal@45 11625 si = sp - bp;
bgneal@45 11626 ei = le;
bgneal@45 11627 }
bgneal@45 11628 }
bgneal@45 11629
bgneal@183 11630 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
bgneal@183 11631 // See: http://support.microsoft.com/kb/829907
bgneal@45 11632 bl = ed.dom.create(ed.settings.forced_root_block);
bgneal@45 11633 nx.parentNode.replaceChild(bl, nx);
bgneal@183 11634 bl.appendChild(nx);
bgneal@45 11635 }
bgneal@45 11636 } else {
bgneal@45 11637 if (bl.hasChildNodes())
bgneal@45 11638 bl.insertBefore(nx, bl.firstChild);
bgneal@45 11639 else
bgneal@45 11640 bl.appendChild(nx);
bgneal@45 11641 }
bgneal@45 11642 } else
bgneal@45 11643 bl = null; // Time to create new block
bgneal@45 11644 }
bgneal@45 11645
bgneal@45 11646 // Restore selection
bgneal@45 11647 if (si != -2) {
bgneal@45 11648 if (!isIE) {
bgneal@45 11649 bl = b.getElementsByTagName(ed.settings.element)[0];
bgneal@45 11650 r = d.createRange();
bgneal@45 11651
bgneal@45 11652 // Select last location or generated block
bgneal@45 11653 if (si != -1)
bgneal@45 11654 r.setStart(t.find(b, 1, si), so);
bgneal@45 11655 else
bgneal@45 11656 r.setStart(bl, 0);
bgneal@45 11657
bgneal@45 11658 // Select last location or generated block
bgneal@45 11659 if (ei != -1)
bgneal@45 11660 r.setEnd(t.find(b, 1, ei), eo);
bgneal@45 11661 else
bgneal@45 11662 r.setEnd(bl, 0);
bgneal@45 11663
bgneal@45 11664 if (s) {
bgneal@45 11665 s.removeAllRanges();
bgneal@45 11666 s.addRange(r);
bgneal@45 11667 }
bgneal@45 11668 } else {
bgneal@45 11669 try {
bgneal@45 11670 r = s.createRange();
bgneal@45 11671 r.moveToElementText(b);
bgneal@45 11672 r.collapse(1);
bgneal@45 11673 r.moveStart('character', si);
bgneal@45 11674 r.moveEnd('character', ei);
bgneal@45 11675 r.select();
bgneal@45 11676 } catch (ex) {
bgneal@45 11677 // Ignore
bgneal@45 11678 }
bgneal@45 11679 }
bgneal@45 11680 } else if (!isIE && (n = ed.dom.get('__mce'))) {
bgneal@45 11681 // Restore the id of the selected element
bgneal@45 11682 if (eid)
bgneal@45 11683 n.setAttribute('id', eid);
bgneal@45 11684 else
bgneal@45 11685 n.removeAttribute('id');
bgneal@45 11686
bgneal@45 11687 // Move caret before selected element
bgneal@45 11688 r = d.createRange();
bgneal@45 11689 r.setStartBefore(n);
bgneal@45 11690 r.setEndBefore(n);
bgneal@45 11691 se.setRng(r);
bgneal@45 11692 }
bgneal@45 11693 },
bgneal@45 11694
bgneal@45 11695 getParentBlock : function(n) {
bgneal@45 11696 var d = this.dom;
bgneal@45 11697
bgneal@45 11698 return d.getParent(n, d.isBlock);
bgneal@45 11699 },
bgneal@45 11700
bgneal@45 11701 insertPara : function(e) {
bgneal@45 11702 var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
bgneal@45 11703 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
bgneal@45 11704
bgneal@45 11705 // If root blocks are forced then use Operas default behavior since it's really good
bgneal@45 11706 // Removed due to bug: #1853816
bgneal@45 11707 // if (se.forced_root_block && isOpera)
bgneal@183 11708 // return TRUE;
bgneal@45 11709
bgneal@45 11710 // Setup before range
bgneal@45 11711 rb = d.createRange();
bgneal@45 11712
bgneal@45 11713 // If is before the first block element and in body, then move it into first block element
bgneal@45 11714 rb.setStart(s.anchorNode, s.anchorOffset);
bgneal@183 11715 rb.collapse(TRUE);
bgneal@45 11716
bgneal@45 11717 // Setup after range
bgneal@45 11718 ra = d.createRange();
bgneal@45 11719
bgneal@45 11720 // If is before the first block element and in body, then move it into first block element
bgneal@45 11721 ra.setStart(s.focusNode, s.focusOffset);
bgneal@183 11722 ra.collapse(TRUE);
bgneal@45 11723
bgneal@45 11724 // Setup start/end points
bgneal@45 11725 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
bgneal@45 11726 sn = dir ? s.anchorNode : s.focusNode;
bgneal@45 11727 so = dir ? s.anchorOffset : s.focusOffset;
bgneal@45 11728 en = dir ? s.focusNode : s.anchorNode;
bgneal@45 11729 eo = dir ? s.focusOffset : s.anchorOffset;
bgneal@45 11730
bgneal@45 11731 // If selection is in empty table cell
bgneal@45 11732 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
bgneal@183 11733 if (sn.firstChild.nodeName == 'BR')
bgneal@183 11734 dom.remove(sn.firstChild); // Remove BR
bgneal@45 11735
bgneal@45 11736 // Create two new block elements
bgneal@183 11737 if (sn.childNodes.length == 0) {
bgneal@183 11738 ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 11739 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 11740 } else {
bgneal@183 11741 n = sn.innerHTML;
bgneal@183 11742 sn.innerHTML = '';
bgneal@183 11743 ed.dom.add(sn, se.element, null, n);
bgneal@183 11744 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@183 11745 }
bgneal@45 11746
bgneal@45 11747 // Move caret into the last one
bgneal@45 11748 r = d.createRange();
bgneal@45 11749 r.selectNodeContents(aft);
bgneal@45 11750 r.collapse(1);
bgneal@45 11751 ed.selection.setRng(r);
bgneal@45 11752
bgneal@183 11753 return FALSE;
bgneal@45 11754 }
bgneal@45 11755
bgneal@45 11756 // If the caret is in an invalid location in FF we need to move it into the first block
bgneal@45 11757 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
bgneal@45 11758 sn = en = sn.firstChild;
bgneal@45 11759 so = eo = 0;
bgneal@45 11760 rb = d.createRange();
bgneal@45 11761 rb.setStart(sn, 0);
bgneal@45 11762 ra = d.createRange();
bgneal@45 11763 ra.setStart(en, 0);
bgneal@45 11764 }
bgneal@45 11765
bgneal@45 11766 // Never use body as start or end node
bgneal@45 11767 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 11768 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
bgneal@45 11769 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@45 11770 en = en.nodeName == "BODY" ? en.firstChild : en;
bgneal@45 11771
bgneal@45 11772 // Get start and end blocks
bgneal@45 11773 sb = t.getParentBlock(sn);
bgneal@45 11774 eb = t.getParentBlock(en);
bgneal@45 11775 bn = sb ? sb.nodeName : se.element; // Get block name to create
bgneal@45 11776
bgneal@45 11777 // Return inside list use default browser behavior
bgneal@183 11778 if (n = t.dom.getParent(sb, 'li,pre')) {
bgneal@183 11779 if (n.nodeName == 'LI')
bgneal@183 11780 return splitList(ed.selection, t.dom, n);
bgneal@183 11781
bgneal@183 11782 return TRUE;
bgneal@183 11783 }
bgneal@45 11784
bgneal@45 11785 // If caption or absolute layers then always generate new blocks within
bgneal@45 11786 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 11787 bn = se.element;
bgneal@45 11788 sb = null;
bgneal@45 11789 }
bgneal@45 11790
bgneal@45 11791 // If caption or absolute layers then always generate new blocks within
bgneal@45 11792 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@45 11793 bn = se.element;
bgneal@45 11794 eb = null;
bgneal@45 11795 }
bgneal@45 11796
bgneal@45 11797 // Use P instead
bgneal@45 11798 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bgneal@45 11799 bn = se.element;
bgneal@45 11800 sb = eb = null;
bgneal@45 11801 }
bgneal@45 11802
bgneal@45 11803 // Setup new before and after blocks
bgneal@45 11804 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
bgneal@45 11805 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
bgneal@45 11806
bgneal@45 11807 // Remove id from after clone
bgneal@45 11808 aft.removeAttribute('id');
bgneal@45 11809
bgneal@45 11810 // Is header and cursor is at the end, then force paragraph under
bgneal@183 11811 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
bgneal@45 11812 aft = ed.dom.create(se.element);
bgneal@45 11813
bgneal@45 11814 // Find start chop node
bgneal@45 11815 n = sc = sn;
bgneal@45 11816 do {
bgneal@45 11817 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 11818 break;
bgneal@45 11819
bgneal@45 11820 sc = n;
bgneal@45 11821 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
bgneal@45 11822
bgneal@45 11823 // Find end chop node
bgneal@45 11824 n = ec = en;
bgneal@45 11825 do {
bgneal@45 11826 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@45 11827 break;
bgneal@45 11828
bgneal@45 11829 ec = n;
bgneal@45 11830 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
bgneal@45 11831
bgneal@45 11832 // Place first chop part into before block element
bgneal@45 11833 if (sc.nodeName == bn)
bgneal@45 11834 rb.setStart(sc, 0);
bgneal@45 11835 else
bgneal@45 11836 rb.setStartBefore(sc);
bgneal@45 11837
bgneal@45 11838 rb.setEnd(sn, so);
bgneal@45 11839 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 11840
bgneal@45 11841 // Place secnd chop part within new block element
bgneal@45 11842 try {
bgneal@45 11843 ra.setEndAfter(ec);
bgneal@45 11844 } catch(ex) {
bgneal@45 11845 //console.debug(s.focusNode, s.focusOffset);
bgneal@45 11846 }
bgneal@45 11847
bgneal@45 11848 ra.setStart(en, eo);
bgneal@45 11849 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@45 11850
bgneal@45 11851 // Create range around everything
bgneal@45 11852 r = d.createRange();
bgneal@45 11853 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
bgneal@45 11854 r.setStartBefore(sc.parentNode);
bgneal@45 11855 } else {
bgneal@45 11856 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
bgneal@45 11857 r.setStartBefore(rb.startContainer);
bgneal@45 11858 else
bgneal@45 11859 r.setStart(rb.startContainer, rb.startOffset);
bgneal@45 11860 }
bgneal@45 11861
bgneal@45 11862 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
bgneal@45 11863 r.setEndAfter(ec.parentNode);
bgneal@45 11864 else
bgneal@45 11865 r.setEnd(ra.endContainer, ra.endOffset);
bgneal@45 11866
bgneal@45 11867 // Delete and replace it with new block elements
bgneal@45 11868 r.deleteContents();
bgneal@45 11869
bgneal@45 11870 if (isOpera)
bgneal@45 11871 ed.getWin().scrollTo(0, vp.y);
bgneal@45 11872
bgneal@45 11873 // Never wrap blocks in blocks
bgneal@45 11874 if (bef.firstChild && bef.firstChild.nodeName == bn)
bgneal@45 11875 bef.innerHTML = bef.firstChild.innerHTML;
bgneal@45 11876
bgneal@45 11877 if (aft.firstChild && aft.firstChild.nodeName == bn)
bgneal@45 11878 aft.innerHTML = aft.firstChild.innerHTML;
bgneal@45 11879
bgneal@45 11880 // Padd empty blocks
bgneal@45 11881 if (isEmpty(bef))
bgneal@45 11882 bef.innerHTML = '<br />';
bgneal@45 11883
bgneal@45 11884 function appendStyles(e, en) {
bgneal@45 11885 var nl = [], nn, n, i;
bgneal@45 11886
bgneal@45 11887 e.innerHTML = '';
bgneal@45 11888
bgneal@45 11889 // Make clones of style elements
bgneal@45 11890 if (se.keep_styles) {
bgneal@45 11891 n = en;
bgneal@45 11892 do {
bgneal@45 11893 // We only want style specific elements
bgneal@45 11894 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
bgneal@183 11895 nn = n.cloneNode(FALSE);
bgneal@45 11896 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
bgneal@45 11897 nl.push(nn);
bgneal@45 11898 }
bgneal@45 11899 } while (n = n.parentNode);
bgneal@45 11900 }
bgneal@45 11901
bgneal@45 11902 // Append style elements to aft
bgneal@45 11903 if (nl.length > 0) {
bgneal@45 11904 for (i = nl.length - 1, nn = e; i >= 0; i--)
bgneal@45 11905 nn = nn.appendChild(nl[i]);
bgneal@45 11906
bgneal@45 11907 // Padd most inner style element
bgneal@45 11908 nl[0].innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@45 11909 return nl[0]; // Move caret to most inner element
bgneal@45 11910 } else
bgneal@45 11911 e.innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@45 11912 };
bgneal@45 11913
bgneal@45 11914 // Fill empty afterblook with current style
bgneal@45 11915 if (isEmpty(aft))
bgneal@45 11916 car = appendStyles(aft, en);
bgneal@45 11917
bgneal@45 11918 // Opera needs this one backwards for older versions
bgneal@45 11919 if (isOpera && parseFloat(opera.version()) < 9.5) {
bgneal@45 11920 r.insertNode(bef);
bgneal@45 11921 r.insertNode(aft);
bgneal@45 11922 } else {
bgneal@45 11923 r.insertNode(aft);
bgneal@45 11924 r.insertNode(bef);
bgneal@45 11925 }
bgneal@45 11926
bgneal@45 11927 // Normalize
bgneal@45 11928 aft.normalize();
bgneal@45 11929 bef.normalize();
bgneal@45 11930
bgneal@45 11931 function first(n) {
bgneal@183 11932 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
bgneal@45 11933 };
bgneal@45 11934
bgneal@45 11935 // Move cursor and scroll into view
bgneal@45 11936 r = d.createRange();
bgneal@45 11937 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
bgneal@45 11938 r.collapse(1);
bgneal@45 11939 s.removeAllRanges();
bgneal@45 11940 s.addRange(r);
bgneal@45 11941
bgneal@45 11942 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
bgneal@45 11943 y = ed.dom.getPos(aft).y;
bgneal@45 11944 ch = aft.clientHeight;
bgneal@45 11945
bgneal@45 11946 // Is element within viewport
bgneal@45 11947 if (y < vp.y || y + ch > vp.y + vp.h) {
bgneal@45 11948 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
bgneal@45 11949 //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
bgneal@45 11950 }
bgneal@45 11951
bgneal@183 11952 return FALSE;
bgneal@45 11953 },
bgneal@45 11954
bgneal@45 11955 backspaceDelete : function(e, bs) {
bgneal@183 11956 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;
bgneal@45 11957
bgneal@45 11958 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
bgneal@45 11959 // This workaround removes the element by hand and moves the caret to the previous element
bgneal@45 11960 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
bgneal@45 11961 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
bgneal@45 11962 // Find previous block element
bgneal@45 11963 n = sc;
bgneal@45 11964 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
bgneal@45 11965
bgneal@45 11966 if (n) {
bgneal@45 11967 if (sc != b.firstChild) {
bgneal@45 11968 // Find last text node
bgneal@183 11969 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
bgneal@45 11970 while (tn = w.nextNode())
bgneal@45 11971 n = tn;
bgneal@45 11972
bgneal@45 11973 // Place caret at the end of last text node
bgneal@45 11974 r = ed.getDoc().createRange();
bgneal@45 11975 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 11976 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@45 11977 se.setRng(r);
bgneal@45 11978
bgneal@45 11979 // Remove the target container
bgneal@45 11980 ed.dom.remove(sc);
bgneal@45 11981 }
bgneal@45 11982
bgneal@45 11983 return Event.cancel(e);
bgneal@45 11984 }
bgneal@45 11985 }
bgneal@45 11986 }
bgneal@45 11987
bgneal@45 11988 // Gecko generates BR elements here and there, we don't like those so lets remove them
bgneal@45 11989 function handler(e) {
bgneal@45 11990 var pr;
bgneal@45 11991
bgneal@45 11992 e = e.target;
bgneal@45 11993
bgneal@45 11994 // A new BR was created in a block element, remove it
bgneal@45 11995 if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) {
bgneal@45 11996 pr = e.previousSibling;
bgneal@45 11997
bgneal@45 11998 Event.remove(b, 'DOMNodeInserted', handler);
bgneal@45 11999
bgneal@45 12000 // Is there whitespace at the end of the node before then we might need the pesky BR
bgneal@45 12001 // to place the caret at a correct location see bug: #2013943
bgneal@45 12002 if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
bgneal@45 12003 return;
bgneal@45 12004
bgneal@45 12005 // Only remove BR elements that got inserted in the middle of the text
bgneal@45 12006 if (e.previousSibling || e.nextSibling)
bgneal@45 12007 ed.dom.remove(e);
bgneal@45 12008 }
bgneal@45 12009 };
bgneal@45 12010
bgneal@45 12011 // Listen for new nodes
bgneal@45 12012 Event._add(b, 'DOMNodeInserted', handler);
bgneal@45 12013
bgneal@45 12014 // Remove listener
bgneal@45 12015 window.setTimeout(function() {
bgneal@45 12016 Event._remove(b, 'DOMNodeInserted', handler);
bgneal@45 12017 }, 1);
bgneal@45 12018 }
bgneal@45 12019 });
bgneal@45 12020 })(tinymce);
bgneal@183 12021
bgneal@45 12022 (function(tinymce) {
bgneal@45 12023 // Shorten names
bgneal@45 12024 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
bgneal@45 12025
bgneal@45 12026 tinymce.create('tinymce.ControlManager', {
bgneal@45 12027 ControlManager : function(ed, s) {
bgneal@45 12028 var t = this, i;
bgneal@45 12029
bgneal@45 12030 s = s || {};
bgneal@45 12031 t.editor = ed;
bgneal@45 12032 t.controls = {};
bgneal@45 12033 t.onAdd = new tinymce.util.Dispatcher(t);
bgneal@45 12034 t.onPostRender = new tinymce.util.Dispatcher(t);
bgneal@45 12035 t.prefix = s.prefix || ed.id + '_';
bgneal@45 12036 t._cls = {};
bgneal@45 12037
bgneal@45 12038 t.onPostRender.add(function() {
bgneal@45 12039 each(t.controls, function(c) {
bgneal@45 12040 c.postRender();
bgneal@45 12041 });
bgneal@45 12042 });
bgneal@45 12043 },
bgneal@45 12044
bgneal@45 12045 get : function(id) {
bgneal@45 12046 return this.controls[this.prefix + id] || this.controls[id];
bgneal@45 12047 },
bgneal@45 12048
bgneal@45 12049 setActive : function(id, s) {
bgneal@45 12050 var c = null;
bgneal@45 12051
bgneal@45 12052 if (c = this.get(id))
bgneal@45 12053 c.setActive(s);
bgneal@45 12054
bgneal@45 12055 return c;
bgneal@45 12056 },
bgneal@45 12057
bgneal@45 12058 setDisabled : function(id, s) {
bgneal@45 12059 var c = null;
bgneal@45 12060
bgneal@45 12061 if (c = this.get(id))
bgneal@45 12062 c.setDisabled(s);
bgneal@45 12063
bgneal@45 12064 return c;
bgneal@45 12065 },
bgneal@45 12066
bgneal@45 12067 add : function(c) {
bgneal@45 12068 var t = this;
bgneal@45 12069
bgneal@45 12070 if (c) {
bgneal@45 12071 t.controls[c.id] = c;
bgneal@45 12072 t.onAdd.dispatch(c, t);
bgneal@45 12073 }
bgneal@45 12074
bgneal@45 12075 return c;
bgneal@45 12076 },
bgneal@45 12077
bgneal@45 12078 createControl : function(n) {
bgneal@45 12079 var c, t = this, ed = t.editor;
bgneal@45 12080
bgneal@45 12081 each(ed.plugins, function(p) {
bgneal@45 12082 if (p.createControl) {
bgneal@45 12083 c = p.createControl(n, t);
bgneal@45 12084
bgneal@45 12085 if (c)
bgneal@45 12086 return false;
bgneal@45 12087 }
bgneal@45 12088 });
bgneal@45 12089
bgneal@45 12090 switch (n) {
bgneal@45 12091 case "|":
bgneal@45 12092 case "separator":
bgneal@45 12093 return t.createSeparator();
bgneal@45 12094 }
bgneal@45 12095
bgneal@45 12096 if (!c && ed.buttons && (c = ed.buttons[n]))
bgneal@45 12097 return t.createButton(n, c);
bgneal@45 12098
bgneal@45 12099 return t.add(c);
bgneal@45 12100 },
bgneal@45 12101
bgneal@45 12102 createDropMenu : function(id, s, cc) {
bgneal@45 12103 var t = this, ed = t.editor, c, bm, v, cls;
bgneal@45 12104
bgneal@45 12105 s = extend({
bgneal@45 12106 'class' : 'mceDropDown',
bgneal@45 12107 constrain : ed.settings.constrain_menus
bgneal@45 12108 }, s);
bgneal@45 12109
bgneal@45 12110 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
bgneal@45 12111 if (v = ed.getParam('skin_variant'))
bgneal@45 12112 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
bgneal@45 12113
bgneal@45 12114 id = t.prefix + id;
bgneal@45 12115 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
bgneal@45 12116 c = t.controls[id] = new cls(id, s);
bgneal@45 12117 c.onAddItem.add(function(c, o) {
bgneal@45 12118 var s = o.settings;
bgneal@45 12119
bgneal@45 12120 s.title = ed.getLang(s.title, s.title);
bgneal@45 12121
bgneal@45 12122 if (!s.onclick) {
bgneal@45 12123 s.onclick = function(v) {
bgneal@183 12124 if (s.cmd)
bgneal@183 12125 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 12126 };
bgneal@45 12127 }
bgneal@45 12128 });
bgneal@45 12129
bgneal@45 12130 ed.onRemove.add(function() {
bgneal@45 12131 c.destroy();
bgneal@45 12132 });
bgneal@45 12133
bgneal@45 12134 // Fix for bug #1897785, #1898007
bgneal@45 12135 if (tinymce.isIE) {
bgneal@45 12136 c.onShowMenu.add(function() {
bgneal@183 12137 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@183 12138 ed.focus();
bgneal@183 12139
bgneal@45 12140 bm = ed.selection.getBookmark(1);
bgneal@45 12141 });
bgneal@45 12142
bgneal@45 12143 c.onHideMenu.add(function() {
bgneal@45 12144 if (bm) {
bgneal@45 12145 ed.selection.moveToBookmark(bm);
bgneal@45 12146 bm = 0;
bgneal@45 12147 }
bgneal@45 12148 });
bgneal@45 12149 }
bgneal@45 12150
bgneal@45 12151 return t.add(c);
bgneal@45 12152 },
bgneal@45 12153
bgneal@45 12154 createListBox : function(id, s, cc) {
bgneal@45 12155 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 12156
bgneal@45 12157 if (t.get(id))
bgneal@45 12158 return null;
bgneal@45 12159
bgneal@45 12160 s.title = ed.translate(s.title);
bgneal@45 12161 s.scope = s.scope || ed;
bgneal@45 12162
bgneal@45 12163 if (!s.onselect) {
bgneal@45 12164 s.onselect = function(v) {
bgneal@45 12165 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12166 };
bgneal@45 12167 }
bgneal@45 12168
bgneal@45 12169 s = extend({
bgneal@45 12170 title : s.title,
bgneal@45 12171 'class' : 'mce_' + id,
bgneal@45 12172 scope : s.scope,
bgneal@45 12173 control_manager : t
bgneal@45 12174 }, s);
bgneal@45 12175
bgneal@45 12176 id = t.prefix + id;
bgneal@45 12177
bgneal@45 12178 if (ed.settings.use_native_selects)
bgneal@45 12179 c = new tinymce.ui.NativeListBox(id, s);
bgneal@45 12180 else {
bgneal@45 12181 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
bgneal@45 12182 c = new cls(id, s);
bgneal@45 12183 }
bgneal@45 12184
bgneal@45 12185 t.controls[id] = c;
bgneal@45 12186
bgneal@45 12187 // Fix focus problem in Safari
bgneal@45 12188 if (tinymce.isWebKit) {
bgneal@45 12189 c.onPostRender.add(function(c, n) {
bgneal@45 12190 // Store bookmark on mousedown
bgneal@45 12191 Event.add(n, 'mousedown', function() {
bgneal@183 12192 ed.bookmark = ed.selection.getBookmark(1);
bgneal@45 12193 });
bgneal@45 12194
bgneal@45 12195 // Restore on focus, since it might be lost
bgneal@45 12196 Event.add(n, 'focus', function() {
bgneal@45 12197 ed.selection.moveToBookmark(ed.bookmark);
bgneal@45 12198 ed.bookmark = null;
bgneal@45 12199 });
bgneal@45 12200 });
bgneal@45 12201 }
bgneal@45 12202
bgneal@45 12203 if (c.hideMenu)
bgneal@45 12204 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12205
bgneal@45 12206 return t.add(c);
bgneal@45 12207 },
bgneal@45 12208
bgneal@45 12209 createButton : function(id, s, cc) {
bgneal@45 12210 var t = this, ed = t.editor, o, c, cls;
bgneal@45 12211
bgneal@45 12212 if (t.get(id))
bgneal@45 12213 return null;
bgneal@45 12214
bgneal@45 12215 s.title = ed.translate(s.title);
bgneal@45 12216 s.label = ed.translate(s.label);
bgneal@45 12217 s.scope = s.scope || ed;
bgneal@45 12218
bgneal@45 12219 if (!s.onclick && !s.menu_button) {
bgneal@45 12220 s.onclick = function() {
bgneal@45 12221 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@45 12222 };
bgneal@45 12223 }
bgneal@45 12224
bgneal@45 12225 s = extend({
bgneal@45 12226 title : s.title,
bgneal@45 12227 'class' : 'mce_' + id,
bgneal@45 12228 unavailable_prefix : ed.getLang('unavailable', ''),
bgneal@45 12229 scope : s.scope,
bgneal@45 12230 control_manager : t
bgneal@45 12231 }, s);
bgneal@45 12232
bgneal@45 12233 id = t.prefix + id;
bgneal@45 12234
bgneal@45 12235 if (s.menu_button) {
bgneal@45 12236 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
bgneal@45 12237 c = new cls(id, s);
bgneal@45 12238 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12239 } else {
bgneal@45 12240 cls = t._cls.button || tinymce.ui.Button;
bgneal@45 12241 c = new cls(id, s);
bgneal@45 12242 }
bgneal@45 12243
bgneal@45 12244 return t.add(c);
bgneal@45 12245 },
bgneal@45 12246
bgneal@45 12247 createMenuButton : function(id, s, cc) {
bgneal@45 12248 s = s || {};
bgneal@45 12249 s.menu_button = 1;
bgneal@45 12250
bgneal@45 12251 return this.createButton(id, s, cc);
bgneal@45 12252 },
bgneal@45 12253
bgneal@45 12254 createSplitButton : function(id, s, cc) {
bgneal@45 12255 var t = this, ed = t.editor, cmd, c, cls;
bgneal@45 12256
bgneal@45 12257 if (t.get(id))
bgneal@45 12258 return null;
bgneal@45 12259
bgneal@45 12260 s.title = ed.translate(s.title);
bgneal@45 12261 s.scope = s.scope || ed;
bgneal@45 12262
bgneal@45 12263 if (!s.onclick) {
bgneal@45 12264 s.onclick = function(v) {
bgneal@45 12265 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12266 };
bgneal@45 12267 }
bgneal@45 12268
bgneal@45 12269 if (!s.onselect) {
bgneal@45 12270 s.onselect = function(v) {
bgneal@45 12271 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12272 };
bgneal@45 12273 }
bgneal@45 12274
bgneal@45 12275 s = extend({
bgneal@45 12276 title : s.title,
bgneal@45 12277 'class' : 'mce_' + id,
bgneal@45 12278 scope : s.scope,
bgneal@45 12279 control_manager : t
bgneal@45 12280 }, s);
bgneal@45 12281
bgneal@45 12282 id = t.prefix + id;
bgneal@45 12283 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
bgneal@45 12284 c = t.add(new cls(id, s));
bgneal@45 12285 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12286
bgneal@45 12287 return c;
bgneal@45 12288 },
bgneal@45 12289
bgneal@45 12290 createColorSplitButton : function(id, s, cc) {
bgneal@45 12291 var t = this, ed = t.editor, cmd, c, cls, bm;
bgneal@45 12292
bgneal@45 12293 if (t.get(id))
bgneal@45 12294 return null;
bgneal@45 12295
bgneal@45 12296 s.title = ed.translate(s.title);
bgneal@45 12297 s.scope = s.scope || ed;
bgneal@45 12298
bgneal@45 12299 if (!s.onclick) {
bgneal@45 12300 s.onclick = function(v) {
bgneal@45 12301 if (tinymce.isIE)
bgneal@45 12302 bm = ed.selection.getBookmark(1);
bgneal@183 12303
bgneal@45 12304 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12305 };
bgneal@45 12306 }
bgneal@45 12307
bgneal@45 12308 if (!s.onselect) {
bgneal@45 12309 s.onselect = function(v) {
bgneal@45 12310 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@45 12311 };
bgneal@45 12312 }
bgneal@45 12313
bgneal@45 12314 s = extend({
bgneal@45 12315 title : s.title,
bgneal@45 12316 'class' : 'mce_' + id,
bgneal@45 12317 'menu_class' : ed.getParam('skin') + 'Skin',
bgneal@45 12318 scope : s.scope,
bgneal@45 12319 more_colors_title : ed.getLang('more_colors')
bgneal@45 12320 }, s);
bgneal@45 12321
bgneal@45 12322 id = t.prefix + id;
bgneal@45 12323 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
bgneal@45 12324 c = new cls(id, s);
bgneal@45 12325 ed.onMouseDown.add(c.hideMenu, c);
bgneal@45 12326
bgneal@45 12327 // Remove the menu element when the editor is removed
bgneal@45 12328 ed.onRemove.add(function() {
bgneal@45 12329 c.destroy();
bgneal@45 12330 });
bgneal@45 12331
bgneal@45 12332 // Fix for bug #1897785, #1898007
bgneal@45 12333 if (tinymce.isIE) {
bgneal@183 12334 c.onShowMenu.add(function() {
bgneal@183 12335 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@183 12336 ed.focus();
bgneal@183 12337 bm = ed.selection.getBookmark(1);
bgneal@183 12338 });
bgneal@183 12339
bgneal@45 12340 c.onHideMenu.add(function() {
bgneal@45 12341 if (bm) {
bgneal@45 12342 ed.selection.moveToBookmark(bm);
bgneal@45 12343 bm = 0;
bgneal@45 12344 }
bgneal@45 12345 });
bgneal@45 12346 }
bgneal@45 12347
bgneal@45 12348 return t.add(c);
bgneal@45 12349 },
bgneal@45 12350
bgneal@45 12351 createToolbar : function(id, s, cc) {
bgneal@45 12352 var c, t = this, cls;
bgneal@45 12353
bgneal@45 12354 id = t.prefix + id;
bgneal@45 12355 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
bgneal@45 12356 c = new cls(id, s);
bgneal@45 12357
bgneal@45 12358 if (t.get(id))
bgneal@45 12359 return null;
bgneal@45 12360
bgneal@45 12361 return t.add(c);
bgneal@45 12362 },
bgneal@45 12363
bgneal@45 12364 createSeparator : function(cc) {
bgneal@45 12365 var cls = cc || this._cls.separator || tinymce.ui.Separator;
bgneal@45 12366
bgneal@45 12367 return new cls();
bgneal@45 12368 },
bgneal@45 12369
bgneal@45 12370 setControlType : function(n, c) {
bgneal@45 12371 return this._cls[n.toLowerCase()] = c;
bgneal@45 12372 },
bgneal@183 12373
bgneal@45 12374 destroy : function() {
bgneal@45 12375 each(this.controls, function(c) {
bgneal@45 12376 c.destroy();
bgneal@45 12377 });
bgneal@45 12378
bgneal@45 12379 this.controls = null;
bgneal@45 12380 }
bgneal@183 12381 });
bgneal@45 12382 })(tinymce);
bgneal@183 12383
bgneal@45 12384 (function(tinymce) {
bgneal@45 12385 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
bgneal@45 12386
bgneal@45 12387 tinymce.create('tinymce.WindowManager', {
bgneal@45 12388 WindowManager : function(ed) {
bgneal@45 12389 var t = this;
bgneal@45 12390
bgneal@45 12391 t.editor = ed;
bgneal@45 12392 t.onOpen = new Dispatcher(t);
bgneal@45 12393 t.onClose = new Dispatcher(t);
bgneal@45 12394 t.params = {};
bgneal@45 12395 t.features = {};
bgneal@45 12396 },
bgneal@45 12397
bgneal@45 12398 open : function(s, p) {
bgneal@45 12399 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
bgneal@45 12400
bgneal@45 12401 // Default some options
bgneal@45 12402 s = s || {};
bgneal@45 12403 p = p || {};
bgneal@45 12404 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
bgneal@45 12405 sh = isOpera ? vp.h : screen.height;
bgneal@45 12406 s.name = s.name || 'mc_' + new Date().getTime();
bgneal@45 12407 s.width = parseInt(s.width || 320);
bgneal@45 12408 s.height = parseInt(s.height || 240);
bgneal@45 12409 s.resizable = true;
bgneal@45 12410 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
bgneal@45 12411 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
bgneal@45 12412 p.inline = false;
bgneal@45 12413 p.mce_width = s.width;
bgneal@45 12414 p.mce_height = s.height;
bgneal@45 12415 p.mce_auto_focus = s.auto_focus;
bgneal@45 12416
bgneal@45 12417 if (mo) {
bgneal@45 12418 if (isIE) {
bgneal@45 12419 s.center = true;
bgneal@45 12420 s.help = false;
bgneal@45 12421 s.dialogWidth = s.width + 'px';
bgneal@45 12422 s.dialogHeight = s.height + 'px';
bgneal@45 12423 s.scroll = s.scrollbars || false;
bgneal@45 12424 }
bgneal@45 12425 }
bgneal@45 12426
bgneal@45 12427 // Build features string
bgneal@45 12428 each(s, function(v, k) {
bgneal@45 12429 if (tinymce.is(v, 'boolean'))
bgneal@45 12430 v = v ? 'yes' : 'no';
bgneal@45 12431
bgneal@45 12432 if (!/^(name|url)$/.test(k)) {
bgneal@45 12433 if (isIE && mo)
bgneal@45 12434 f += (f ? ';' : '') + k + ':' + v;
bgneal@45 12435 else
bgneal@45 12436 f += (f ? ',' : '') + k + '=' + v;
bgneal@45 12437 }
bgneal@45 12438 });
bgneal@45 12439
bgneal@45 12440 t.features = s;
bgneal@45 12441 t.params = p;
bgneal@45 12442 t.onOpen.dispatch(t, s, p);
bgneal@45 12443
bgneal@45 12444 u = s.url || s.file;
bgneal@45 12445 u = tinymce._addVer(u);
bgneal@45 12446
bgneal@45 12447 try {
bgneal@45 12448 if (isIE && mo) {
bgneal@45 12449 w = 1;
bgneal@45 12450 window.showModalDialog(u, window, f);
bgneal@45 12451 } else
bgneal@45 12452 w = window.open(u, s.name, f);
bgneal@45 12453 } catch (ex) {
bgneal@45 12454 // Ignore
bgneal@45 12455 }
bgneal@45 12456
bgneal@45 12457 if (!w)
bgneal@45 12458 alert(t.editor.getLang('popup_blocked'));
bgneal@45 12459 },
bgneal@45 12460
bgneal@45 12461 close : function(w) {
bgneal@45 12462 w.close();
bgneal@45 12463 this.onClose.dispatch(this);
bgneal@45 12464 },
bgneal@45 12465
bgneal@45 12466 createInstance : function(cl, a, b, c, d, e) {
bgneal@45 12467 var f = tinymce.resolve(cl);
bgneal@45 12468
bgneal@45 12469 return new f(a, b, c, d, e);
bgneal@45 12470 },
bgneal@45 12471
bgneal@45 12472 confirm : function(t, cb, s, w) {
bgneal@45 12473 w = w || window;
bgneal@45 12474
bgneal@45 12475 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
bgneal@45 12476 },
bgneal@45 12477
bgneal@45 12478 alert : function(tx, cb, s, w) {
bgneal@45 12479 var t = this;
bgneal@45 12480
bgneal@45 12481 w = w || window;
bgneal@45 12482 w.alert(t._decode(t.editor.getLang(tx, tx)));
bgneal@45 12483
bgneal@45 12484 if (cb)
bgneal@45 12485 cb.call(s || t);
bgneal@45 12486 },
bgneal@45 12487
bgneal@183 12488 resizeBy : function(dw, dh, win) {
bgneal@183 12489 win.resizeBy(dw, dh);
bgneal@183 12490 },
bgneal@183 12491
bgneal@45 12492 // Internal functions
bgneal@45 12493
bgneal@45 12494 _decode : function(s) {
bgneal@45 12495 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
bgneal@45 12496 }
bgneal@183 12497 });
bgneal@183 12498 }(tinymce));
bgneal@183 12499 (function(tinymce) {
bgneal@183 12500 function CommandManager() {
bgneal@45 12501 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
bgneal@45 12502
bgneal@45 12503 function add(collection, cmd, func, scope) {
bgneal@45 12504 if (typeof(cmd) == 'string')
bgneal@45 12505 cmd = [cmd];
bgneal@45 12506
bgneal@45 12507 tinymce.each(cmd, function(cmd) {
bgneal@45 12508 collection[cmd.toLowerCase()] = {func : func, scope : scope};
bgneal@45 12509 });
bgneal@45 12510 };
bgneal@45 12511
bgneal@45 12512 tinymce.extend(this, {
bgneal@45 12513 add : function(cmd, func, scope) {
bgneal@45 12514 add(execCommands, cmd, func, scope);
bgneal@45 12515 },
bgneal@45 12516
bgneal@45 12517 addQueryStateHandler : function(cmd, func, scope) {
bgneal@45 12518 add(queryStateCommands, cmd, func, scope);
bgneal@45 12519 },
bgneal@45 12520
bgneal@45 12521 addQueryValueHandler : function(cmd, func, scope) {
bgneal@45 12522 add(queryValueCommands, cmd, func, scope);
bgneal@45 12523 },
bgneal@45 12524
bgneal@45 12525 execCommand : function(scope, cmd, ui, value, args) {
bgneal@45 12526 if (cmd = execCommands[cmd.toLowerCase()]) {
bgneal@45 12527 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
bgneal@45 12528 return true;
bgneal@45 12529 }
bgneal@45 12530 },
bgneal@45 12531
bgneal@45 12532 queryCommandValue : function() {
bgneal@45 12533 if (cmd = queryValueCommands[cmd.toLowerCase()])
bgneal@45 12534 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@45 12535 },
bgneal@45 12536
bgneal@45 12537 queryCommandState : function() {
bgneal@45 12538 if (cmd = queryStateCommands[cmd.toLowerCase()])
bgneal@45 12539 return cmd.func.call(scope || cmd.scope, ui, value, args);
bgneal@45 12540 }
bgneal@45 12541 });
bgneal@45 12542 };
bgneal@45 12543
bgneal@183 12544 tinymce.GlobalCommands = new CommandManager();
bgneal@45 12545 })(tinymce);
bgneal@45 12546 (function(tinymce) {
bgneal@183 12547 tinymce.Formatter = function(ed) {
bgneal@183 12548 var formats = {},
bgneal@183 12549 each = tinymce.each,
bgneal@183 12550 dom = ed.dom,
bgneal@183 12551 selection = ed.selection,
bgneal@183 12552 TreeWalker = tinymce.dom.TreeWalker,
bgneal@183 12553 rangeUtils = new tinymce.dom.RangeUtils(dom),
bgneal@183 12554 isValid = ed.schema.isValid,
bgneal@183 12555 isBlock = dom.isBlock,
bgneal@183 12556 forcedRootBlock = ed.settings.forced_root_block,
bgneal@183 12557 nodeIndex = dom.nodeIndex,
bgneal@183 12558 INVISIBLE_CHAR = '\uFEFF',
bgneal@183 12559 MCE_ATTR_RE = /^(src|href|style)$/,
bgneal@183 12560 FALSE = false,
bgneal@183 12561 TRUE = true,
bgneal@183 12562 undefined,
bgneal@183 12563 pendingFormats = {apply : [], remove : []};
bgneal@183 12564
bgneal@183 12565 function isArray(obj) {
bgneal@183 12566 return obj instanceof Array;
bgneal@183 12567 };
bgneal@183 12568
bgneal@183 12569 function getParents(node, selector) {
bgneal@183 12570 return dom.getParents(node, selector, dom.getRoot());
bgneal@183 12571 };
bgneal@183 12572
bgneal@183 12573 function isCaretNode(node) {
bgneal@183 12574 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
bgneal@183 12575 };
bgneal@183 12576
bgneal@183 12577 // Public functions
bgneal@183 12578
bgneal@183 12579 function get(name) {
bgneal@183 12580 return name ? formats[name] : formats;
bgneal@183 12581 };
bgneal@183 12582
bgneal@183 12583 function register(name, format) {
bgneal@183 12584 if (name) {
bgneal@183 12585 if (typeof(name) !== 'string') {
bgneal@183 12586 each(name, function(format, name) {
bgneal@183 12587 register(name, format);
bgneal@183 12588 });
bgneal@45 12589 } else {
bgneal@183 12590 // Force format into array and add it to internal collection
bgneal@183 12591 format = format.length ? format : [format];
bgneal@183 12592
bgneal@183 12593 each(format, function(format) {
bgneal@183 12594 // Set deep to false by default on selector formats this to avoid removing
bgneal@183 12595 // alignment on images inside paragraphs when alignment is changed on paragraphs
bgneal@183 12596 if (format.deep === undefined)
bgneal@183 12597 format.deep = !format.selector;
bgneal@183 12598
bgneal@183 12599 // Default to true
bgneal@183 12600 if (format.split === undefined)
bgneal@183 12601 format.split = !format.selector || format.inline;
bgneal@183 12602
bgneal@183 12603 // Default to true
bgneal@183 12604 if (format.remove === undefined && format.selector && !format.inline)
bgneal@183 12605 format.remove = 'none';
bgneal@183 12606
bgneal@183 12607 // Mark format as a mixed format inline + block level
bgneal@183 12608 if (format.selector && format.inline) {
bgneal@183 12609 format.mixed = true;
bgneal@183 12610 format.block_expand = true;
bgneal@183 12611 }
bgneal@183 12612
bgneal@183 12613 // Split classes if needed
bgneal@183 12614 if (typeof(format.classes) === 'string')
bgneal@183 12615 format.classes = format.classes.split(/\s+/);
bgneal@183 12616 });
bgneal@183 12617
bgneal@183 12618 formats[name] = format;
bgneal@183 12619 }
bgneal@183 12620 }
bgneal@183 12621 };
bgneal@183 12622
bgneal@183 12623 function apply(name, vars, node) {
bgneal@183 12624 var formatList = get(name), format = formatList[0], bookmark, rng, i;
bgneal@183 12625
bgneal@183 12626 function moveStart(rng) {
bgneal@183 12627 var container = rng.startContainer,
bgneal@183 12628 offset = rng.startOffset,
bgneal@183 12629 walker, node;
bgneal@183 12630
bgneal@183 12631 // Move startContainer/startOffset in to a suitable node
bgneal@183 12632 if (container.nodeType == 1 || container.nodeValue === "") {
bgneal@183 12633 walker = new TreeWalker(container.childNodes[offset]);
bgneal@183 12634 for (node = walker.current(); node; node = walker.next()) {
bgneal@183 12635 if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
bgneal@183 12636 rng.setStart(node, 0);
bgneal@183 12637 break;
bgneal@183 12638 }
bgneal@183 12639 }
bgneal@183 12640 }
bgneal@183 12641
bgneal@183 12642 return rng;
bgneal@183 12643 };
bgneal@183 12644
bgneal@183 12645 function setElementFormat(elm, fmt) {
bgneal@183 12646 fmt = fmt || format;
bgneal@183 12647
bgneal@183 12648 if (elm) {
bgneal@183 12649 each(fmt.styles, function(value, name) {
bgneal@183 12650 dom.setStyle(elm, name, replaceVars(value, vars));
bgneal@183 12651 });
bgneal@183 12652
bgneal@183 12653 each(fmt.attributes, function(value, name) {
bgneal@183 12654 dom.setAttrib(elm, name, replaceVars(value, vars));
bgneal@183 12655 });
bgneal@183 12656
bgneal@183 12657 each(fmt.classes, function(value) {
bgneal@183 12658 value = replaceVars(value, vars);
bgneal@183 12659
bgneal@183 12660 if (!dom.hasClass(elm, value))
bgneal@183 12661 dom.addClass(elm, value);
bgneal@183 12662 });
bgneal@183 12663 }
bgneal@183 12664 };
bgneal@183 12665
bgneal@183 12666 function applyRngStyle(rng) {
bgneal@183 12667 var newWrappers = [], wrapName, wrapElm;
bgneal@183 12668
bgneal@183 12669 // Setup wrapper element
bgneal@183 12670 wrapName = format.inline || format.block;
bgneal@183 12671 wrapElm = dom.create(wrapName);
bgneal@183 12672 setElementFormat(wrapElm);
bgneal@183 12673
bgneal@183 12674 rangeUtils.walk(rng, function(nodes) {
bgneal@183 12675 var currentWrapElm;
bgneal@183 12676
bgneal@183 12677 function process(node) {
bgneal@183 12678 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
bgneal@183 12679
bgneal@183 12680 // Stop wrapping on br elements
bgneal@183 12681 if (isEq(nodeName, 'br')) {
bgneal@183 12682 currentWrapElm = 0;
bgneal@183 12683
bgneal@183 12684 // Remove any br elements when we wrap things
bgneal@183 12685 if (format.block)
bgneal@183 12686 dom.remove(node);
bgneal@183 12687
bgneal@183 12688 return;
bgneal@183 12689 }
bgneal@183 12690
bgneal@183 12691 // If node is wrapper type
bgneal@183 12692 if (format.wrapper && matchNode(node, name, vars)) {
bgneal@183 12693 currentWrapElm = 0;
bgneal@183 12694 return;
bgneal@183 12695 }
bgneal@183 12696
bgneal@183 12697 // Can we rename the block
bgneal@183 12698 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
bgneal@183 12699 node = dom.rename(node, wrapName);
bgneal@183 12700 setElementFormat(node);
bgneal@183 12701 newWrappers.push(node);
bgneal@183 12702 currentWrapElm = 0;
bgneal@183 12703 return;
bgneal@183 12704 }
bgneal@183 12705
bgneal@183 12706 // Handle selector patterns
bgneal@183 12707 if (format.selector) {
bgneal@183 12708 // Look for matching formats
bgneal@183 12709 each(formatList, function(format) {
bgneal@183 12710 if (dom.is(node, format.selector) && !isCaretNode(node)) {
bgneal@183 12711 setElementFormat(node, format);
bgneal@183 12712 found = true;
bgneal@183 12713 }
bgneal@183 12714 });
bgneal@183 12715
bgneal@183 12716 // Contine processing if a selector match wasn't found and a inline element is defined
bgneal@183 12717 if (!format.inline || found) {
bgneal@183 12718 currentWrapElm = 0;
bgneal@183 12719 return;
bgneal@183 12720 }
bgneal@183 12721 }
bgneal@183 12722
bgneal@183 12723 // Is it valid to wrap this item
bgneal@183 12724 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
bgneal@183 12725 // Start wrapping
bgneal@183 12726 if (!currentWrapElm) {
bgneal@183 12727 // Wrap the node
bgneal@183 12728 currentWrapElm = wrapElm.cloneNode(FALSE);
bgneal@183 12729 node.parentNode.insertBefore(currentWrapElm, node);
bgneal@183 12730 newWrappers.push(currentWrapElm);
bgneal@183 12731 }
bgneal@183 12732
bgneal@183 12733 currentWrapElm.appendChild(node);
bgneal@183 12734 } else {
bgneal@183 12735 // Start a new wrapper for possible children
bgneal@183 12736 currentWrapElm = 0;
bgneal@183 12737
bgneal@183 12738 each(tinymce.grep(node.childNodes), process);
bgneal@183 12739
bgneal@183 12740 // End the last wrapper
bgneal@183 12741 currentWrapElm = 0;
bgneal@183 12742 }
bgneal@183 12743 };
bgneal@183 12744
bgneal@183 12745 // Process siblings from range
bgneal@183 12746 each(nodes, process);
bgneal@183 12747 });
bgneal@183 12748
bgneal@183 12749 // Cleanup
bgneal@183 12750 each(newWrappers, function(node) {
bgneal@183 12751 var childCount;
bgneal@183 12752
bgneal@183 12753 function getChildCount(node) {
bgneal@183 12754 var count = 0;
bgneal@183 12755
bgneal@183 12756 each(node.childNodes, function(node) {
bgneal@183 12757 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
bgneal@183 12758 count++;
bgneal@183 12759 });
bgneal@183 12760
bgneal@183 12761 return count;
bgneal@183 12762 };
bgneal@183 12763
bgneal@183 12764 function mergeStyles(node) {
bgneal@183 12765 var child, clone;
bgneal@183 12766
bgneal@183 12767 each(node.childNodes, function(node) {
bgneal@183 12768 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
bgneal@183 12769 child = node;
bgneal@183 12770 return FALSE; // break loop
bgneal@183 12771 }
bgneal@183 12772 });
bgneal@183 12773
bgneal@183 12774 // If child was found and of the same type as the current node
bgneal@183 12775 if (child && matchName(child, format)) {
bgneal@183 12776 clone = child.cloneNode(FALSE);
bgneal@183 12777 setElementFormat(clone);
bgneal@183 12778
bgneal@183 12779 dom.replace(clone, node, TRUE);
bgneal@183 12780 dom.remove(child, 1);
bgneal@183 12781 }
bgneal@183 12782
bgneal@183 12783 return clone || node;
bgneal@183 12784 };
bgneal@183 12785
bgneal@183 12786 childCount = getChildCount(node);
bgneal@183 12787
bgneal@183 12788 // Remove empty nodes
bgneal@183 12789 if (childCount === 0) {
bgneal@183 12790 dom.remove(node, 1);
bgneal@183 12791 return;
bgneal@183 12792 }
bgneal@183 12793
bgneal@183 12794 if (format.inline || format.wrapper) {
bgneal@183 12795 // Merges the current node with it's children of similar type to reduce the number of elements
bgneal@183 12796 if (!format.exact && childCount === 1)
bgneal@183 12797 node = mergeStyles(node);
bgneal@183 12798
bgneal@183 12799 // Remove/merge children
bgneal@183 12800 each(formatList, function(format) {
bgneal@183 12801 // Merge all children of similar type will move styles from child to parent
bgneal@183 12802 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
bgneal@183 12803 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
bgneal@183 12804 each(dom.select(format.inline, node), function(child) {
bgneal@183 12805 removeFormat(format, vars, child, format.exact ? child : null);
bgneal@183 12806 });
bgneal@183 12807 });
bgneal@183 12808
bgneal@183 12809 // Look for parent with similar style format
bgneal@183 12810 dom.getParent(node.parentNode, function(parent) {
bgneal@183 12811 if (matchNode(parent, name, vars)) {
bgneal@183 12812 dom.remove(node, 1);
bgneal@183 12813 node = 0;
bgneal@183 12814 return TRUE;
bgneal@183 12815 }
bgneal@183 12816 });
bgneal@183 12817
bgneal@183 12818 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
bgneal@183 12819 if (node) {
bgneal@183 12820 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
bgneal@183 12821 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
bgneal@183 12822 }
bgneal@183 12823 }
bgneal@183 12824 });
bgneal@183 12825 };
bgneal@183 12826
bgneal@183 12827 if (format) {
bgneal@183 12828 if (node) {
bgneal@183 12829 rng = dom.createRng();
bgneal@183 12830
bgneal@183 12831 rng.setStartBefore(node);
bgneal@183 12832 rng.setEndAfter(node);
bgneal@183 12833
bgneal@183 12834 applyRngStyle(rng);
bgneal@183 12835 } else {
bgneal@183 12836 if (!selection.isCollapsed() || !format.inline) {
bgneal@183 12837 // Apply formatting to selection
bgneal@183 12838 bookmark = selection.getBookmark();
bgneal@183 12839 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
bgneal@183 12840
bgneal@183 12841 selection.moveToBookmark(bookmark);
bgneal@183 12842 selection.setRng(moveStart(selection.getRng(TRUE)));
bgneal@183 12843 ed.nodeChanged();
bgneal@183 12844 } else
bgneal@183 12845 performCaretAction('apply', name, vars);
bgneal@183 12846 }
bgneal@183 12847 }
bgneal@183 12848 };
bgneal@183 12849
bgneal@183 12850 function remove(name, vars, node) {
bgneal@183 12851 var formatList = get(name), format = formatList[0], bookmark, i, rng;
bgneal@183 12852
bgneal@183 12853 // Merges the styles for each node
bgneal@183 12854 function process(node) {
bgneal@183 12855 var children, i, l;
bgneal@183 12856
bgneal@183 12857 // Grab the children first since the nodelist might be changed
bgneal@183 12858 children = tinymce.grep(node.childNodes);
bgneal@183 12859
bgneal@183 12860 // Process current node
bgneal@183 12861 for (i = 0, l = formatList.length; i < l; i++) {
bgneal@183 12862 if (removeFormat(formatList[i], vars, node, node))
bgneal@183 12863 break;
bgneal@183 12864 }
bgneal@183 12865
bgneal@183 12866 // Process the children
bgneal@183 12867 if (format.deep) {
bgneal@183 12868 for (i = 0, l = children.length; i < l; i++)
bgneal@183 12869 process(children[i]);
bgneal@183 12870 }
bgneal@183 12871 };
bgneal@183 12872
bgneal@183 12873 function findFormatRoot(container) {
bgneal@183 12874 var formatRoot;
bgneal@183 12875
bgneal@183 12876 // Find format root
bgneal@183 12877 each(getParents(container.parentNode).reverse(), function(parent) {
bgneal@183 12878 var format;
bgneal@183 12879
bgneal@183 12880 // Find format root element
bgneal@183 12881 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
bgneal@183 12882 // Is the node matching the format we are looking for
bgneal@183 12883 format = matchNode(parent, name, vars);
bgneal@183 12884 if (format && format.split !== false)
bgneal@183 12885 formatRoot = parent;
bgneal@183 12886 }
bgneal@183 12887 });
bgneal@183 12888
bgneal@183 12889 return formatRoot;
bgneal@183 12890 };
bgneal@183 12891
bgneal@183 12892 function wrapAndSplit(format_root, container, target, split) {
bgneal@183 12893 var parent, clone, lastClone, firstClone, i, formatRootParent;
bgneal@183 12894
bgneal@183 12895 // Format root found then clone formats and split it
bgneal@183 12896 if (format_root) {
bgneal@183 12897 formatRootParent = format_root.parentNode;
bgneal@183 12898
bgneal@183 12899 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
bgneal@183 12900 clone = parent.cloneNode(FALSE);
bgneal@183 12901
bgneal@183 12902 for (i = 0; i < formatList.length; i++) {
bgneal@183 12903 if (removeFormat(formatList[i], vars, clone, clone)) {
bgneal@183 12904 clone = 0;
bgneal@183 12905 break;
bgneal@183 12906 }
bgneal@183 12907 }
bgneal@183 12908
bgneal@183 12909 // Build wrapper node
bgneal@183 12910 if (clone) {
bgneal@183 12911 if (lastClone)
bgneal@183 12912 clone.appendChild(lastClone);
bgneal@183 12913
bgneal@183 12914 if (!firstClone)
bgneal@183 12915 firstClone = clone;
bgneal@183 12916
bgneal@183 12917 lastClone = clone;
bgneal@183 12918 }
bgneal@183 12919 }
bgneal@183 12920
bgneal@183 12921 // Never split block elements if the format is mixed
bgneal@183 12922 if (split && (!format.mixed || !isBlock(format_root)))
bgneal@183 12923 container = dom.split(format_root, container);
bgneal@183 12924
bgneal@183 12925 // Wrap container in cloned formats
bgneal@183 12926 if (lastClone) {
bgneal@183 12927 target.parentNode.insertBefore(lastClone, target);
bgneal@183 12928 firstClone.appendChild(target);
bgneal@183 12929 }
bgneal@183 12930 }
bgneal@183 12931
bgneal@183 12932 return container;
bgneal@183 12933 };
bgneal@183 12934
bgneal@183 12935 function splitToFormatRoot(container) {
bgneal@183 12936 return wrapAndSplit(findFormatRoot(container), container, container, true);
bgneal@183 12937 };
bgneal@183 12938
bgneal@183 12939 function unwrap(start) {
bgneal@183 12940 var node = dom.get(start ? '_start' : '_end'),
bgneal@183 12941 out = node[start ? 'firstChild' : 'lastChild'];
bgneal@183 12942
bgneal@183 12943 dom.remove(node, 1);
bgneal@183 12944
bgneal@183 12945 return out;
bgneal@183 12946 };
bgneal@183 12947
bgneal@183 12948 function removeRngStyle(rng) {
bgneal@183 12949 var startContainer, endContainer;
bgneal@183 12950
bgneal@183 12951 rng = expandRng(rng, formatList, TRUE);
bgneal@183 12952
bgneal@183 12953 if (format.split) {
bgneal@183 12954 startContainer = getContainer(rng, TRUE);
bgneal@183 12955 endContainer = getContainer(rng);
bgneal@183 12956
bgneal@183 12957 if (startContainer != endContainer) {
bgneal@183 12958 // Wrap start/end nodes in span element since these might be cloned/moved
bgneal@183 12959 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
bgneal@183 12960 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
bgneal@183 12961
bgneal@183 12962 // Split start/end
bgneal@183 12963 splitToFormatRoot(startContainer);
bgneal@183 12964 splitToFormatRoot(endContainer);
bgneal@183 12965
bgneal@183 12966 // Unwrap start/end to get real elements again
bgneal@183 12967 startContainer = unwrap(TRUE);
bgneal@183 12968 endContainer = unwrap();
bgneal@183 12969 } else
bgneal@183 12970 startContainer = endContainer = splitToFormatRoot(startContainer);
bgneal@183 12971
bgneal@183 12972 // Update range positions since they might have changed after the split operations
bgneal@183 12973 rng.startContainer = startContainer.parentNode;
bgneal@183 12974 rng.startOffset = nodeIndex(startContainer);
bgneal@183 12975 rng.endContainer = endContainer.parentNode;
bgneal@183 12976 rng.endOffset = nodeIndex(endContainer) + 1;
bgneal@183 12977 }
bgneal@183 12978
bgneal@183 12979 // Remove items between start/end
bgneal@183 12980 rangeUtils.walk(rng, function(nodes) {
bgneal@183 12981 each(nodes, function(node) {
bgneal@183 12982 process(node);
bgneal@183 12983 });
bgneal@183 12984 });
bgneal@183 12985 };
bgneal@183 12986
bgneal@183 12987 // Handle node
bgneal@183 12988 if (node) {
bgneal@183 12989 rng = dom.createRng();
bgneal@183 12990 rng.setStartBefore(node);
bgneal@183 12991 rng.setEndAfter(node);
bgneal@183 12992 removeRngStyle(rng);
bgneal@183 12993 return;
bgneal@183 12994 }
bgneal@183 12995
bgneal@183 12996 if (!selection.isCollapsed() || !format.inline) {
bgneal@183 12997 bookmark = selection.getBookmark();
bgneal@183 12998 removeRngStyle(selection.getRng(TRUE));
bgneal@183 12999 selection.moveToBookmark(bookmark);
bgneal@183 13000 ed.nodeChanged();
bgneal@45 13001 } else
bgneal@183 13002 performCaretAction('remove', name, vars);
bgneal@183 13003 };
bgneal@183 13004
bgneal@183 13005 function toggle(name, vars, node) {
bgneal@183 13006 if (match(name, vars, node))
bgneal@183 13007 remove(name, vars, node);
bgneal@183 13008 else
bgneal@183 13009 apply(name, vars, node);
bgneal@183 13010 };
bgneal@183 13011
bgneal@183 13012 function matchNode(node, name, vars) {
bgneal@183 13013 var formatList = get(name), format, i, classes;
bgneal@183 13014
bgneal@183 13015 function matchItems(node, format, item_name) {
bgneal@183 13016 var key, value, items = format[item_name], i;
bgneal@183 13017
bgneal@183 13018 // Check all items
bgneal@183 13019 if (items) {
bgneal@183 13020 // Non indexed object
bgneal@183 13021 if (items.length === undefined) {
bgneal@183 13022 for (key in items) {
bgneal@183 13023 if (items.hasOwnProperty(key)) {
bgneal@183 13024 if (item_name === 'attributes')
bgneal@183 13025 value = dom.getAttrib(node, key);
bgneal@183 13026 else
bgneal@183 13027 value = getStyle(node, key);
bgneal@183 13028
bgneal@183 13029 if (!isEq(value, replaceVars(items[key], vars)))
bgneal@183 13030 return;
bgneal@183 13031 }
bgneal@183 13032 }
bgneal@183 13033 } else {
bgneal@183 13034 // Only one match needed for indexed arrays
bgneal@183 13035 for (i = 0; i < items.length; i++) {
bgneal@183 13036 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
bgneal@183 13037 return format;
bgneal@183 13038 }
bgneal@183 13039 }
bgneal@183 13040 }
bgneal@183 13041
bgneal@183 13042 return format;
bgneal@183 13043 };
bgneal@183 13044
bgneal@183 13045 if (formatList && node) {
bgneal@183 13046 // Check each format in list
bgneal@183 13047 for (i = 0; i < formatList.length; i++) {
bgneal@183 13048 format = formatList[i];
bgneal@183 13049
bgneal@183 13050 // Name name, attributes, styles and classes
bgneal@183 13051 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
bgneal@183 13052 // Match classes
bgneal@183 13053 if (classes = format.classes) {
bgneal@183 13054 for (i = 0; i < classes.length; i++) {
bgneal@183 13055 if (!dom.hasClass(node, classes[i]))
bgneal@183 13056 return;
bgneal@183 13057 }
bgneal@183 13058 }
bgneal@183 13059
bgneal@183 13060 return format;
bgneal@183 13061 }
bgneal@183 13062 }
bgneal@183 13063 }
bgneal@183 13064 };
bgneal@183 13065
bgneal@183 13066 function match(name, vars, node) {
bgneal@183 13067 var startNode, i;
bgneal@183 13068
bgneal@183 13069 function matchParents(node) {
bgneal@183 13070 // Find first node with similar format settings
bgneal@183 13071 node = dom.getParent(node, function(node) {
bgneal@183 13072 return !!matchNode(node, name, vars);
bgneal@183 13073 });
bgneal@183 13074
bgneal@183 13075 // Do an exact check on the similar format element
bgneal@183 13076 return matchNode(node, name, vars);
bgneal@183 13077 };
bgneal@183 13078
bgneal@183 13079 // Check specified node
bgneal@183 13080 if (node)
bgneal@183 13081 return matchParents(node);
bgneal@183 13082
bgneal@183 13083 // Check pending formats
bgneal@183 13084 if (selection.isCollapsed()) {
bgneal@183 13085 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@183 13086 if (pendingFormats.apply[i].name == name)
bgneal@183 13087 return true;
bgneal@183 13088 }
bgneal@183 13089
bgneal@183 13090 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@183 13091 if (pendingFormats.remove[i].name == name)
bgneal@183 13092 return false;
bgneal@183 13093 }
bgneal@183 13094
bgneal@183 13095 return matchParents(selection.getNode());
bgneal@183 13096 }
bgneal@183 13097
bgneal@183 13098 // Check selected node
bgneal@183 13099 node = selection.getNode();
bgneal@183 13100 if (matchParents(node))
bgneal@183 13101 return TRUE;
bgneal@183 13102
bgneal@183 13103 // Check start node if it's different
bgneal@183 13104 startNode = selection.getStart();
bgneal@183 13105 if (startNode != node) {
bgneal@183 13106 if (matchParents(startNode))
bgneal@183 13107 return TRUE;
bgneal@183 13108 }
bgneal@183 13109
bgneal@183 13110 return FALSE;
bgneal@183 13111 };
bgneal@183 13112
bgneal@183 13113 function matchAll(names, vars) {
bgneal@183 13114 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
bgneal@183 13115
bgneal@183 13116 // If the selection is collapsed then check pending formats
bgneal@183 13117 if (selection.isCollapsed()) {
bgneal@183 13118 for (ni = 0; ni < names.length; ni++) {
bgneal@183 13119 // If the name is to be removed, then stop it from being added
bgneal@183 13120 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@183 13121 name = names[ni];
bgneal@183 13122
bgneal@183 13123 if (pendingFormats.remove[i].name == name) {
bgneal@183 13124 checkedMap[name] = true;
bgneal@183 13125 break;
bgneal@183 13126 }
bgneal@183 13127 }
bgneal@183 13128 }
bgneal@183 13129
bgneal@183 13130 // If the format is to be applied
bgneal@183 13131 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@183 13132 for (ni = 0; ni < names.length; ni++) {
bgneal@183 13133 name = names[ni];
bgneal@183 13134
bgneal@183 13135 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
bgneal@183 13136 checkedMap[name] = true;
bgneal@183 13137 matchedFormatNames.push(name);
bgneal@183 13138 }
bgneal@183 13139 }
bgneal@183 13140 }
bgneal@183 13141 }
bgneal@183 13142
bgneal@183 13143 // Check start of selection for formats
bgneal@183 13144 startElement = selection.getStart();
bgneal@183 13145 dom.getParent(startElement, function(node) {
bgneal@183 13146 var i, name;
bgneal@183 13147
bgneal@183 13148 for (i = 0; i < names.length; i++) {
bgneal@183 13149 name = names[i];
bgneal@183 13150
bgneal@183 13151 if (!checkedMap[name] && matchNode(node, name, vars)) {
bgneal@183 13152 checkedMap[name] = true;
bgneal@183 13153 matchedFormatNames.push(name);
bgneal@183 13154 }
bgneal@183 13155 }
bgneal@183 13156 });
bgneal@183 13157
bgneal@183 13158 return matchedFormatNames;
bgneal@183 13159 };
bgneal@183 13160
bgneal@183 13161 function canApply(name) {
bgneal@183 13162 var formatList = get(name), startNode, parents, i, x, selector;
bgneal@183 13163
bgneal@183 13164 if (formatList) {
bgneal@183 13165 startNode = selection.getStart();
bgneal@183 13166 parents = getParents(startNode);
bgneal@183 13167
bgneal@183 13168 for (x = formatList.length - 1; x >= 0; x--) {
bgneal@183 13169 selector = formatList[x].selector;
bgneal@183 13170
bgneal@183 13171 // Format is not selector based, then always return TRUE
bgneal@183 13172 if (!selector)
bgneal@183 13173 return TRUE;
bgneal@183 13174
bgneal@183 13175 for (i = parents.length - 1; i >= 0; i--) {
bgneal@183 13176 if (dom.is(parents[i], selector))
bgneal@183 13177 return TRUE;
bgneal@183 13178 }
bgneal@183 13179 }
bgneal@183 13180 }
bgneal@183 13181
bgneal@183 13182 return FALSE;
bgneal@183 13183 };
bgneal@183 13184
bgneal@183 13185 // Expose to public
bgneal@183 13186 tinymce.extend(this, {
bgneal@183 13187 get : get,
bgneal@183 13188 register : register,
bgneal@183 13189 apply : apply,
bgneal@183 13190 remove : remove,
bgneal@183 13191 toggle : toggle,
bgneal@183 13192 match : match,
bgneal@183 13193 matchAll : matchAll,
bgneal@183 13194 matchNode : matchNode,
bgneal@183 13195 canApply : canApply
bgneal@183 13196 });
bgneal@183 13197
bgneal@183 13198 // Private functions
bgneal@183 13199
bgneal@183 13200 function matchName(node, format) {
bgneal@183 13201 // Check for inline match
bgneal@183 13202 if (isEq(node, format.inline))
bgneal@183 13203 return TRUE;
bgneal@183 13204
bgneal@183 13205 // Check for block match
bgneal@183 13206 if (isEq(node, format.block))
bgneal@183 13207 return TRUE;
bgneal@183 13208
bgneal@183 13209 // Check for selector match
bgneal@183 13210 if (format.selector)
bgneal@183 13211 return dom.is(node, format.selector);
bgneal@183 13212 };
bgneal@183 13213
bgneal@183 13214 function isEq(str1, str2) {
bgneal@183 13215 str1 = str1 || '';
bgneal@183 13216 str2 = str2 || '';
bgneal@183 13217
bgneal@183 13218 str1 = '' + (str1.nodeName || str1);
bgneal@183 13219 str2 = '' + (str2.nodeName || str2);
bgneal@183 13220
bgneal@183 13221 return str1.toLowerCase() == str2.toLowerCase();
bgneal@183 13222 };
bgneal@183 13223
bgneal@183 13224 function getStyle(node, name) {
bgneal@183 13225 var styleVal = dom.getStyle(node, name);
bgneal@183 13226
bgneal@183 13227 // Force the format to hex
bgneal@183 13228 if (name == 'color' || name == 'backgroundColor')
bgneal@183 13229 styleVal = dom.toHex(styleVal);
bgneal@183 13230
bgneal@183 13231 // Opera will return bold as 700
bgneal@183 13232 if (name == 'fontWeight' && styleVal == 700)
bgneal@183 13233 styleVal = 'bold';
bgneal@183 13234
bgneal@183 13235 return '' + styleVal;
bgneal@183 13236 };
bgneal@183 13237
bgneal@183 13238 function replaceVars(value, vars) {
bgneal@183 13239 if (typeof(value) != "string")
bgneal@183 13240 value = value(vars);
bgneal@183 13241 else if (vars) {
bgneal@183 13242 value = value.replace(/%(\w+)/g, function(str, name) {
bgneal@183 13243 return vars[name] || str;
bgneal@183 13244 });
bgneal@183 13245 }
bgneal@183 13246
bgneal@183 13247 return value;
bgneal@183 13248 };
bgneal@183 13249
bgneal@183 13250 function isWhiteSpaceNode(node) {
bgneal@183 13251 return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue);
bgneal@183 13252 };
bgneal@183 13253
bgneal@183 13254 function wrap(node, name, attrs) {
bgneal@183 13255 var wrapper = dom.create(name, attrs);
bgneal@183 13256
bgneal@183 13257 node.parentNode.insertBefore(wrapper, node);
bgneal@183 13258 wrapper.appendChild(node);
bgneal@183 13259
bgneal@183 13260 return wrapper;
bgneal@183 13261 };
bgneal@183 13262
bgneal@183 13263 function expandRng(rng, format, remove) {
bgneal@183 13264 var startContainer = rng.startContainer,
bgneal@183 13265 startOffset = rng.startOffset,
bgneal@183 13266 endContainer = rng.endContainer,
bgneal@183 13267 endOffset = rng.endOffset, sibling, lastIdx;
bgneal@183 13268
bgneal@183 13269 // This function walks up the tree if there is no siblings before/after the node
bgneal@183 13270 function findParentContainer(container, child_name, sibling_name, root) {
bgneal@183 13271 var parent, child;
bgneal@183 13272
bgneal@183 13273 root = root || dom.getRoot();
bgneal@183 13274
bgneal@183 13275 for (;;) {
bgneal@183 13276 // Check if we can move up are we at root level or body level
bgneal@183 13277 parent = container.parentNode;
bgneal@183 13278
bgneal@183 13279 // Stop expanding on block elements or root depending on format
bgneal@183 13280 if (parent == root || (!format[0].block_expand && isBlock(parent)))
bgneal@183 13281 return container;
bgneal@183 13282
bgneal@183 13283 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
bgneal@183 13284 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@183 13285 return container;
bgneal@183 13286
bgneal@183 13287 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@183 13288 return container;
bgneal@183 13289 }
bgneal@183 13290
bgneal@183 13291 container = container.parentNode;
bgneal@183 13292 }
bgneal@183 13293
bgneal@183 13294 return container;
bgneal@183 13295 };
bgneal@183 13296
bgneal@183 13297 // If index based start position then resolve it
bgneal@183 13298 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
bgneal@183 13299 lastIdx = startContainer.childNodes.length - 1;
bgneal@183 13300 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
bgneal@183 13301
bgneal@183 13302 if (startContainer.nodeType == 3)
bgneal@183 13303 startOffset = 0;
bgneal@183 13304 }
bgneal@183 13305
bgneal@183 13306 // If index based end position then resolve it
bgneal@183 13307 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
bgneal@183 13308 lastIdx = endContainer.childNodes.length - 1;
bgneal@183 13309 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
bgneal@183 13310
bgneal@183 13311 if (endContainer.nodeType == 3)
bgneal@183 13312 endOffset = endContainer.nodeValue.length;
bgneal@183 13313 }
bgneal@183 13314
bgneal@183 13315 // Exclude bookmark nodes if possible
bgneal@183 13316 if (isBookmarkNode(startContainer.parentNode))
bgneal@183 13317 startContainer = startContainer.parentNode;
bgneal@183 13318
bgneal@183 13319 if (isBookmarkNode(startContainer))
bgneal@183 13320 startContainer = startContainer.nextSibling || startContainer;
bgneal@183 13321
bgneal@183 13322 if (isBookmarkNode(endContainer.parentNode))
bgneal@183 13323 endContainer = endContainer.parentNode;
bgneal@183 13324
bgneal@183 13325 if (isBookmarkNode(endContainer))
bgneal@183 13326 endContainer = endContainer.previousSibling || endContainer;
bgneal@183 13327
bgneal@183 13328 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
bgneal@183 13329 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
bgneal@183 13330 // This will reduce the number of wrapper elements that needs to be created
bgneal@183 13331 // Move start point up the tree
bgneal@183 13332 if (format[0].inline || format[0].block_expand) {
bgneal@183 13333 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@183 13334 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@183 13335 }
bgneal@183 13336
bgneal@183 13337 // Expand start/end container to matching selector
bgneal@183 13338 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
bgneal@183 13339 function findSelectorEndPoint(container, sibling_name) {
bgneal@183 13340 var parents, i, y;
bgneal@183 13341
bgneal@183 13342 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
bgneal@183 13343 container = container[sibling_name];
bgneal@183 13344
bgneal@183 13345 parents = getParents(container);
bgneal@183 13346 for (i = 0; i < parents.length; i++) {
bgneal@183 13347 for (y = 0; y < format.length; y++) {
bgneal@183 13348 if (dom.is(parents[i], format[y].selector))
bgneal@183 13349 return parents[i];
bgneal@183 13350 }
bgneal@183 13351 }
bgneal@183 13352
bgneal@183 13353 return container;
bgneal@183 13354 };
bgneal@183 13355
bgneal@183 13356 // Find new startContainer/endContainer if there is better one
bgneal@183 13357 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
bgneal@183 13358 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
bgneal@183 13359 }
bgneal@183 13360
bgneal@183 13361 // Expand start/end container to matching block element or text node
bgneal@183 13362 if (format[0].block || format[0].selector) {
bgneal@183 13363 function findBlockEndPoint(container, sibling_name, sibling_name2) {
bgneal@183 13364 var node;
bgneal@183 13365
bgneal@183 13366 // Expand to block of similar type
bgneal@183 13367 if (!format[0].wrapper)
bgneal@183 13368 node = dom.getParent(container, format[0].block);
bgneal@183 13369
bgneal@183 13370 // Expand to first wrappable block element or any block element
bgneal@183 13371 if (!node)
bgneal@183 13372 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
bgneal@183 13373
bgneal@183 13374 // Exclude inner lists from wrapping
bgneal@183 13375 if (node && format[0].wrapper)
bgneal@183 13376 node = getParents(node, 'ul,ol').reverse()[0] || node;
bgneal@183 13377
bgneal@183 13378 // Didn't find a block element look for first/last wrappable element
bgneal@183 13379 if (!node) {
bgneal@183 13380 node = container;
bgneal@183 13381
bgneal@183 13382 while (node[sibling_name] && !isBlock(node[sibling_name])) {
bgneal@183 13383 node = node[sibling_name];
bgneal@183 13384
bgneal@183 13385 // Break on BR but include it will be removed later on
bgneal@183 13386 // we can't remove it now since we need to check if it can be wrapped
bgneal@183 13387 if (isEq(node, 'br'))
bgneal@183 13388 break;
bgneal@183 13389 }
bgneal@183 13390 }
bgneal@183 13391
bgneal@183 13392 return node || container;
bgneal@183 13393 };
bgneal@183 13394
bgneal@183 13395 // Find new startContainer/endContainer if there is better one
bgneal@183 13396 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
bgneal@183 13397 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
bgneal@183 13398
bgneal@183 13399 // Non block element then try to expand up the leaf
bgneal@183 13400 if (format[0].block) {
bgneal@183 13401 if (!isBlock(startContainer))
bgneal@183 13402 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@183 13403
bgneal@183 13404 if (!isBlock(endContainer))
bgneal@183 13405 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@183 13406 }
bgneal@183 13407 }
bgneal@183 13408
bgneal@183 13409 // Setup index for startContainer
bgneal@183 13410 if (startContainer.nodeType == 1) {
bgneal@183 13411 startOffset = nodeIndex(startContainer);
bgneal@183 13412 startContainer = startContainer.parentNode;
bgneal@183 13413 }
bgneal@183 13414
bgneal@183 13415 // Setup index for endContainer
bgneal@183 13416 if (endContainer.nodeType == 1) {
bgneal@183 13417 endOffset = nodeIndex(endContainer) + 1;
bgneal@183 13418 endContainer = endContainer.parentNode;
bgneal@183 13419 }
bgneal@183 13420
bgneal@183 13421 // Return new range like object
bgneal@183 13422 return {
bgneal@183 13423 startContainer : startContainer,
bgneal@183 13424 startOffset : startOffset,
bgneal@183 13425 endContainer : endContainer,
bgneal@183 13426 endOffset : endOffset
bgneal@183 13427 };
bgneal@45 13428 }
bgneal@45 13429
bgneal@183 13430 function removeFormat(format, vars, node, compare_node) {
bgneal@183 13431 var i, attrs, stylesModified;
bgneal@183 13432
bgneal@183 13433 // Check if node matches format
bgneal@183 13434 if (!matchName(node, format))
bgneal@183 13435 return FALSE;
bgneal@183 13436
bgneal@183 13437 // Should we compare with format attribs and styles
bgneal@183 13438 if (format.remove != 'all') {
bgneal@183 13439 // Remove styles
bgneal@183 13440 each(format.styles, function(value, name) {
bgneal@183 13441 value = replaceVars(value, vars);
bgneal@183 13442
bgneal@183 13443 // Indexed array
bgneal@183 13444 if (typeof(name) === 'number') {
bgneal@183 13445 name = value;
bgneal@183 13446 compare_node = 0;
bgneal@183 13447 }
bgneal@183 13448
bgneal@183 13449 if (!compare_node || isEq(getStyle(compare_node, name), value))
bgneal@183 13450 dom.setStyle(node, name, '');
bgneal@183 13451
bgneal@183 13452 stylesModified = 1;
bgneal@183 13453 });
bgneal@183 13454
bgneal@183 13455 // Remove style attribute if it's empty
bgneal@183 13456 if (stylesModified && dom.getAttrib(node, 'style') == '') {
bgneal@183 13457 node.removeAttribute('style');
bgneal@183 13458 node.removeAttribute('_mce_style');
bgneal@183 13459 }
bgneal@183 13460
bgneal@183 13461 // Remove attributes
bgneal@183 13462 each(format.attributes, function(value, name) {
bgneal@183 13463 var valueOut;
bgneal@183 13464
bgneal@183 13465 value = replaceVars(value, vars);
bgneal@183 13466
bgneal@183 13467 // Indexed array
bgneal@183 13468 if (typeof(name) === 'number') {
bgneal@183 13469 name = value;
bgneal@183 13470 compare_node = 0;
bgneal@183 13471 }
bgneal@183 13472
bgneal@183 13473 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
bgneal@183 13474 // Keep internal classes
bgneal@183 13475 if (name == 'class') {
bgneal@183 13476 value = dom.getAttrib(node, name);
bgneal@183 13477 if (value) {
bgneal@183 13478 // Build new class value where everything is removed except the internal prefixed classes
bgneal@183 13479 valueOut = '';
bgneal@183 13480 each(value.split(/\s+/), function(cls) {
bgneal@183 13481 if (/mce\w+/.test(cls))
bgneal@183 13482 valueOut += (valueOut ? ' ' : '') + cls;
bgneal@183 13483 });
bgneal@183 13484
bgneal@183 13485 // We got some internal classes left
bgneal@183 13486 if (valueOut) {
bgneal@183 13487 dom.setAttrib(node, name, valueOut);
bgneal@183 13488 return;
bgneal@183 13489 }
bgneal@183 13490 }
bgneal@183 13491 }
bgneal@183 13492
bgneal@183 13493 // IE6 has a bug where the attribute doesn't get removed correctly
bgneal@183 13494 if (name == "class")
bgneal@183 13495 node.removeAttribute('className');
bgneal@183 13496
bgneal@183 13497 // Remove mce prefixed attributes
bgneal@183 13498 if (MCE_ATTR_RE.test(name))
bgneal@183 13499 node.removeAttribute('_mce_' + name);
bgneal@183 13500
bgneal@183 13501 node.removeAttribute(name);
bgneal@183 13502 }
bgneal@183 13503 });
bgneal@183 13504
bgneal@183 13505 // Remove classes
bgneal@183 13506 each(format.classes, function(value) {
bgneal@183 13507 value = replaceVars(value, vars);
bgneal@183 13508
bgneal@183 13509 if (!compare_node || dom.hasClass(compare_node, value))
bgneal@183 13510 dom.removeClass(node, value);
bgneal@183 13511 });
bgneal@183 13512
bgneal@183 13513 // Check for non internal attributes
bgneal@183 13514 attrs = dom.getAttribs(node);
bgneal@183 13515 for (i = 0; i < attrs.length; i++) {
bgneal@183 13516 if (attrs[i].nodeName.indexOf('_') !== 0)
bgneal@183 13517 return FALSE;
bgneal@183 13518 }
bgneal@183 13519 }
bgneal@183 13520
bgneal@183 13521 // Remove the inline child if it's empty for example <b> or <span>
bgneal@183 13522 if (format.remove != 'none') {
bgneal@183 13523 removeNode(node, format);
bgneal@183 13524 return TRUE;
bgneal@183 13525 }
bgneal@183 13526 };
bgneal@183 13527
bgneal@183 13528 function removeNode(node, format) {
bgneal@183 13529 var parentNode = node.parentNode, rootBlockElm;
bgneal@183 13530
bgneal@183 13531 if (format.block) {
bgneal@183 13532 if (!forcedRootBlock) {
bgneal@183 13533 function find(node, next, inc) {
bgneal@183 13534 node = getNonWhiteSpaceSibling(node, next, inc);
bgneal@183 13535
bgneal@183 13536 return !node || (node.nodeName == 'BR' || isBlock(node));
bgneal@183 13537 };
bgneal@183 13538
bgneal@183 13539 // Append BR elements if needed before we remove the block
bgneal@183 13540 if (isBlock(node) && !isBlock(parentNode)) {
bgneal@183 13541 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
bgneal@183 13542 node.insertBefore(dom.create('br'), node.firstChild);
bgneal@183 13543
bgneal@183 13544 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
bgneal@183 13545 node.appendChild(dom.create('br'));
bgneal@183 13546 }
bgneal@183 13547 } else {
bgneal@183 13548 // Wrap the block in a forcedRootBlock if we are at the root of document
bgneal@183 13549 if (parentNode == dom.getRoot()) {
bgneal@183 13550 if (!format.list_block || !isEq(node, format.list_block)) {
bgneal@183 13551 each(tinymce.grep(node.childNodes), function(node) {
bgneal@183 13552 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
bgneal@183 13553 if (!rootBlockElm)
bgneal@183 13554 rootBlockElm = wrap(node, forcedRootBlock);
bgneal@183 13555 else
bgneal@183 13556 rootBlockElm.appendChild(node);
bgneal@183 13557 } else
bgneal@183 13558 rootBlockElm = 0;
bgneal@183 13559 });
bgneal@183 13560 }
bgneal@183 13561 }
bgneal@183 13562 }
bgneal@183 13563 }
bgneal@183 13564
bgneal@183 13565 // Never remove nodes that isn't the specified inline element if a selector is specified too
bgneal@183 13566 if (format.selector && format.inline && !isEq(format.inline, node))
bgneal@45 13567 return;
bgneal@183 13568
bgneal@183 13569 dom.remove(node, 1);
bgneal@183 13570 };
bgneal@183 13571
bgneal@183 13572 function getNonWhiteSpaceSibling(node, next, inc) {
bgneal@183 13573 if (node) {
bgneal@183 13574 next = next ? 'nextSibling' : 'previousSibling';
bgneal@183 13575
bgneal@183 13576 for (node = inc ? node : node[next]; node; node = node[next]) {
bgneal@183 13577 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
bgneal@183 13578 return node;
bgneal@183 13579 }
bgneal@183 13580 }
bgneal@183 13581 };
bgneal@183 13582
bgneal@183 13583 function isBookmarkNode(node) {
bgneal@183 13584 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
bgneal@183 13585 };
bgneal@183 13586
bgneal@183 13587 function mergeSiblings(prev, next) {
bgneal@183 13588 var marker, sibling, tmpSibling;
bgneal@183 13589
bgneal@183 13590 function compareElements(node1, node2) {
bgneal@183 13591 // Not the same name
bgneal@183 13592 if (node1.nodeName != node2.nodeName)
bgneal@183 13593 return FALSE;
bgneal@183 13594
bgneal@183 13595 function getAttribs(node) {
bgneal@183 13596 var attribs = {};
bgneal@183 13597
bgneal@183 13598 each(dom.getAttribs(node), function(attr) {
bgneal@183 13599 var name = attr.nodeName.toLowerCase();
bgneal@183 13600
bgneal@183 13601 // Don't compare internal attributes or style
bgneal@183 13602 if (name.indexOf('_') !== 0 && name !== 'style')
bgneal@183 13603 attribs[name] = dom.getAttrib(node, name);
bgneal@183 13604 });
bgneal@183 13605
bgneal@183 13606 return attribs;
bgneal@183 13607 };
bgneal@183 13608
bgneal@183 13609 function compareObjects(obj1, obj2) {
bgneal@183 13610 var value, name;
bgneal@183 13611
bgneal@183 13612 for (name in obj1) {
bgneal@183 13613 // Obj1 has item obj2 doesn't have
bgneal@183 13614 if (obj1.hasOwnProperty(name)) {
bgneal@183 13615 value = obj2[name];
bgneal@183 13616
bgneal@183 13617 // Obj2 doesn't have obj1 item
bgneal@183 13618 if (value === undefined)
bgneal@183 13619 return FALSE;
bgneal@183 13620
bgneal@183 13621 // Obj2 item has a different value
bgneal@183 13622 if (obj1[name] != value)
bgneal@183 13623 return FALSE;
bgneal@183 13624
bgneal@183 13625 // Delete similar value
bgneal@183 13626 delete obj2[name];
bgneal@183 13627 }
bgneal@183 13628 }
bgneal@183 13629
bgneal@183 13630 // Check if obj 2 has something obj 1 doesn't have
bgneal@183 13631 for (name in obj2) {
bgneal@183 13632 // Obj2 has item obj1 doesn't have
bgneal@183 13633 if (obj2.hasOwnProperty(name))
bgneal@183 13634 return FALSE;
bgneal@183 13635 }
bgneal@183 13636
bgneal@183 13637 return TRUE;
bgneal@183 13638 };
bgneal@183 13639
bgneal@183 13640 // Attribs are not the same
bgneal@183 13641 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
bgneal@183 13642 return FALSE;
bgneal@183 13643
bgneal@183 13644 // Styles are not the same
bgneal@183 13645 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
bgneal@183 13646 return FALSE;
bgneal@183 13647
bgneal@183 13648 return TRUE;
bgneal@183 13649 };
bgneal@183 13650
bgneal@183 13651 // Check if next/prev exists and that they are elements
bgneal@183 13652 if (prev && next) {
bgneal@183 13653 function findElementSibling(node, sibling_name) {
bgneal@183 13654 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
bgneal@183 13655 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@183 13656 return node;
bgneal@183 13657
bgneal@183 13658 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@183 13659 return sibling;
bgneal@183 13660 }
bgneal@183 13661
bgneal@183 13662 return node;
bgneal@183 13663 };
bgneal@183 13664
bgneal@183 13665 // If previous sibling is empty then jump over it
bgneal@183 13666 prev = findElementSibling(prev, 'previousSibling');
bgneal@183 13667 next = findElementSibling(next, 'nextSibling');
bgneal@183 13668
bgneal@183 13669 // Compare next and previous nodes
bgneal@183 13670 if (compareElements(prev, next)) {
bgneal@183 13671 // Append nodes between
bgneal@183 13672 for (sibling = prev.nextSibling; sibling && sibling != next;) {
bgneal@183 13673 tmpSibling = sibling;
bgneal@183 13674 sibling = sibling.nextSibling;
bgneal@183 13675 prev.appendChild(tmpSibling);
bgneal@183 13676 }
bgneal@183 13677
bgneal@183 13678 // Remove next node
bgneal@183 13679 dom.remove(next);
bgneal@183 13680
bgneal@183 13681 // Move children into prev node
bgneal@183 13682 each(tinymce.grep(next.childNodes), function(node) {
bgneal@183 13683 prev.appendChild(node);
bgneal@183 13684 });
bgneal@183 13685
bgneal@183 13686 return prev;
bgneal@183 13687 }
bgneal@183 13688 }
bgneal@183 13689
bgneal@183 13690 return next;
bgneal@183 13691 };
bgneal@183 13692
bgneal@183 13693 function isTextBlock(name) {
bgneal@183 13694 return /^(h[1-6]|p|div|pre|address)$/.test(name);
bgneal@183 13695 };
bgneal@183 13696
bgneal@183 13697 function getContainer(rng, start) {
bgneal@183 13698 var container, offset, lastIdx;
bgneal@183 13699
bgneal@183 13700 container = rng[start ? 'startContainer' : 'endContainer'];
bgneal@183 13701 offset = rng[start ? 'startOffset' : 'endOffset'];
bgneal@183 13702
bgneal@183 13703 if (container.nodeType == 1) {
bgneal@183 13704 lastIdx = container.childNodes.length - 1;
bgneal@183 13705
bgneal@183 13706 if (!start && offset)
bgneal@183 13707 offset--;
bgneal@183 13708
bgneal@183 13709 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
bgneal@183 13710 }
bgneal@183 13711
bgneal@183 13712 return container;
bgneal@183 13713 };
bgneal@183 13714
bgneal@183 13715 function performCaretAction(type, name, vars) {
bgneal@183 13716 var i, currentPendingFormats = pendingFormats[type],
bgneal@183 13717 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
bgneal@183 13718
bgneal@183 13719 function hasPending() {
bgneal@183 13720 return pendingFormats.apply.length || pendingFormats.remove.length;
bgneal@183 13721 };
bgneal@183 13722
bgneal@183 13723 function resetPending() {
bgneal@183 13724 pendingFormats.apply = [];
bgneal@183 13725 pendingFormats.remove = [];
bgneal@183 13726 };
bgneal@183 13727
bgneal@183 13728 function perform(caret_node) {
bgneal@183 13729 // Apply pending formats
bgneal@183 13730 each(pendingFormats.apply.reverse(), function(item) {
bgneal@183 13731 apply(item.name, item.vars, caret_node);
bgneal@183 13732 });
bgneal@183 13733
bgneal@183 13734 // Remove pending formats
bgneal@183 13735 each(pendingFormats.remove.reverse(), function(item) {
bgneal@183 13736 remove(item.name, item.vars, caret_node);
bgneal@183 13737 });
bgneal@183 13738
bgneal@183 13739 dom.remove(caret_node, 1);
bgneal@183 13740 resetPending();
bgneal@183 13741 };
bgneal@183 13742
bgneal@183 13743 // Check if it already exists then ignore it
bgneal@183 13744 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
bgneal@183 13745 if (currentPendingFormats[i].name == name)
bgneal@183 13746 return;
bgneal@183 13747 }
bgneal@183 13748
bgneal@183 13749 currentPendingFormats.push({name : name, vars : vars});
bgneal@183 13750
bgneal@183 13751 // Check if it's in the other type, then remove it
bgneal@183 13752 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
bgneal@183 13753 if (otherPendingFormats[i].name == name)
bgneal@183 13754 otherPendingFormats.splice(i, 1);
bgneal@183 13755 }
bgneal@183 13756
bgneal@183 13757 // Pending apply or remove formats
bgneal@183 13758 if (hasPending()) {
bgneal@183 13759 ed.getDoc().execCommand('FontName', false, 'mceinline');
bgneal@183 13760
bgneal@183 13761 // IE will convert the current word
bgneal@183 13762 each(dom.select('font,span'), function(node) {
bgneal@183 13763 var bookmark;
bgneal@183 13764
bgneal@183 13765 if (isCaretNode(node)) {
bgneal@183 13766 bookmark = selection.getBookmark();
bgneal@183 13767 perform(node);
bgneal@183 13768 selection.moveToBookmark(bookmark);
bgneal@183 13769 ed.nodeChanged();
bgneal@183 13770 }
bgneal@183 13771 });
bgneal@183 13772
bgneal@183 13773 // Only register listeners once if we need to
bgneal@183 13774 if (!pendingFormats.isListening && hasPending()) {
bgneal@183 13775 pendingFormats.isListening = true;
bgneal@183 13776
bgneal@183 13777 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
bgneal@183 13778 ed[event].addToTop(function(ed, e) {
bgneal@183 13779 if (hasPending()) {
bgneal@183 13780 each(dom.select('font,span'), function(node) {
bgneal@183 13781 var bookmark, textNode, rng;
bgneal@183 13782
bgneal@183 13783 // Look for marker
bgneal@183 13784 if (isCaretNode(node)) {
bgneal@183 13785 textNode = node.firstChild;
bgneal@183 13786
bgneal@183 13787 perform(node);
bgneal@183 13788
bgneal@183 13789 rng = dom.createRng();
bgneal@183 13790 rng.setStart(textNode, textNode.nodeValue.length);
bgneal@183 13791 rng.setEnd(textNode, textNode.nodeValue.length);
bgneal@183 13792 selection.setRng(rng);
bgneal@183 13793 ed.nodeChanged();
bgneal@183 13794 }
bgneal@183 13795 });
bgneal@183 13796
bgneal@183 13797 // Always unbind and clear pending styles on keyup
bgneal@183 13798 if (e.type == 'keyup' || e.type == 'mouseup')
bgneal@183 13799 resetPending();
bgneal@183 13800 }
bgneal@183 13801 });
bgneal@183 13802 });
bgneal@183 13803 }
bgneal@183 13804 }
bgneal@183 13805 };
bgneal@183 13806 };
bgneal@183 13807 })(tinymce);
bgneal@183 13808
bgneal@183 13809 tinymce.onAddEditor.add(function(tinymce, ed) {
bgneal@183 13810 var filters, fontSizes, dom, settings = ed.settings;
bgneal@183 13811
bgneal@183 13812 if (settings.inline_styles) {
bgneal@183 13813 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@183 13814
bgneal@183 13815 function replaceWithSpan(node, styles) {
bgneal@183 13816 dom.replace(dom.create('span', {
bgneal@183 13817 style : styles
bgneal@183 13818 }), node, 1);
bgneal@183 13819 };
bgneal@183 13820
bgneal@183 13821 filters = {
bgneal@183 13822 font : function(dom, node) {
bgneal@183 13823 replaceWithSpan(node, {
bgneal@183 13824 backgroundColor : node.style.backgroundColor,
bgneal@183 13825 color : node.color,
bgneal@183 13826 fontFamily : node.face,
bgneal@183 13827 fontSize : fontSizes[parseInt(node.size) - 1]
bgneal@183 13828 });
bgneal@183 13829 },
bgneal@183 13830
bgneal@183 13831 u : function(dom, node) {
bgneal@183 13832 replaceWithSpan(node, {
bgneal@183 13833 textDecoration : 'underline'
bgneal@183 13834 });
bgneal@183 13835 },
bgneal@183 13836
bgneal@183 13837 strike : function(dom, node) {
bgneal@183 13838 replaceWithSpan(node, {
bgneal@183 13839 textDecoration : 'line-through'
bgneal@183 13840 });
bgneal@183 13841 }
bgneal@183 13842 };
bgneal@183 13843
bgneal@183 13844 function convert(editor, params) {
bgneal@183 13845 dom = editor.dom;
bgneal@183 13846
bgneal@183 13847 if (settings.convert_fonts_to_spans) {
bgneal@183 13848 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
bgneal@183 13849 filters[node.nodeName.toLowerCase()](ed.dom, node);
bgneal@183 13850 });
bgneal@183 13851 }
bgneal@183 13852 };
bgneal@183 13853
bgneal@183 13854 ed.onPreProcess.add(convert);
bgneal@183 13855
bgneal@183 13856 ed.onInit.add(function() {
bgneal@183 13857 ed.selection.onSetContent.add(convert);
bgneal@45 13858 });
bgneal@183 13859 }
bgneal@183 13860 });
bgneal@183 13861