annotate static/js/tiny_mce/tiny_mce_src.js @ 645:99f7917702ca

Fix 081a88b3bfc8, javascript resize of forum images. Commit 081a88b3bfc8 broke those pages that loaded forums.js but did not load the imagesLoaded jQuery extension. Now we have arranged it so that only the forums topic view loads imagesLoaded and put the resizing javascript inline.
author Brian Neal <bgneal@gmail.com>
date Mon, 11 Mar 2013 15:30:25 -0500
parents 6c182ceb7147
children
rev   line source
bgneal@312 1 (function(win) {
bgneal@312 2 var whiteSpaceRe = /^\s*|\s*$/g,
bgneal@442 3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
bgneal@312 4
bgneal@312 5 var tinymce = {
bgneal@312 6 majorVersion : '3',
bgneal@312 7
bgneal@442 8 minorVersion : '4.2',
bgneal@442 9
bgneal@442 10 releaseDate : '2011-04-07',
bgneal@312 11
bgneal@312 12 _init : function() {
bgneal@312 13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
bgneal@312 14
bgneal@312 15 t.isOpera = win.opera && opera.buildNumber;
bgneal@312 16
bgneal@312 17 t.isWebKit = /WebKit/.test(ua);
bgneal@312 18
bgneal@312 19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
bgneal@312 20
bgneal@312 21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
bgneal@312 22
bgneal@312 23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
bgneal@312 24
bgneal@312 25 t.isMac = ua.indexOf('Mac') != -1;
bgneal@312 26
bgneal@312 27 t.isAir = /adobeair/i.test(ua);
bgneal@312 28
bgneal@312 29 t.isIDevice = /(iPad|iPhone)/.test(ua);
bgneal@312 30
bgneal@312 31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
bgneal@312 32 if (win.tinyMCEPreInit) {
bgneal@312 33 t.suffix = tinyMCEPreInit.suffix;
bgneal@312 34 t.baseURL = tinyMCEPreInit.base;
bgneal@312 35 t.query = tinyMCEPreInit.query;
bgneal@312 36 return;
bgneal@312 37 }
bgneal@312 38
bgneal@312 39 // Get suffix and base
bgneal@312 40 t.suffix = '';
bgneal@312 41
bgneal@312 42 // If base element found, add that infront of baseURL
bgneal@312 43 nl = d.getElementsByTagName('base');
bgneal@312 44 for (i=0; i<nl.length; i++) {
bgneal@312 45 if (v = nl[i].href) {
bgneal@312 46 // Host only value like http://site.com or http://site.com:8008
bgneal@312 47 if (/^https?:\/\/[^\/]+$/.test(v))
bgneal@312 48 v += '/';
bgneal@312 49
bgneal@312 50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
bgneal@312 51 }
bgneal@312 52 }
bgneal@312 53
bgneal@312 54 function getBase(n) {
bgneal@312 55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
bgneal@312 56 if (/_(src|dev)\.js/g.test(n.src))
bgneal@312 57 t.suffix = '_src';
bgneal@312 58
bgneal@312 59 if ((p = n.src.indexOf('?')) != -1)
bgneal@312 60 t.query = n.src.substring(p + 1);
bgneal@312 61
bgneal@312 62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
bgneal@312 63
bgneal@312 64 // If path to script is relative and a base href was found add that one infront
bgneal@312 65 // the src property will always be an absolute one on non IE browsers and IE 8
bgneal@312 66 // so this logic will basically only be executed on older IE versions
bgneal@312 67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
bgneal@312 68 t.baseURL = base + t.baseURL;
bgneal@312 69
bgneal@312 70 return t.baseURL;
bgneal@312 71 }
bgneal@312 72
bgneal@312 73 return null;
bgneal@312 74 };
bgneal@312 75
bgneal@312 76 // Check document
bgneal@312 77 nl = d.getElementsByTagName('script');
bgneal@312 78 for (i=0; i<nl.length; i++) {
bgneal@312 79 if (getBase(nl[i]))
bgneal@312 80 return;
bgneal@312 81 }
bgneal@312 82
bgneal@312 83 // Check head
bgneal@312 84 n = d.getElementsByTagName('head')[0];
bgneal@312 85 if (n) {
bgneal@312 86 nl = n.getElementsByTagName('script');
bgneal@312 87 for (i=0; i<nl.length; i++) {
bgneal@312 88 if (getBase(nl[i]))
bgneal@312 89 return;
bgneal@312 90 }
bgneal@312 91 }
bgneal@312 92
bgneal@312 93 return;
bgneal@312 94 },
bgneal@312 95
bgneal@312 96 is : function(o, t) {
bgneal@312 97 if (!t)
bgneal@312 98 return o !== undefined;
bgneal@312 99
bgneal@312 100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
bgneal@312 101 return true;
bgneal@312 102
bgneal@312 103 return typeof(o) == t;
bgneal@312 104 },
bgneal@312 105
bgneal@442 106 makeMap : function(items, delim, map) {
bgneal@442 107 var i;
bgneal@442 108
bgneal@442 109 items = items || [];
bgneal@442 110 delim = delim || ',';
bgneal@442 111
bgneal@442 112 if (typeof(items) == "string")
bgneal@442 113 items = items.split(delim);
bgneal@442 114
bgneal@442 115 map = map || {};
bgneal@442 116
bgneal@442 117 i = items.length;
bgneal@442 118 while (i--)
bgneal@442 119 map[items[i]] = {};
bgneal@442 120
bgneal@442 121 return map;
bgneal@442 122 },
bgneal@442 123
bgneal@312 124 each : function(o, cb, s) {
bgneal@312 125 var n, l;
bgneal@312 126
bgneal@312 127 if (!o)
bgneal@312 128 return 0;
bgneal@312 129
bgneal@312 130 s = s || o;
bgneal@312 131
bgneal@312 132 if (o.length !== undefined) {
bgneal@312 133 // Indexed arrays, needed for Safari
bgneal@312 134 for (n=0, l = o.length; n < l; n++) {
bgneal@312 135 if (cb.call(s, o[n], n, o) === false)
bgneal@312 136 return 0;
bgneal@312 137 }
bgneal@312 138 } else {
bgneal@312 139 // Hashtables
bgneal@312 140 for (n in o) {
bgneal@312 141 if (o.hasOwnProperty(n)) {
bgneal@312 142 if (cb.call(s, o[n], n, o) === false)
bgneal@312 143 return 0;
bgneal@312 144 }
bgneal@312 145 }
bgneal@312 146 }
bgneal@312 147
bgneal@312 148 return 1;
bgneal@312 149 },
bgneal@312 150
bgneal@312 151
bgneal@312 152 map : function(a, f) {
bgneal@312 153 var o = [];
bgneal@312 154
bgneal@312 155 tinymce.each(a, function(v) {
bgneal@312 156 o.push(f(v));
bgneal@312 157 });
bgneal@312 158
bgneal@312 159 return o;
bgneal@312 160 },
bgneal@312 161
bgneal@312 162 grep : function(a, f) {
bgneal@312 163 var o = [];
bgneal@312 164
bgneal@312 165 tinymce.each(a, function(v) {
bgneal@312 166 if (!f || f(v))
bgneal@312 167 o.push(v);
bgneal@312 168 });
bgneal@312 169
bgneal@312 170 return o;
bgneal@312 171 },
bgneal@312 172
bgneal@312 173 inArray : function(a, v) {
bgneal@312 174 var i, l;
bgneal@312 175
bgneal@312 176 if (a) {
bgneal@312 177 for (i = 0, l = a.length; i < l; i++) {
bgneal@312 178 if (a[i] === v)
bgneal@312 179 return i;
bgneal@312 180 }
bgneal@312 181 }
bgneal@312 182
bgneal@312 183 return -1;
bgneal@312 184 },
bgneal@312 185
bgneal@312 186 extend : function(o, e) {
bgneal@312 187 var i, l, a = arguments;
bgneal@312 188
bgneal@312 189 for (i = 1, l = a.length; i < l; i++) {
bgneal@312 190 e = a[i];
bgneal@312 191
bgneal@312 192 tinymce.each(e, function(v, n) {
bgneal@312 193 if (v !== undefined)
bgneal@312 194 o[n] = v;
bgneal@312 195 });
bgneal@312 196 }
bgneal@312 197
bgneal@312 198 return o;
bgneal@312 199 },
bgneal@312 200
bgneal@312 201
bgneal@312 202 trim : function(s) {
bgneal@312 203 return (s ? '' + s : '').replace(whiteSpaceRe, '');
bgneal@312 204 },
bgneal@312 205
bgneal@442 206 create : function(s, p, root) {
bgneal@312 207 var t = this, sp, ns, cn, scn, c, de = 0;
bgneal@312 208
bgneal@312 209 // Parse : <prefix> <class>:<super class>
bgneal@312 210 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
bgneal@312 211 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
bgneal@312 212
bgneal@312 213 // Create namespace for new class
bgneal@442 214 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
bgneal@312 215
bgneal@312 216 // Class already exists
bgneal@312 217 if (ns[cn])
bgneal@312 218 return;
bgneal@312 219
bgneal@312 220 // Make pure static class
bgneal@312 221 if (s[2] == 'static') {
bgneal@312 222 ns[cn] = p;
bgneal@312 223
bgneal@312 224 if (this.onCreate)
bgneal@312 225 this.onCreate(s[2], s[3], ns[cn]);
bgneal@312 226
bgneal@312 227 return;
bgneal@312 228 }
bgneal@312 229
bgneal@312 230 // Create default constructor
bgneal@312 231 if (!p[cn]) {
bgneal@312 232 p[cn] = function() {};
bgneal@312 233 de = 1;
bgneal@312 234 }
bgneal@312 235
bgneal@312 236 // Add constructor and methods
bgneal@312 237 ns[cn] = p[cn];
bgneal@312 238 t.extend(ns[cn].prototype, p);
bgneal@312 239
bgneal@312 240 // Extend
bgneal@312 241 if (s[5]) {
bgneal@312 242 sp = t.resolve(s[5]).prototype;
bgneal@312 243 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
bgneal@312 244
bgneal@312 245 // Extend constructor
bgneal@312 246 c = ns[cn];
bgneal@312 247 if (de) {
bgneal@312 248 // Add passthrough constructor
bgneal@312 249 ns[cn] = function() {
bgneal@312 250 return sp[scn].apply(this, arguments);
bgneal@312 251 };
bgneal@312 252 } else {
bgneal@312 253 // Add inherit constructor
bgneal@312 254 ns[cn] = function() {
bgneal@312 255 this.parent = sp[scn];
bgneal@312 256 return c.apply(this, arguments);
bgneal@312 257 };
bgneal@312 258 }
bgneal@312 259 ns[cn].prototype[cn] = ns[cn];
bgneal@312 260
bgneal@312 261 // Add super methods
bgneal@312 262 t.each(sp, function(f, n) {
bgneal@312 263 ns[cn].prototype[n] = sp[n];
bgneal@312 264 });
bgneal@312 265
bgneal@312 266 // Add overridden methods
bgneal@312 267 t.each(p, function(f, n) {
bgneal@312 268 // Extend methods if needed
bgneal@312 269 if (sp[n]) {
bgneal@312 270 ns[cn].prototype[n] = function() {
bgneal@312 271 this.parent = sp[n];
bgneal@312 272 return f.apply(this, arguments);
bgneal@312 273 };
bgneal@312 274 } else {
bgneal@312 275 if (n != cn)
bgneal@312 276 ns[cn].prototype[n] = f;
bgneal@312 277 }
bgneal@312 278 });
bgneal@312 279 }
bgneal@312 280
bgneal@312 281 // Add static methods
bgneal@312 282 t.each(p['static'], function(f, n) {
bgneal@312 283 ns[cn][n] = f;
bgneal@312 284 });
bgneal@312 285
bgneal@312 286 if (this.onCreate)
bgneal@312 287 this.onCreate(s[2], s[3], ns[cn].prototype);
bgneal@312 288 },
bgneal@312 289
bgneal@312 290 walk : function(o, f, n, s) {
bgneal@312 291 s = s || this;
bgneal@312 292
bgneal@312 293 if (o) {
bgneal@312 294 if (n)
bgneal@312 295 o = o[n];
bgneal@312 296
bgneal@312 297 tinymce.each(o, function(o, i) {
bgneal@312 298 if (f.call(s, o, i, n) === false)
bgneal@312 299 return false;
bgneal@312 300
bgneal@312 301 tinymce.walk(o, f, n, s);
bgneal@312 302 });
bgneal@312 303 }
bgneal@312 304 },
bgneal@312 305
bgneal@312 306 createNS : function(n, o) {
bgneal@312 307 var i, v;
bgneal@312 308
bgneal@312 309 o = o || win;
bgneal@312 310
bgneal@312 311 n = n.split('.');
bgneal@312 312 for (i=0; i<n.length; i++) {
bgneal@312 313 v = n[i];
bgneal@312 314
bgneal@312 315 if (!o[v])
bgneal@312 316 o[v] = {};
bgneal@312 317
bgneal@312 318 o = o[v];
bgneal@312 319 }
bgneal@312 320
bgneal@312 321 return o;
bgneal@312 322 },
bgneal@312 323
bgneal@312 324 resolve : function(n, o) {
bgneal@312 325 var i, l;
bgneal@312 326
bgneal@312 327 o = o || win;
bgneal@312 328
bgneal@312 329 n = n.split('.');
bgneal@312 330 for (i = 0, l = n.length; i < l; i++) {
bgneal@312 331 o = o[n[i]];
bgneal@312 332
bgneal@312 333 if (!o)
bgneal@312 334 break;
bgneal@312 335 }
bgneal@312 336
bgneal@312 337 return o;
bgneal@312 338 },
bgneal@312 339
bgneal@312 340 addUnload : function(f, s) {
bgneal@312 341 var t = this;
bgneal@312 342
bgneal@312 343 f = {func : f, scope : s || this};
bgneal@312 344
bgneal@312 345 if (!t.unloads) {
bgneal@312 346 function unload() {
bgneal@312 347 var li = t.unloads, o, n;
bgneal@312 348
bgneal@312 349 if (li) {
bgneal@312 350 // Call unload handlers
bgneal@312 351 for (n in li) {
bgneal@312 352 o = li[n];
bgneal@312 353
bgneal@312 354 if (o && o.func)
bgneal@312 355 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
bgneal@312 356 }
bgneal@312 357
bgneal@312 358 // Detach unload function
bgneal@312 359 if (win.detachEvent) {
bgneal@312 360 win.detachEvent('onbeforeunload', fakeUnload);
bgneal@312 361 win.detachEvent('onunload', unload);
bgneal@312 362 } else if (win.removeEventListener)
bgneal@312 363 win.removeEventListener('unload', unload, false);
bgneal@312 364
bgneal@312 365 // Destroy references
bgneal@312 366 t.unloads = o = li = w = unload = 0;
bgneal@312 367
bgneal@312 368 // Run garbarge collector on IE
bgneal@312 369 if (win.CollectGarbage)
bgneal@312 370 CollectGarbage();
bgneal@312 371 }
bgneal@312 372 };
bgneal@312 373
bgneal@312 374 function fakeUnload() {
bgneal@312 375 var d = document;
bgneal@312 376
bgneal@312 377 // Is there things still loading, then do some magic
bgneal@312 378 if (d.readyState == 'interactive') {
bgneal@312 379 function stop() {
bgneal@312 380 // Prevent memory leak
bgneal@312 381 d.detachEvent('onstop', stop);
bgneal@312 382
bgneal@312 383 // Call unload handler
bgneal@312 384 if (unload)
bgneal@312 385 unload();
bgneal@312 386
bgneal@312 387 d = 0;
bgneal@312 388 };
bgneal@312 389
bgneal@312 390 // Fire unload when the currently loading page is stopped
bgneal@312 391 if (d)
bgneal@312 392 d.attachEvent('onstop', stop);
bgneal@312 393
bgneal@312 394 // Remove onstop listener after a while to prevent the unload function
bgneal@312 395 // to execute if the user presses cancel in an onbeforeunload
bgneal@312 396 // confirm dialog and then presses the browser stop button
bgneal@312 397 win.setTimeout(function() {
bgneal@312 398 if (d)
bgneal@312 399 d.detachEvent('onstop', stop);
bgneal@312 400 }, 0);
bgneal@312 401 }
bgneal@312 402 };
bgneal@312 403
bgneal@312 404 // Attach unload handler
bgneal@312 405 if (win.attachEvent) {
bgneal@312 406 win.attachEvent('onunload', unload);
bgneal@312 407 win.attachEvent('onbeforeunload', fakeUnload);
bgneal@312 408 } else if (win.addEventListener)
bgneal@312 409 win.addEventListener('unload', unload, false);
bgneal@312 410
bgneal@312 411 // Setup initial unload handler array
bgneal@312 412 t.unloads = [f];
bgneal@312 413 } else
bgneal@312 414 t.unloads.push(f);
bgneal@312 415
bgneal@312 416 return f;
bgneal@312 417 },
bgneal@312 418
bgneal@312 419 removeUnload : function(f) {
bgneal@312 420 var u = this.unloads, r = null;
bgneal@312 421
bgneal@312 422 tinymce.each(u, function(o, i) {
bgneal@312 423 if (o && o.func == f) {
bgneal@312 424 u.splice(i, 1);
bgneal@312 425 r = f;
bgneal@312 426 return false;
bgneal@312 427 }
bgneal@312 428 });
bgneal@312 429
bgneal@312 430 return r;
bgneal@312 431 },
bgneal@312 432
bgneal@312 433 explode : function(s, d) {
bgneal@312 434 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
bgneal@312 435 },
bgneal@312 436
bgneal@312 437 _addVer : function(u) {
bgneal@312 438 var v;
bgneal@312 439
bgneal@312 440 if (!this.query)
bgneal@312 441 return u;
bgneal@312 442
bgneal@312 443 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
bgneal@312 444
bgneal@312 445 if (u.indexOf('#') == -1)
bgneal@312 446 return u + v;
bgneal@312 447
bgneal@312 448 return u.replace('#', v + '#');
bgneal@442 449 },
bgneal@442 450
bgneal@442 451 // Fix function for IE 9 where regexps isn't working correctly
bgneal@442 452 // Todo: remove me once MS fixes the bug
bgneal@442 453 _replace : function(find, replace, str) {
bgneal@442 454 // On IE9 we have to fake $x replacement
bgneal@442 455 if (isRegExpBroken) {
bgneal@442 456 return str.replace(find, function() {
bgneal@442 457 var val = replace, args = arguments, i;
bgneal@442 458
bgneal@442 459 for (i = 0; i < args.length - 2; i++) {
bgneal@442 460 if (args[i] === undefined) {
bgneal@442 461 val = val.replace(new RegExp('\\$' + i, 'g'), '');
bgneal@442 462 } else {
bgneal@442 463 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
bgneal@442 464 }
bgneal@442 465 }
bgneal@442 466
bgneal@442 467 return val;
bgneal@442 468 });
bgneal@442 469 }
bgneal@442 470
bgneal@442 471 return str.replace(find, replace);
bgneal@312 472 }
bgneal@312 473
bgneal@312 474 };
bgneal@312 475
bgneal@312 476 // Initialize the API
bgneal@312 477 tinymce._init();
bgneal@312 478
bgneal@312 479 // Expose tinymce namespace to the global namespace (window)
bgneal@312 480 win.tinymce = win.tinyMCE = tinymce;
bgneal@442 481
bgneal@442 482 // Describe the different namespaces
bgneal@442 483
bgneal@442 484 })(window);
bgneal@312 485
bgneal@312 486
bgneal@312 487 tinymce.create('tinymce.util.Dispatcher', {
bgneal@312 488 scope : null,
bgneal@312 489 listeners : null,
bgneal@312 490
bgneal@312 491 Dispatcher : function(s) {
bgneal@312 492 this.scope = s || this;
bgneal@312 493 this.listeners = [];
bgneal@312 494 },
bgneal@312 495
bgneal@312 496 add : function(cb, s) {
bgneal@312 497 this.listeners.push({cb : cb, scope : s || this.scope});
bgneal@312 498
bgneal@312 499 return cb;
bgneal@312 500 },
bgneal@312 501
bgneal@312 502 addToTop : function(cb, s) {
bgneal@312 503 this.listeners.unshift({cb : cb, scope : s || this.scope});
bgneal@312 504
bgneal@312 505 return cb;
bgneal@312 506 },
bgneal@312 507
bgneal@312 508 remove : function(cb) {
bgneal@312 509 var l = this.listeners, o = null;
bgneal@312 510
bgneal@312 511 tinymce.each(l, function(c, i) {
bgneal@312 512 if (cb == c.cb) {
bgneal@312 513 o = cb;
bgneal@312 514 l.splice(i, 1);
bgneal@312 515 return false;
bgneal@312 516 }
bgneal@312 517 });
bgneal@312 518
bgneal@312 519 return o;
bgneal@312 520 },
bgneal@312 521
bgneal@312 522 dispatch : function() {
bgneal@312 523 var s, a = arguments, i, li = this.listeners, c;
bgneal@312 524
bgneal@312 525 // Needs to be a real loop since the listener count might change while looping
bgneal@312 526 // And this is also more efficient
bgneal@312 527 for (i = 0; i<li.length; i++) {
bgneal@312 528 c = li[i];
bgneal@312 529 s = c.cb.apply(c.scope, a);
bgneal@312 530
bgneal@312 531 if (s === false)
bgneal@312 532 break;
bgneal@312 533 }
bgneal@312 534
bgneal@312 535 return s;
bgneal@312 536 }
bgneal@312 537
bgneal@312 538 });
bgneal@312 539
bgneal@312 540 (function() {
bgneal@312 541 var each = tinymce.each;
bgneal@312 542
bgneal@312 543 tinymce.create('tinymce.util.URI', {
bgneal@312 544 URI : function(u, s) {
bgneal@312 545 var t = this, o, a, b;
bgneal@312 546
bgneal@312 547 // Trim whitespace
bgneal@312 548 u = tinymce.trim(u);
bgneal@312 549
bgneal@312 550 // Default settings
bgneal@312 551 s = t.settings = s || {};
bgneal@312 552
bgneal@312 553 // Strange app protocol or local anchor
bgneal@312 554 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
bgneal@312 555 t.source = u;
bgneal@312 556 return;
bgneal@312 557 }
bgneal@312 558
bgneal@312 559 // Absolute path with no host, fake host and protocol
bgneal@312 560 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
bgneal@312 561 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
bgneal@312 562
bgneal@312 563 // Relative path http:// or protocol relative //path
bgneal@312 564 if (!/^\w*:?\/\//.test(u))
bgneal@312 565 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
bgneal@312 566
bgneal@312 567 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
bgneal@312 568 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
bgneal@312 569 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
bgneal@312 570 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
bgneal@312 571 var s = u[i];
bgneal@312 572
bgneal@312 573 // Zope 3 workaround, they use @@something
bgneal@312 574 if (s)
bgneal@312 575 s = s.replace(/\(mce_at\)/g, '@@');
bgneal@312 576
bgneal@312 577 t[v] = s;
bgneal@312 578 });
bgneal@312 579
bgneal@312 580 if (b = s.base_uri) {
bgneal@312 581 if (!t.protocol)
bgneal@312 582 t.protocol = b.protocol;
bgneal@312 583
bgneal@312 584 if (!t.userInfo)
bgneal@312 585 t.userInfo = b.userInfo;
bgneal@312 586
bgneal@312 587 if (!t.port && t.host == 'mce_host')
bgneal@312 588 t.port = b.port;
bgneal@312 589
bgneal@312 590 if (!t.host || t.host == 'mce_host')
bgneal@312 591 t.host = b.host;
bgneal@312 592
bgneal@312 593 t.source = '';
bgneal@312 594 }
bgneal@312 595
bgneal@312 596 //t.path = t.path || '/';
bgneal@312 597 },
bgneal@312 598
bgneal@312 599 setPath : function(p) {
bgneal@312 600 var t = this;
bgneal@312 601
bgneal@312 602 p = /^(.*?)\/?(\w+)?$/.exec(p);
bgneal@312 603
bgneal@312 604 // Update path parts
bgneal@312 605 t.path = p[0];
bgneal@312 606 t.directory = p[1];
bgneal@312 607 t.file = p[2];
bgneal@312 608
bgneal@312 609 // Rebuild source
bgneal@312 610 t.source = '';
bgneal@312 611 t.getURI();
bgneal@312 612 },
bgneal@312 613
bgneal@312 614 toRelative : function(u) {
bgneal@312 615 var t = this, o;
bgneal@312 616
bgneal@312 617 if (u === "./")
bgneal@312 618 return u;
bgneal@312 619
bgneal@312 620 u = new tinymce.util.URI(u, {base_uri : t});
bgneal@312 621
bgneal@312 622 // Not on same domain/port or protocol
bgneal@312 623 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
bgneal@312 624 return u.getURI();
bgneal@312 625
bgneal@312 626 o = t.toRelPath(t.path, u.path);
bgneal@312 627
bgneal@312 628 // Add query
bgneal@312 629 if (u.query)
bgneal@312 630 o += '?' + u.query;
bgneal@312 631
bgneal@312 632 // Add anchor
bgneal@312 633 if (u.anchor)
bgneal@312 634 o += '#' + u.anchor;
bgneal@312 635
bgneal@312 636 return o;
bgneal@312 637 },
bgneal@312 638
bgneal@312 639 toAbsolute : function(u, nh) {
bgneal@312 640 var u = new tinymce.util.URI(u, {base_uri : this});
bgneal@312 641
bgneal@312 642 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
bgneal@312 643 },
bgneal@312 644
bgneal@312 645 toRelPath : function(base, path) {
bgneal@312 646 var items, bp = 0, out = '', i, l;
bgneal@312 647
bgneal@312 648 // Split the paths
bgneal@312 649 base = base.substring(0, base.lastIndexOf('/'));
bgneal@312 650 base = base.split('/');
bgneal@312 651 items = path.split('/');
bgneal@312 652
bgneal@312 653 if (base.length >= items.length) {
bgneal@312 654 for (i = 0, l = base.length; i < l; i++) {
bgneal@312 655 if (i >= items.length || base[i] != items[i]) {
bgneal@312 656 bp = i + 1;
bgneal@312 657 break;
bgneal@312 658 }
bgneal@312 659 }
bgneal@312 660 }
bgneal@312 661
bgneal@312 662 if (base.length < items.length) {
bgneal@312 663 for (i = 0, l = items.length; i < l; i++) {
bgneal@312 664 if (i >= base.length || base[i] != items[i]) {
bgneal@312 665 bp = i + 1;
bgneal@312 666 break;
bgneal@312 667 }
bgneal@312 668 }
bgneal@312 669 }
bgneal@312 670
bgneal@312 671 if (bp == 1)
bgneal@312 672 return path;
bgneal@312 673
bgneal@312 674 for (i = 0, l = base.length - (bp - 1); i < l; i++)
bgneal@312 675 out += "../";
bgneal@312 676
bgneal@312 677 for (i = bp - 1, l = items.length; i < l; i++) {
bgneal@312 678 if (i != bp - 1)
bgneal@312 679 out += "/" + items[i];
bgneal@312 680 else
bgneal@312 681 out += items[i];
bgneal@312 682 }
bgneal@312 683
bgneal@312 684 return out;
bgneal@312 685 },
bgneal@312 686
bgneal@312 687 toAbsPath : function(base, path) {
bgneal@312 688 var i, nb = 0, o = [], tr, outPath;
bgneal@312 689
bgneal@312 690 // Split paths
bgneal@312 691 tr = /\/$/.test(path) ? '/' : '';
bgneal@312 692 base = base.split('/');
bgneal@312 693 path = path.split('/');
bgneal@312 694
bgneal@312 695 // Remove empty chunks
bgneal@312 696 each(base, function(k) {
bgneal@312 697 if (k)
bgneal@312 698 o.push(k);
bgneal@312 699 });
bgneal@312 700
bgneal@312 701 base = o;
bgneal@312 702
bgneal@312 703 // Merge relURLParts chunks
bgneal@312 704 for (i = path.length - 1, o = []; i >= 0; i--) {
bgneal@312 705 // Ignore empty or .
bgneal@312 706 if (path[i].length == 0 || path[i] == ".")
bgneal@312 707 continue;
bgneal@312 708
bgneal@312 709 // Is parent
bgneal@312 710 if (path[i] == '..') {
bgneal@312 711 nb++;
bgneal@312 712 continue;
bgneal@312 713 }
bgneal@312 714
bgneal@312 715 // Move up
bgneal@312 716 if (nb > 0) {
bgneal@312 717 nb--;
bgneal@312 718 continue;
bgneal@312 719 }
bgneal@312 720
bgneal@312 721 o.push(path[i]);
bgneal@312 722 }
bgneal@312 723
bgneal@312 724 i = base.length - nb;
bgneal@312 725
bgneal@312 726 // If /a/b/c or /
bgneal@312 727 if (i <= 0)
bgneal@312 728 outPath = o.reverse().join('/');
bgneal@312 729 else
bgneal@312 730 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
bgneal@312 731
bgneal@312 732 // Add front / if it's needed
bgneal@312 733 if (outPath.indexOf('/') !== 0)
bgneal@312 734 outPath = '/' + outPath;
bgneal@312 735
bgneal@312 736 // Add traling / if it's needed
bgneal@312 737 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
bgneal@312 738 outPath += tr;
bgneal@312 739
bgneal@312 740 return outPath;
bgneal@312 741 },
bgneal@312 742
bgneal@312 743 getURI : function(nh) {
bgneal@312 744 var s, t = this;
bgneal@312 745
bgneal@312 746 // Rebuild source
bgneal@312 747 if (!t.source || nh) {
bgneal@312 748 s = '';
bgneal@312 749
bgneal@312 750 if (!nh) {
bgneal@312 751 if (t.protocol)
bgneal@312 752 s += t.protocol + '://';
bgneal@312 753
bgneal@312 754 if (t.userInfo)
bgneal@312 755 s += t.userInfo + '@';
bgneal@312 756
bgneal@312 757 if (t.host)
bgneal@312 758 s += t.host;
bgneal@312 759
bgneal@312 760 if (t.port)
bgneal@312 761 s += ':' + t.port;
bgneal@312 762 }
bgneal@312 763
bgneal@312 764 if (t.path)
bgneal@312 765 s += t.path;
bgneal@312 766
bgneal@312 767 if (t.query)
bgneal@312 768 s += '?' + t.query;
bgneal@312 769
bgneal@312 770 if (t.anchor)
bgneal@312 771 s += '#' + t.anchor;
bgneal@312 772
bgneal@312 773 t.source = s;
bgneal@312 774 }
bgneal@312 775
bgneal@312 776 return t.source;
bgneal@312 777 }
bgneal@312 778 });
bgneal@312 779 })();
bgneal@312 780
bgneal@312 781 (function() {
bgneal@312 782 var each = tinymce.each;
bgneal@312 783
bgneal@312 784 tinymce.create('static tinymce.util.Cookie', {
bgneal@312 785 getHash : function(n) {
bgneal@312 786 var v = this.get(n), h;
bgneal@312 787
bgneal@312 788 if (v) {
bgneal@312 789 each(v.split('&'), function(v) {
bgneal@312 790 v = v.split('=');
bgneal@312 791 h = h || {};
bgneal@312 792 h[unescape(v[0])] = unescape(v[1]);
bgneal@312 793 });
bgneal@312 794 }
bgneal@312 795
bgneal@312 796 return h;
bgneal@312 797 },
bgneal@312 798
bgneal@312 799 setHash : function(n, v, e, p, d, s) {
bgneal@312 800 var o = '';
bgneal@312 801
bgneal@312 802 each(v, function(v, k) {
bgneal@312 803 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
bgneal@312 804 });
bgneal@312 805
bgneal@312 806 this.set(n, o, e, p, d, s);
bgneal@312 807 },
bgneal@312 808
bgneal@312 809 get : function(n) {
bgneal@312 810 var c = document.cookie, e, p = n + "=", b;
bgneal@312 811
bgneal@312 812 // Strict mode
bgneal@312 813 if (!c)
bgneal@312 814 return;
bgneal@312 815
bgneal@312 816 b = c.indexOf("; " + p);
bgneal@312 817
bgneal@312 818 if (b == -1) {
bgneal@312 819 b = c.indexOf(p);
bgneal@312 820
bgneal@312 821 if (b != 0)
bgneal@312 822 return null;
bgneal@312 823 } else
bgneal@312 824 b += 2;
bgneal@312 825
bgneal@312 826 e = c.indexOf(";", b);
bgneal@312 827
bgneal@312 828 if (e == -1)
bgneal@312 829 e = c.length;
bgneal@312 830
bgneal@312 831 return unescape(c.substring(b + p.length, e));
bgneal@312 832 },
bgneal@312 833
bgneal@312 834 set : function(n, v, e, p, d, s) {
bgneal@312 835 document.cookie = n + "=" + escape(v) +
bgneal@312 836 ((e) ? "; expires=" + e.toGMTString() : "") +
bgneal@312 837 ((p) ? "; path=" + escape(p) : "") +
bgneal@312 838 ((d) ? "; domain=" + d : "") +
bgneal@312 839 ((s) ? "; secure" : "");
bgneal@312 840 },
bgneal@312 841
bgneal@312 842 remove : function(n, p) {
bgneal@312 843 var d = new Date();
bgneal@312 844
bgneal@312 845 d.setTime(d.getTime() - 1000);
bgneal@312 846
bgneal@312 847 this.set(n, '', d, p, d);
bgneal@312 848 }
bgneal@312 849 });
bgneal@312 850 })();
bgneal@312 851
bgneal@442 852 (function() {
bgneal@442 853 function serialize(o, quote) {
bgneal@442 854 var i, v, t;
bgneal@442 855
bgneal@442 856 quote = quote || '"';
bgneal@312 857
bgneal@312 858 if (o == null)
bgneal@312 859 return 'null';
bgneal@312 860
bgneal@312 861 t = typeof o;
bgneal@312 862
bgneal@312 863 if (t == 'string') {
bgneal@312 864 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
bgneal@312 865
bgneal@442 866 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
bgneal@442 867 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
bgneal@442 868 if (quote === '"' && a === "'")
bgneal@442 869 return a;
bgneal@442 870
bgneal@312 871 i = v.indexOf(b);
bgneal@312 872
bgneal@312 873 if (i + 1)
bgneal@312 874 return '\\' + v.charAt(i + 1);
bgneal@312 875
bgneal@312 876 a = b.charCodeAt().toString(16);
bgneal@312 877
bgneal@312 878 return '\\u' + '0000'.substring(a.length) + a;
bgneal@442 879 }) + quote;
bgneal@312 880 }
bgneal@312 881
bgneal@312 882 if (t == 'object') {
bgneal@312 883 if (o.hasOwnProperty && o instanceof Array) {
bgneal@312 884 for (i=0, v = '['; i<o.length; i++)
bgneal@442 885 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
bgneal@312 886
bgneal@312 887 return v + ']';
bgneal@312 888 }
bgneal@312 889
bgneal@312 890 v = '{';
bgneal@312 891
bgneal@312 892 for (i in o)
bgneal@442 893 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
bgneal@312 894
bgneal@312 895 return v + '}';
bgneal@312 896 }
bgneal@312 897
bgneal@312 898 return '' + o;
bgneal@442 899 };
bgneal@442 900
bgneal@442 901 tinymce.util.JSON = {
bgneal@442 902 serialize: serialize,
bgneal@442 903
bgneal@442 904 parse: function(s) {
bgneal@442 905 try {
bgneal@442 906 return eval('(' + s + ')');
bgneal@442 907 } catch (ex) {
bgneal@442 908 // Ignore
bgneal@442 909 }
bgneal@442 910 }
bgneal@442 911
bgneal@442 912 };
bgneal@442 913 })();
bgneal@312 914 tinymce.create('static tinymce.util.XHR', {
bgneal@312 915 send : function(o) {
bgneal@312 916 var x, t, w = window, c = 0;
bgneal@312 917
bgneal@312 918 // Default settings
bgneal@312 919 o.scope = o.scope || this;
bgneal@312 920 o.success_scope = o.success_scope || o.scope;
bgneal@312 921 o.error_scope = o.error_scope || o.scope;
bgneal@312 922 o.async = o.async === false ? false : true;
bgneal@312 923 o.data = o.data || '';
bgneal@312 924
bgneal@312 925 function get(s) {
bgneal@312 926 x = 0;
bgneal@312 927
bgneal@312 928 try {
bgneal@312 929 x = new ActiveXObject(s);
bgneal@312 930 } catch (ex) {
bgneal@312 931 }
bgneal@312 932
bgneal@312 933 return x;
bgneal@312 934 };
bgneal@312 935
bgneal@312 936 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
bgneal@312 937
bgneal@312 938 if (x) {
bgneal@312 939 if (x.overrideMimeType)
bgneal@312 940 x.overrideMimeType(o.content_type);
bgneal@312 941
bgneal@312 942 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
bgneal@312 943
bgneal@312 944 if (o.content_type)
bgneal@312 945 x.setRequestHeader('Content-Type', o.content_type);
bgneal@312 946
bgneal@312 947 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bgneal@312 948
bgneal@312 949 x.send(o.data);
bgneal@312 950
bgneal@312 951 function ready() {
bgneal@312 952 if (!o.async || x.readyState == 4 || c++ > 10000) {
bgneal@312 953 if (o.success && c < 10000 && x.status == 200)
bgneal@312 954 o.success.call(o.success_scope, '' + x.responseText, x, o);
bgneal@312 955 else if (o.error)
bgneal@312 956 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
bgneal@312 957
bgneal@312 958 x = null;
bgneal@312 959 } else
bgneal@312 960 w.setTimeout(ready, 10);
bgneal@312 961 };
bgneal@312 962
bgneal@312 963 // Syncronous request
bgneal@312 964 if (!o.async)
bgneal@312 965 return ready();
bgneal@312 966
bgneal@312 967 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
bgneal@312 968 t = w.setTimeout(ready, 10);
bgneal@312 969 }
bgneal@312 970 }
bgneal@312 971 });
bgneal@312 972
bgneal@312 973 (function() {
bgneal@312 974 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
bgneal@312 975
bgneal@312 976 tinymce.create('tinymce.util.JSONRequest', {
bgneal@312 977 JSONRequest : function(s) {
bgneal@312 978 this.settings = extend({
bgneal@312 979 }, s);
bgneal@312 980 this.count = 0;
bgneal@312 981 },
bgneal@312 982
bgneal@312 983 send : function(o) {
bgneal@312 984 var ecb = o.error, scb = o.success;
bgneal@312 985
bgneal@312 986 o = extend(this.settings, o);
bgneal@312 987
bgneal@312 988 o.success = function(c, x) {
bgneal@312 989 c = JSON.parse(c);
bgneal@312 990
bgneal@312 991 if (typeof(c) == 'undefined') {
bgneal@312 992 c = {
bgneal@312 993 error : 'JSON Parse error.'
bgneal@312 994 };
bgneal@312 995 }
bgneal@312 996
bgneal@312 997 if (c.error)
bgneal@312 998 ecb.call(o.error_scope || o.scope, c.error, x);
bgneal@312 999 else
bgneal@312 1000 scb.call(o.success_scope || o.scope, c.result);
bgneal@312 1001 };
bgneal@312 1002
bgneal@312 1003 o.error = function(ty, x) {
bgneal@442 1004 if (ecb)
bgneal@442 1005 ecb.call(o.error_scope || o.scope, ty, x);
bgneal@312 1006 };
bgneal@312 1007
bgneal@312 1008 o.data = JSON.serialize({
bgneal@312 1009 id : o.id || 'c' + (this.count++),
bgneal@312 1010 method : o.method,
bgneal@312 1011 params : o.params
bgneal@312 1012 });
bgneal@312 1013
bgneal@312 1014 // JSON content type for Ruby on rails. Bug: #1883287
bgneal@312 1015 o.content_type = 'application/json';
bgneal@312 1016
bgneal@312 1017 XHR.send(o);
bgneal@312 1018 },
bgneal@312 1019
bgneal@312 1020 'static' : {
bgneal@312 1021 sendRPC : function(o) {
bgneal@312 1022 return new tinymce.util.JSONRequest().send(o);
bgneal@312 1023 }
bgneal@312 1024 }
bgneal@312 1025 });
bgneal@312 1026 }());
bgneal@312 1027 (function(tinymce) {
bgneal@442 1028 var namedEntities, baseEntities, reverseEntities,
bgneal@442 1029 attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
bgneal@442 1030 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
bgneal@442 1031 rawCharsRegExp = /[<>&\"\']/g,
bgneal@442 1032 entityRegExp = /&(#)?([\w]+);/g,
bgneal@442 1033 asciiMap = {
bgneal@442 1034 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
bgneal@442 1035 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
bgneal@442 1036 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
bgneal@442 1037 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
bgneal@442 1038 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
bgneal@442 1039 };
bgneal@442 1040
bgneal@442 1041 // Raw entities
bgneal@442 1042 baseEntities = {
bgneal@442 1043 '"' : '&quot;',
bgneal@442 1044 "'" : '&#39;',
bgneal@442 1045 '<' : '&lt;',
bgneal@442 1046 '>' : '&gt;',
bgneal@442 1047 '&' : '&amp;'
bgneal@442 1048 };
bgneal@442 1049
bgneal@442 1050 // Reverse lookup table for raw entities
bgneal@442 1051 reverseEntities = {
bgneal@442 1052 '&lt;' : '<',
bgneal@442 1053 '&gt;' : '>',
bgneal@442 1054 '&amp;' : '&',
bgneal@442 1055 '&quot;' : '"',
bgneal@442 1056 '&apos;' : "'"
bgneal@442 1057 };
bgneal@442 1058
bgneal@442 1059 // Decodes text by using the browser
bgneal@442 1060 function nativeDecode(text) {
bgneal@442 1061 var elm;
bgneal@442 1062
bgneal@442 1063 elm = document.createElement("div");
bgneal@442 1064 elm.innerHTML = text;
bgneal@442 1065
bgneal@442 1066 return elm.textContent || elm.innerText || text;
bgneal@442 1067 };
bgneal@442 1068
bgneal@442 1069 // Build a two way lookup table for the entities
bgneal@442 1070 function buildEntitiesLookup(items, radix) {
bgneal@442 1071 var i, chr, entity, lookup = {};
bgneal@442 1072
bgneal@442 1073 if (items) {
bgneal@442 1074 items = items.split(',');
bgneal@442 1075 radix = radix || 10;
bgneal@442 1076
bgneal@442 1077 // Build entities lookup table
bgneal@442 1078 for (i = 0; i < items.length; i += 2) {
bgneal@442 1079 chr = String.fromCharCode(parseInt(items[i], radix));
bgneal@442 1080
bgneal@442 1081 // Only add non base entities
bgneal@442 1082 if (!baseEntities[chr]) {
bgneal@442 1083 entity = '&' + items[i + 1] + ';';
bgneal@442 1084 lookup[chr] = entity;
bgneal@442 1085 lookup[entity] = chr;
bgneal@442 1086 }
bgneal@442 1087 }
bgneal@442 1088
bgneal@442 1089 return lookup;
bgneal@442 1090 }
bgneal@442 1091 };
bgneal@442 1092
bgneal@442 1093 // Unpack entities lookup where the numbers are in radix 32 to reduce the size
bgneal@442 1094 namedEntities = buildEntitiesLookup(
bgneal@442 1095 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
bgneal@442 1096 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
bgneal@442 1097 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
bgneal@442 1098 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
bgneal@442 1099 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
bgneal@442 1100 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
bgneal@442 1101 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
bgneal@442 1102 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
bgneal@442 1103 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
bgneal@442 1104 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
bgneal@442 1105 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
bgneal@442 1106 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
bgneal@442 1107 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
bgneal@442 1108 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
bgneal@442 1109 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
bgneal@442 1110 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
bgneal@442 1111 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
bgneal@442 1112 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
bgneal@442 1113 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
bgneal@442 1114 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
bgneal@442 1115 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
bgneal@442 1116 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
bgneal@442 1117 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
bgneal@442 1118 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
bgneal@442 1119 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
bgneal@442 1120 , 32);
bgneal@442 1121
bgneal@442 1122 tinymce.html = tinymce.html || {};
bgneal@442 1123
bgneal@442 1124 tinymce.html.Entities = {
bgneal@442 1125 encodeRaw : function(text, attr) {
bgneal@442 1126 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@442 1127 return baseEntities[chr] || chr;
bgneal@442 1128 });
bgneal@442 1129 },
bgneal@442 1130
bgneal@442 1131 encodeAllRaw : function(text) {
bgneal@442 1132 return ('' + text).replace(rawCharsRegExp, function(chr) {
bgneal@442 1133 return baseEntities[chr] || chr;
bgneal@442 1134 });
bgneal@442 1135 },
bgneal@442 1136
bgneal@442 1137 encodeNumeric : function(text, attr) {
bgneal@442 1138 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@442 1139 // Multi byte sequence convert it to a single entity
bgneal@442 1140 if (chr.length > 1)
bgneal@442 1141 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
bgneal@442 1142
bgneal@442 1143 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
bgneal@442 1144 });
bgneal@442 1145 },
bgneal@442 1146
bgneal@442 1147 encodeNamed : function(text, attr, entities) {
bgneal@442 1148 entities = entities || namedEntities;
bgneal@442 1149
bgneal@442 1150 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@442 1151 return baseEntities[chr] || entities[chr] || chr;
bgneal@442 1152 });
bgneal@442 1153 },
bgneal@442 1154
bgneal@442 1155 getEncodeFunc : function(name, entities) {
bgneal@442 1156 var Entities = tinymce.html.Entities;
bgneal@442 1157
bgneal@442 1158 entities = buildEntitiesLookup(entities) || namedEntities;
bgneal@442 1159
bgneal@442 1160 function encodeNamedAndNumeric(text, attr) {
bgneal@442 1161 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
bgneal@442 1162 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
bgneal@442 1163 });
bgneal@442 1164 };
bgneal@442 1165
bgneal@442 1166 function encodeCustomNamed(text, attr) {
bgneal@442 1167 return Entities.encodeNamed(text, attr, entities);
bgneal@442 1168 };
bgneal@442 1169
bgneal@442 1170 // Replace + with , to be compatible with previous TinyMCE versions
bgneal@442 1171 name = tinymce.makeMap(name.replace(/\+/g, ','));
bgneal@442 1172
bgneal@442 1173 // Named and numeric encoder
bgneal@442 1174 if (name.named && name.numeric)
bgneal@442 1175 return encodeNamedAndNumeric;
bgneal@442 1176
bgneal@442 1177 // Named encoder
bgneal@442 1178 if (name.named) {
bgneal@442 1179 // Custom names
bgneal@442 1180 if (entities)
bgneal@442 1181 return encodeCustomNamed;
bgneal@442 1182
bgneal@442 1183 return Entities.encodeNamed;
bgneal@442 1184 }
bgneal@442 1185
bgneal@442 1186 // Numeric
bgneal@442 1187 if (name.numeric)
bgneal@442 1188 return Entities.encodeNumeric;
bgneal@442 1189
bgneal@442 1190 // Raw encoder
bgneal@442 1191 return Entities.encodeRaw;
bgneal@442 1192 },
bgneal@442 1193
bgneal@442 1194 decode : function(text) {
bgneal@442 1195 return text.replace(entityRegExp, function(all, numeric, value) {
bgneal@442 1196 if (numeric) {
bgneal@442 1197 value = parseInt(value);
bgneal@442 1198
bgneal@442 1199 // Support upper UTF
bgneal@442 1200 if (value > 0xFFFF) {
bgneal@442 1201 value -= 0x10000;
bgneal@442 1202
bgneal@442 1203 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
bgneal@442 1204 } else
bgneal@442 1205 return asciiMap[value] || String.fromCharCode(value);
bgneal@442 1206 }
bgneal@442 1207
bgneal@442 1208 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
bgneal@442 1209 });
bgneal@442 1210 }
bgneal@442 1211 };
bgneal@442 1212 })(tinymce);
bgneal@442 1213
bgneal@442 1214 tinymce.html.Styles = function(settings, schema) {
bgneal@442 1215 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
bgneal@442 1216 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
bgneal@442 1217 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
bgneal@442 1218 trimRightRegExp = /\s+$/,
bgneal@442 1219 urlColorRegExp = /rgb/,
bgneal@442 1220 undef, i, encodingLookup = {}, encodingItems;
bgneal@442 1221
bgneal@442 1222 settings = settings || {};
bgneal@442 1223
bgneal@442 1224 encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
bgneal@442 1225 for (i = 0; i < encodingItems.length; i++) {
bgneal@442 1226 encodingLookup[encodingItems[i]] = '_' + i;
bgneal@442 1227 encodingLookup['_' + i] = encodingItems[i];
bgneal@442 1228 }
bgneal@442 1229
bgneal@442 1230 function toHex(match, r, g, b) {
bgneal@442 1231 function hex(val) {
bgneal@442 1232 val = parseInt(val).toString(16);
bgneal@442 1233
bgneal@442 1234 return val.length > 1 ? val : '0' + val; // 0 -> 00
bgneal@442 1235 };
bgneal@442 1236
bgneal@442 1237 return '#' + hex(r) + hex(g) + hex(b);
bgneal@442 1238 };
bgneal@442 1239
bgneal@442 1240 return {
bgneal@442 1241 toHex : function(color) {
bgneal@442 1242 return color.replace(rgbRegExp, toHex);
bgneal@442 1243 },
bgneal@442 1244
bgneal@442 1245 parse : function(css) {
bgneal@442 1246 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
bgneal@442 1247
bgneal@442 1248 function compress(prefix, suffix) {
bgneal@442 1249 var top, right, bottom, left;
bgneal@442 1250
bgneal@442 1251 // Get values and check it it needs compressing
bgneal@442 1252 top = styles[prefix + '-top' + suffix];
bgneal@442 1253 if (!top)
bgneal@442 1254 return;
bgneal@442 1255
bgneal@442 1256 right = styles[prefix + '-right' + suffix];
bgneal@442 1257 if (top != right)
bgneal@442 1258 return;
bgneal@442 1259
bgneal@442 1260 bottom = styles[prefix + '-bottom' + suffix];
bgneal@442 1261 if (right != bottom)
bgneal@442 1262 return;
bgneal@442 1263
bgneal@442 1264 left = styles[prefix + '-left' + suffix];
bgneal@442 1265 if (bottom != left)
bgneal@442 1266 return;
bgneal@442 1267
bgneal@442 1268 // Compress
bgneal@442 1269 styles[prefix + suffix] = left;
bgneal@442 1270 delete styles[prefix + '-top' + suffix];
bgneal@442 1271 delete styles[prefix + '-right' + suffix];
bgneal@442 1272 delete styles[prefix + '-bottom' + suffix];
bgneal@442 1273 delete styles[prefix + '-left' + suffix];
bgneal@442 1274 };
bgneal@442 1275
bgneal@442 1276 function canCompress(key) {
bgneal@442 1277 var value = styles[key], i;
bgneal@442 1278
bgneal@442 1279 if (!value || value.indexOf(' ') < 0)
bgneal@442 1280 return;
bgneal@442 1281
bgneal@442 1282 value = value.split(' ');
bgneal@442 1283 i = value.length;
bgneal@442 1284 while (i--) {
bgneal@442 1285 if (value[i] !== value[0])
bgneal@442 1286 return false;
bgneal@442 1287 }
bgneal@442 1288
bgneal@442 1289 styles[key] = value[0];
bgneal@442 1290
bgneal@442 1291 return true;
bgneal@442 1292 };
bgneal@442 1293
bgneal@442 1294 function compress2(target, a, b, c) {
bgneal@442 1295 if (!canCompress(a))
bgneal@442 1296 return;
bgneal@442 1297
bgneal@442 1298 if (!canCompress(b))
bgneal@442 1299 return;
bgneal@442 1300
bgneal@442 1301 if (!canCompress(c))
bgneal@442 1302 return;
bgneal@442 1303
bgneal@442 1304 // Compress
bgneal@442 1305 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
bgneal@442 1306 delete styles[a];
bgneal@442 1307 delete styles[b];
bgneal@442 1308 delete styles[c];
bgneal@442 1309 };
bgneal@442 1310
bgneal@442 1311 // Encodes the specified string by replacing all \" \' ; : with _<num>
bgneal@442 1312 function encode(str) {
bgneal@442 1313 isEncoded = true;
bgneal@442 1314
bgneal@442 1315 return encodingLookup[str];
bgneal@442 1316 };
bgneal@442 1317
bgneal@442 1318 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
bgneal@442 1319 // It will also decode the \" \' if keep_slashes is set to fale or omitted
bgneal@442 1320 function decode(str, keep_slashes) {
bgneal@442 1321 if (isEncoded) {
bgneal@442 1322 str = str.replace(/_[0-9]/g, function(str) {
bgneal@442 1323 return encodingLookup[str];
bgneal@442 1324 });
bgneal@442 1325 }
bgneal@442 1326
bgneal@442 1327 if (!keep_slashes)
bgneal@442 1328 str = str.replace(/\\([\'\";:])/g, "$1");
bgneal@442 1329
bgneal@442 1330 return str;
bgneal@442 1331 }
bgneal@442 1332
bgneal@442 1333 if (css) {
bgneal@442 1334 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
bgneal@442 1335 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
bgneal@442 1336 return str.replace(/[;:]/g, encode);
bgneal@442 1337 });
bgneal@442 1338
bgneal@442 1339 // Parse styles
bgneal@442 1340 while (matches = styleRegExp.exec(css)) {
bgneal@442 1341 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
bgneal@442 1342 value = matches[2].replace(trimRightRegExp, '');
bgneal@442 1343
bgneal@442 1344 if (name && value.length > 0) {
bgneal@442 1345 // Opera will produce 700 instead of bold in their style values
bgneal@442 1346 if (name === 'font-weight' && value === '700')
bgneal@442 1347 value = 'bold';
bgneal@442 1348 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
bgneal@442 1349 value = value.toLowerCase();
bgneal@442 1350
bgneal@442 1351 // Convert RGB colors to HEX
bgneal@442 1352 value = value.replace(rgbRegExp, toHex);
bgneal@442 1353
bgneal@442 1354 // Convert URLs and force them into url('value') format
bgneal@442 1355 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
bgneal@442 1356 str = str || str2;
bgneal@442 1357
bgneal@442 1358 if (str) {
bgneal@442 1359 str = decode(str);
bgneal@442 1360
bgneal@442 1361 // Force strings into single quote format
bgneal@442 1362 return "'" + str.replace(/\'/g, "\\'") + "'";
bgneal@442 1363 }
bgneal@442 1364
bgneal@442 1365 url = decode(url || url2 || url3);
bgneal@442 1366
bgneal@442 1367 // Convert the URL to relative/absolute depending on config
bgneal@442 1368 if (urlConverter)
bgneal@442 1369 url = urlConverter.call(urlConverterScope, url, 'style');
bgneal@442 1370
bgneal@442 1371 // Output new URL format
bgneal@442 1372 return "url('" + url.replace(/\'/g, "\\'") + "')";
bgneal@442 1373 });
bgneal@442 1374
bgneal@442 1375 styles[name] = isEncoded ? decode(value, true) : value;
bgneal@442 1376 }
bgneal@442 1377
bgneal@442 1378 styleRegExp.lastIndex = matches.index + matches[0].length;
bgneal@442 1379 }
bgneal@442 1380
bgneal@442 1381 // Compress the styles to reduce it's size for example IE will expand styles
bgneal@442 1382 compress("border", "");
bgneal@442 1383 compress("border", "-width");
bgneal@442 1384 compress("border", "-color");
bgneal@442 1385 compress("border", "-style");
bgneal@442 1386 compress("padding", "");
bgneal@442 1387 compress("margin", "");
bgneal@442 1388 compress2('border', 'border-width', 'border-style', 'border-color');
bgneal@442 1389
bgneal@442 1390 // Remove pointless border, IE produces these
bgneal@442 1391 if (styles.border === 'medium none')
bgneal@442 1392 delete styles.border;
bgneal@442 1393 }
bgneal@442 1394
bgneal@442 1395 return styles;
bgneal@442 1396 },
bgneal@442 1397
bgneal@442 1398 serialize : function(styles, element_name) {
bgneal@442 1399 var css = '', name, value;
bgneal@442 1400
bgneal@442 1401 function serializeStyles(name) {
bgneal@442 1402 var styleList, i, l, name, value;
bgneal@442 1403
bgneal@442 1404 styleList = schema.styles[name];
bgneal@442 1405 if (styleList) {
bgneal@442 1406 for (i = 0, l = styleList.length; i < l; i++) {
bgneal@442 1407 name = styleList[i];
bgneal@442 1408 value = styles[name];
bgneal@442 1409
bgneal@442 1410 if (value !== undef && value.length > 0)
bgneal@442 1411 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
bgneal@442 1412 }
bgneal@442 1413 }
bgneal@442 1414 };
bgneal@442 1415
bgneal@442 1416 // Serialize styles according to schema
bgneal@442 1417 if (element_name && schema && schema.styles) {
bgneal@442 1418 // Serialize global styles and element specific styles
bgneal@442 1419 serializeStyles('*');
bgneal@442 1420 serializeStyles(name);
bgneal@442 1421 } else {
bgneal@442 1422 // Output the styles in the order they are inside the object
bgneal@442 1423 for (name in styles) {
bgneal@442 1424 value = styles[name];
bgneal@442 1425
bgneal@442 1426 if (value !== undef && value.length > 0)
bgneal@442 1427 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
bgneal@442 1428 }
bgneal@442 1429 }
bgneal@442 1430
bgneal@442 1431 return css;
bgneal@442 1432 }
bgneal@442 1433 };
bgneal@442 1434 };
bgneal@442 1435
bgneal@442 1436 (function(tinymce) {
bgneal@442 1437 var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
bgneal@442 1438 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
bgneal@442 1439
bgneal@442 1440 function split(str, delim) {
bgneal@442 1441 return str.split(delim || ',');
bgneal@442 1442 };
bgneal@442 1443
bgneal@442 1444 function unpack(lookup, data) {
bgneal@442 1445 var key, elements = {};
bgneal@442 1446
bgneal@442 1447 function replace(value) {
bgneal@442 1448 return value.replace(/[A-Z]+/g, function(key) {
bgneal@442 1449 return replace(lookup[key]);
bgneal@442 1450 });
bgneal@442 1451 };
bgneal@442 1452
bgneal@442 1453 // Unpack lookup
bgneal@442 1454 for (key in lookup) {
bgneal@442 1455 if (lookup.hasOwnProperty(key))
bgneal@442 1456 lookup[key] = replace(lookup[key]);
bgneal@442 1457 }
bgneal@442 1458
bgneal@442 1459 // Unpack and parse data into object map
bgneal@442 1460 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
bgneal@442 1461 attributes = split(attributes, '|');
bgneal@442 1462
bgneal@442 1463 elements[name] = {
bgneal@442 1464 attributes : makeMap(attributes),
bgneal@442 1465 attributesOrder : attributes,
bgneal@442 1466 children : makeMap(children, '|', {'#comment' : {}})
bgneal@442 1467 }
bgneal@442 1468 });
bgneal@442 1469
bgneal@442 1470 return elements;
bgneal@442 1471 };
bgneal@442 1472
bgneal@442 1473 // Build a lookup table for block elements both lowercase and uppercase
bgneal@442 1474 blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' +
bgneal@442 1475 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' +
bgneal@442 1476 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
bgneal@442 1477 blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
bgneal@442 1478
bgneal@442 1479 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
bgneal@442 1480 transitional = unpack({
bgneal@442 1481 Z : 'H|K|N|O|P',
bgneal@442 1482 Y : 'X|form|R|Q',
bgneal@442 1483 ZG : 'E|span|width|align|char|charoff|valign',
bgneal@442 1484 X : 'p|T|div|U|W|isindex|fieldset|table',
bgneal@442 1485 ZF : 'E|align|char|charoff|valign',
bgneal@442 1486 W : 'pre|hr|blockquote|address|center|noframes',
bgneal@442 1487 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
bgneal@442 1488 ZD : '[E][S]',
bgneal@442 1489 U : 'ul|ol|dl|menu|dir',
bgneal@442 1490 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
bgneal@442 1491 T : 'h1|h2|h3|h4|h5|h6',
bgneal@442 1492 ZB : 'X|S|Q',
bgneal@442 1493 S : 'R|P',
bgneal@442 1494 ZA : 'a|G|J|M|O|P',
bgneal@442 1495 R : 'a|H|K|N|O',
bgneal@442 1496 Q : 'noscript|P',
bgneal@442 1497 P : 'ins|del|script',
bgneal@442 1498 O : 'input|select|textarea|label|button',
bgneal@442 1499 N : 'M|L',
bgneal@442 1500 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
bgneal@442 1501 L : 'sub|sup',
bgneal@442 1502 K : 'J|I',
bgneal@442 1503 J : 'tt|i|b|u|s|strike',
bgneal@442 1504 I : 'big|small|font|basefont',
bgneal@442 1505 H : 'G|F',
bgneal@442 1506 G : 'br|span|bdo',
bgneal@442 1507 F : 'object|applet|img|map|iframe',
bgneal@442 1508 E : 'A|B|C',
bgneal@442 1509 D : 'accesskey|tabindex|onfocus|onblur',
bgneal@442 1510 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
bgneal@442 1511 B : 'lang|xml:lang|dir',
bgneal@442 1512 A : 'id|class|style|title'
bgneal@442 1513 }, 'script[id|charset|type|language|src|defer|xml:space][]' +
bgneal@442 1514 'style[B|id|type|media|title|xml:space][]' +
bgneal@442 1515 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
bgneal@442 1516 'param[id|name|value|valuetype|type][]' +
bgneal@442 1517 'p[E|align][#|S]' +
bgneal@442 1518 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
bgneal@442 1519 'br[A|clear][]' +
bgneal@442 1520 'span[E][#|S]' +
bgneal@442 1521 'bdo[A|C|B][#|S]' +
bgneal@442 1522 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
bgneal@442 1523 'h1[E|align][#|S]' +
bgneal@442 1524 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
bgneal@442 1525 'map[B|C|A|name][X|form|Q|area]' +
bgneal@442 1526 'h2[E|align][#|S]' +
bgneal@442 1527 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
bgneal@442 1528 'h3[E|align][#|S]' +
bgneal@442 1529 'tt[E][#|S]' +
bgneal@442 1530 'i[E][#|S]' +
bgneal@442 1531 'b[E][#|S]' +
bgneal@442 1532 'u[E][#|S]' +
bgneal@442 1533 's[E][#|S]' +
bgneal@442 1534 'strike[E][#|S]' +
bgneal@442 1535 'big[E][#|S]' +
bgneal@442 1536 'small[E][#|S]' +
bgneal@442 1537 'font[A|B|size|color|face][#|S]' +
bgneal@442 1538 'basefont[id|size|color|face][]' +
bgneal@442 1539 'em[E][#|S]' +
bgneal@442 1540 'strong[E][#|S]' +
bgneal@442 1541 'dfn[E][#|S]' +
bgneal@442 1542 'code[E][#|S]' +
bgneal@442 1543 'q[E|cite][#|S]' +
bgneal@442 1544 'samp[E][#|S]' +
bgneal@442 1545 'kbd[E][#|S]' +
bgneal@442 1546 'var[E][#|S]' +
bgneal@442 1547 'cite[E][#|S]' +
bgneal@442 1548 'abbr[E][#|S]' +
bgneal@442 1549 'acronym[E][#|S]' +
bgneal@442 1550 'sub[E][#|S]' +
bgneal@442 1551 'sup[E][#|S]' +
bgneal@442 1552 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
bgneal@442 1553 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
bgneal@442 1554 'optgroup[E|disabled|label][option]' +
bgneal@442 1555 'option[E|selected|disabled|label|value][]' +
bgneal@442 1556 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
bgneal@442 1557 'label[E|for|accesskey|onfocus|onblur][#|S]' +
bgneal@442 1558 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
bgneal@442 1559 'h4[E|align][#|S]' +
bgneal@442 1560 'ins[E|cite|datetime][#|Y]' +
bgneal@442 1561 'h5[E|align][#|S]' +
bgneal@442 1562 'del[E|cite|datetime][#|Y]' +
bgneal@442 1563 'h6[E|align][#|S]' +
bgneal@442 1564 'div[E|align][#|Y]' +
bgneal@442 1565 'ul[E|type|compact][li]' +
bgneal@442 1566 'li[E|type|value][#|Y]' +
bgneal@442 1567 'ol[E|type|compact|start][li]' +
bgneal@442 1568 'dl[E|compact][dt|dd]' +
bgneal@442 1569 'dt[E][#|S]' +
bgneal@442 1570 'dd[E][#|Y]' +
bgneal@442 1571 'menu[E|compact][li]' +
bgneal@442 1572 'dir[E|compact][li]' +
bgneal@442 1573 'pre[E|width|xml:space][#|ZA]' +
bgneal@442 1574 'hr[E|align|noshade|size|width][]' +
bgneal@442 1575 'blockquote[E|cite][#|Y]' +
bgneal@442 1576 'address[E][#|S|p]' +
bgneal@442 1577 'center[E][#|Y]' +
bgneal@442 1578 'noframes[E][#|Y]' +
bgneal@442 1579 'isindex[A|B|prompt][]' +
bgneal@442 1580 'fieldset[E][#|legend|Y]' +
bgneal@442 1581 'legend[E|accesskey|align][#|S]' +
bgneal@442 1582 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
bgneal@442 1583 'caption[E|align][#|S]' +
bgneal@442 1584 'col[ZG][]' +
bgneal@442 1585 'colgroup[ZG][col]' +
bgneal@442 1586 'thead[ZF][tr]' +
bgneal@442 1587 'tr[ZF|bgcolor][th|td]' +
bgneal@442 1588 'th[E|ZE][#|Y]' +
bgneal@442 1589 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
bgneal@442 1590 'noscript[E][#|Y]' +
bgneal@442 1591 'td[E|ZE][#|Y]' +
bgneal@442 1592 'tfoot[ZF][tr]' +
bgneal@442 1593 'tbody[ZF][tr]' +
bgneal@442 1594 'area[E|D|shape|coords|href|nohref|alt|target][]' +
bgneal@442 1595 'base[id|href|target][]' +
bgneal@442 1596 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
bgneal@442 1597 );
bgneal@442 1598
bgneal@442 1599 boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
bgneal@442 1600 shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
bgneal@442 1601 nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
bgneal@442 1602 whiteSpaceElementsMap = makeMap('pre,script,style');
bgneal@442 1603 selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
bgneal@442 1604
bgneal@442 1605 tinymce.html.Schema = function(settings) {
bgneal@442 1606 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
bgneal@442 1607
bgneal@442 1608 settings = settings || {};
bgneal@442 1609
bgneal@442 1610 // Allow all elements and attributes if verify_html is set to false
bgneal@442 1611 if (settings.verify_html === false)
bgneal@442 1612 settings.valid_elements = '*[*]';
bgneal@442 1613
bgneal@442 1614 // Build styles list
bgneal@442 1615 if (settings.valid_styles) {
bgneal@442 1616 validStyles = {};
bgneal@442 1617
bgneal@442 1618 // Convert styles into a rule list
bgneal@442 1619 each(settings.valid_styles, function(value, key) {
bgneal@442 1620 validStyles[key] = tinymce.explode(value);
bgneal@442 1621 });
bgneal@442 1622 }
bgneal@442 1623
bgneal@442 1624 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
bgneal@442 1625 function patternToRegExp(str) {
bgneal@442 1626 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
bgneal@442 1627 };
bgneal@442 1628
bgneal@442 1629 // Parses the specified valid_elements string and adds to the current rules
bgneal@442 1630 // This function is a bit hard to read since it's heavily optimized for speed
bgneal@442 1631 function addValidElements(valid_elements) {
bgneal@442 1632 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
bgneal@442 1633 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
bgneal@442 1634 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
bgneal@442 1635 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
bgneal@442 1636 hasPatternsRegExp = /[*?+]/;
bgneal@442 1637
bgneal@442 1638 if (valid_elements) {
bgneal@442 1639 // Split valid elements into an array with rules
bgneal@442 1640 valid_elements = split(valid_elements);
bgneal@442 1641
bgneal@442 1642 if (elements['@']) {
bgneal@442 1643 globalAttributes = elements['@'].attributes;
bgneal@442 1644 globalAttributesOrder = elements['@'].attributesOrder;
bgneal@442 1645 }
bgneal@442 1646
bgneal@442 1647 // Loop all rules
bgneal@442 1648 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
bgneal@442 1649 // Parse element rule
bgneal@442 1650 matches = elementRuleRegExp.exec(valid_elements[ei]);
bgneal@442 1651 if (matches) {
bgneal@442 1652 // Setup local names for matches
bgneal@442 1653 prefix = matches[1];
bgneal@442 1654 elementName = matches[2];
bgneal@442 1655 outputName = matches[3];
bgneal@442 1656 attrData = matches[4];
bgneal@442 1657
bgneal@442 1658 // Create new attributes and attributesOrder
bgneal@442 1659 attributes = {};
bgneal@442 1660 attributesOrder = [];
bgneal@442 1661
bgneal@442 1662 // Create the new element
bgneal@442 1663 element = {
bgneal@442 1664 attributes : attributes,
bgneal@442 1665 attributesOrder : attributesOrder
bgneal@442 1666 };
bgneal@442 1667
bgneal@442 1668 // Padd empty elements prefix
bgneal@442 1669 if (prefix === '#')
bgneal@442 1670 element.paddEmpty = true;
bgneal@442 1671
bgneal@442 1672 // Remove empty elements prefix
bgneal@442 1673 if (prefix === '-')
bgneal@442 1674 element.removeEmpty = true;
bgneal@442 1675
bgneal@442 1676 // Copy attributes from global rule into current rule
bgneal@442 1677 if (globalAttributes) {
bgneal@442 1678 for (key in globalAttributes)
bgneal@442 1679 attributes[key] = globalAttributes[key];
bgneal@442 1680
bgneal@442 1681 attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
bgneal@442 1682 }
bgneal@442 1683
bgneal@442 1684 // Attributes defined
bgneal@442 1685 if (attrData) {
bgneal@442 1686 attrData = split(attrData, '|');
bgneal@442 1687 for (ai = 0, al = attrData.length; ai < al; ai++) {
bgneal@442 1688 matches = attrRuleRegExp.exec(attrData[ai]);
bgneal@442 1689 if (matches) {
bgneal@442 1690 attr = {};
bgneal@442 1691 attrType = matches[1];
bgneal@442 1692 attrName = matches[2].replace(/::/g, ':');
bgneal@442 1693 prefix = matches[3];
bgneal@442 1694 value = matches[4];
bgneal@442 1695
bgneal@442 1696 // Required
bgneal@442 1697 if (attrType === '!') {
bgneal@442 1698 element.attributesRequired = element.attributesRequired || [];
bgneal@442 1699 element.attributesRequired.push(attrName);
bgneal@442 1700 attr.required = true;
bgneal@442 1701 }
bgneal@442 1702
bgneal@442 1703 // Denied from global
bgneal@442 1704 if (attrType === '-') {
bgneal@442 1705 delete attributes[attrName];
bgneal@442 1706 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
bgneal@442 1707 continue;
bgneal@442 1708 }
bgneal@442 1709
bgneal@442 1710 // Default value
bgneal@442 1711 if (prefix) {
bgneal@442 1712 // Default value
bgneal@442 1713 if (prefix === '=') {
bgneal@442 1714 element.attributesDefault = element.attributesDefault || [];
bgneal@442 1715 element.attributesDefault.push({name: attrName, value: value});
bgneal@442 1716 attr.defaultValue = value;
bgneal@442 1717 }
bgneal@442 1718
bgneal@442 1719 // Forced value
bgneal@442 1720 if (prefix === ':') {
bgneal@442 1721 element.attributesForced = element.attributesForced || [];
bgneal@442 1722 element.attributesForced.push({name: attrName, value: value});
bgneal@442 1723 attr.forcedValue = value;
bgneal@442 1724 }
bgneal@442 1725
bgneal@442 1726 // Required values
bgneal@442 1727 if (prefix === '<')
bgneal@442 1728 attr.validValues = makeMap(value, '?');
bgneal@442 1729 }
bgneal@442 1730
bgneal@442 1731 // Check for attribute patterns
bgneal@442 1732 if (hasPatternsRegExp.test(attrName)) {
bgneal@442 1733 element.attributePatterns = element.attributePatterns || [];
bgneal@442 1734 attr.pattern = patternToRegExp(attrName);
bgneal@442 1735 element.attributePatterns.push(attr);
bgneal@442 1736 } else {
bgneal@442 1737 // Add attribute to order list if it doesn't already exist
bgneal@442 1738 if (!attributes[attrName])
bgneal@442 1739 attributesOrder.push(attrName);
bgneal@442 1740
bgneal@442 1741 attributes[attrName] = attr;
bgneal@442 1742 }
bgneal@442 1743 }
bgneal@442 1744 }
bgneal@442 1745 }
bgneal@442 1746
bgneal@442 1747 // Global rule, store away these for later usage
bgneal@442 1748 if (!globalAttributes && elementName == '@') {
bgneal@442 1749 globalAttributes = attributes;
bgneal@442 1750 globalAttributesOrder = attributesOrder;
bgneal@442 1751 }
bgneal@442 1752
bgneal@442 1753 // Handle substitute elements such as b/strong
bgneal@442 1754 if (outputName) {
bgneal@442 1755 element.outputName = elementName;
bgneal@442 1756 elements[outputName] = element;
bgneal@442 1757 }
bgneal@442 1758
bgneal@442 1759 // Add pattern or exact element
bgneal@442 1760 if (hasPatternsRegExp.test(elementName)) {
bgneal@442 1761 element.pattern = patternToRegExp(elementName);
bgneal@442 1762 patternElements.push(element);
bgneal@442 1763 } else
bgneal@442 1764 elements[elementName] = element;
bgneal@442 1765 }
bgneal@442 1766 }
bgneal@442 1767 }
bgneal@442 1768 };
bgneal@442 1769
bgneal@442 1770 function setValidElements(valid_elements) {
bgneal@442 1771 elements = {};
bgneal@442 1772 patternElements = [];
bgneal@442 1773
bgneal@442 1774 addValidElements(valid_elements);
bgneal@442 1775
bgneal@442 1776 each(transitional, function(element, name) {
bgneal@442 1777 children[name] = element.children;
bgneal@442 1778 });
bgneal@442 1779 };
bgneal@442 1780
bgneal@442 1781 // Adds custom non HTML elements to the schema
bgneal@442 1782 function addCustomElements(custom_elements) {
bgneal@442 1783 var customElementRegExp = /^(~)?(.+)$/;
bgneal@442 1784
bgneal@442 1785 if (custom_elements) {
bgneal@442 1786 each(split(custom_elements), function(rule) {
bgneal@442 1787 var matches = customElementRegExp.exec(rule),
bgneal@442 1788 cloneName = matches[1] === '~' ? 'span' : 'div',
bgneal@442 1789 name = matches[2];
bgneal@442 1790
bgneal@442 1791 children[name] = children[cloneName];
bgneal@442 1792
bgneal@442 1793 // Add custom elements at span/div positions
bgneal@442 1794 each(children, function(element, child) {
bgneal@442 1795 if (element[cloneName])
bgneal@442 1796 element[name] = element[cloneName];
bgneal@442 1797 });
bgneal@442 1798 });
bgneal@442 1799 }
bgneal@442 1800 };
bgneal@442 1801
bgneal@442 1802 // Adds valid children to the schema object
bgneal@442 1803 function addValidChildren(valid_children) {
bgneal@442 1804 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
bgneal@442 1805
bgneal@442 1806 if (valid_children) {
bgneal@442 1807 each(split(valid_children), function(rule) {
bgneal@442 1808 var matches = childRuleRegExp.exec(rule), parent, prefix;
bgneal@442 1809
bgneal@442 1810 if (matches) {
bgneal@442 1811 prefix = matches[1];
bgneal@442 1812
bgneal@442 1813 // Add/remove items from default
bgneal@442 1814 if (prefix)
bgneal@442 1815 parent = children[matches[2]];
bgneal@442 1816 else
bgneal@442 1817 parent = children[matches[2]] = {'#comment' : {}};
bgneal@442 1818
bgneal@442 1819 parent = children[matches[2]];
bgneal@442 1820
bgneal@442 1821 each(split(matches[3], '|'), function(child) {
bgneal@442 1822 if (prefix === '-')
bgneal@442 1823 delete parent[child];
bgneal@442 1824 else
bgneal@442 1825 parent[child] = {};
bgneal@442 1826 });
bgneal@442 1827 }
bgneal@442 1828 });
bgneal@442 1829 }
bgneal@442 1830 }
bgneal@442 1831
bgneal@442 1832 if (!settings.valid_elements) {
bgneal@442 1833 // No valid elements defined then clone the elements from the transitional spec
bgneal@442 1834 each(transitional, function(element, name) {
bgneal@442 1835 elements[name] = {
bgneal@442 1836 attributes : element.attributes,
bgneal@442 1837 attributesOrder : element.attributesOrder
bgneal@442 1838 };
bgneal@442 1839
bgneal@442 1840 children[name] = element.children;
bgneal@442 1841 });
bgneal@442 1842
bgneal@442 1843 // Switch these
bgneal@442 1844 each(split('strong/b,em/i'), function(item) {
bgneal@442 1845 item = split(item, '/');
bgneal@442 1846 elements[item[1]].outputName = item[0];
bgneal@442 1847 });
bgneal@442 1848
bgneal@442 1849 // Add default alt attribute for images
bgneal@442 1850 elements.img.attributesDefault = [{name: 'alt', value: ''}];
bgneal@442 1851
bgneal@442 1852 // Remove these if they are empty by default
bgneal@442 1853 each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
bgneal@442 1854 elements[name].removeEmpty = true;
bgneal@442 1855 });
bgneal@442 1856
bgneal@442 1857 // Padd these by default
bgneal@442 1858 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
bgneal@442 1859 elements[name].paddEmpty = true;
bgneal@442 1860 });
bgneal@442 1861 } else
bgneal@442 1862 setValidElements(settings.valid_elements);
bgneal@442 1863
bgneal@442 1864 addCustomElements(settings.custom_elements);
bgneal@442 1865 addValidChildren(settings.valid_children);
bgneal@442 1866 addValidElements(settings.extended_valid_elements);
bgneal@442 1867
bgneal@442 1868 // Todo: Remove this when we fix list handling to be valid
bgneal@442 1869 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
bgneal@442 1870
bgneal@442 1871 // Delete invalid elements
bgneal@442 1872 if (settings.invalid_elements) {
bgneal@442 1873 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
bgneal@442 1874 if (elements[item])
bgneal@442 1875 delete elements[item];
bgneal@442 1876 });
bgneal@442 1877 }
bgneal@442 1878
bgneal@442 1879 self.children = children;
bgneal@442 1880
bgneal@442 1881 self.styles = validStyles;
bgneal@442 1882
bgneal@442 1883 self.getBoolAttrs = function() {
bgneal@442 1884 return boolAttrMap;
bgneal@442 1885 };
bgneal@442 1886
bgneal@442 1887 self.getBlockElements = function() {
bgneal@442 1888 return blockElementsMap;
bgneal@442 1889 };
bgneal@442 1890
bgneal@442 1891 self.getShortEndedElements = function() {
bgneal@442 1892 return shortEndedElementsMap;
bgneal@442 1893 };
bgneal@442 1894
bgneal@442 1895 self.getSelfClosingElements = function() {
bgneal@442 1896 return selfClosingElementsMap;
bgneal@442 1897 };
bgneal@442 1898
bgneal@442 1899 self.getNonEmptyElements = function() {
bgneal@442 1900 return nonEmptyElementsMap;
bgneal@442 1901 };
bgneal@442 1902
bgneal@442 1903 self.getWhiteSpaceElements = function() {
bgneal@442 1904 return whiteSpaceElementsMap;
bgneal@442 1905 };
bgneal@442 1906
bgneal@442 1907 self.isValidChild = function(name, child) {
bgneal@442 1908 var parent = children[name];
bgneal@442 1909
bgneal@442 1910 return !!(parent && parent[child]);
bgneal@442 1911 };
bgneal@442 1912
bgneal@442 1913 self.getElementRule = function(name) {
bgneal@442 1914 var element = elements[name], i;
bgneal@442 1915
bgneal@442 1916 // Exact match found
bgneal@442 1917 if (element)
bgneal@442 1918 return element;
bgneal@442 1919
bgneal@442 1920 // No exact match then try the patterns
bgneal@442 1921 i = patternElements.length;
bgneal@442 1922 while (i--) {
bgneal@442 1923 element = patternElements[i];
bgneal@442 1924
bgneal@442 1925 if (element.pattern.test(name))
bgneal@442 1926 return element;
bgneal@442 1927 }
bgneal@442 1928 };
bgneal@442 1929
bgneal@442 1930 self.addValidElements = addValidElements;
bgneal@442 1931
bgneal@442 1932 self.setValidElements = setValidElements;
bgneal@442 1933
bgneal@442 1934 self.addCustomElements = addCustomElements;
bgneal@442 1935
bgneal@442 1936 self.addValidChildren = addValidChildren;
bgneal@442 1937 };
bgneal@442 1938
bgneal@442 1939 // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
bgneal@442 1940 tinymce.html.Schema.boolAttrMap = boolAttrMap;
bgneal@442 1941 tinymce.html.Schema.blockElementsMap = blockElementsMap;
bgneal@442 1942 })(tinymce);
bgneal@442 1943
bgneal@442 1944 (function(tinymce) {
bgneal@442 1945 tinymce.html.SaxParser = function(settings, schema) {
bgneal@442 1946 var self = this, noop = function() {};
bgneal@442 1947
bgneal@442 1948 settings = settings || {};
bgneal@442 1949 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@442 1950
bgneal@442 1951 if (settings.fix_self_closing !== false)
bgneal@442 1952 settings.fix_self_closing = true;
bgneal@442 1953
bgneal@442 1954 // Add handler functions from settings and setup default handlers
bgneal@442 1955 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
bgneal@442 1956 if (name)
bgneal@442 1957 self[name] = settings[name] || noop;
bgneal@442 1958 });
bgneal@442 1959
bgneal@442 1960 self.parse = function(html) {
bgneal@442 1961 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
bgneal@442 1962 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
bgneal@442 1963 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
bgneal@442 1964 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
bgneal@442 1965
bgneal@442 1966 function processEndTag(name) {
bgneal@442 1967 var pos, i;
bgneal@442 1968
bgneal@442 1969 // Find position of parent of the same type
bgneal@442 1970 pos = stack.length;
bgneal@442 1971 while (pos--) {
bgneal@442 1972 if (stack[pos].name === name)
bgneal@442 1973 break;
bgneal@442 1974 }
bgneal@442 1975
bgneal@442 1976 // Found parent
bgneal@442 1977 if (pos >= 0) {
bgneal@442 1978 // Close all the open elements
bgneal@442 1979 for (i = stack.length - 1; i >= pos; i--) {
bgneal@442 1980 name = stack[i];
bgneal@442 1981
bgneal@442 1982 if (name.valid)
bgneal@442 1983 self.end(name.name);
bgneal@442 1984 }
bgneal@442 1985
bgneal@442 1986 // Remove the open elements from the stack
bgneal@442 1987 stack.length = pos;
bgneal@442 1988 }
bgneal@442 1989 };
bgneal@442 1990
bgneal@442 1991 // Precompile RegExps and map objects
bgneal@442 1992 tokenRegExp = new RegExp('<(?:' +
bgneal@442 1993 '(?:!--([\\w\\W]*?)-->)|' + // Comment
bgneal@442 1994 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
bgneal@442 1995 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
bgneal@442 1996 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
bgneal@442 1997 '(?:\\/([^>]+)>)|' + // End element
bgneal@442 1998 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
bgneal@442 1999 ')', 'g');
bgneal@442 2000
bgneal@442 2001 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
bgneal@442 2002 specialElements = {
bgneal@442 2003 'script' : /<\/script[^>]*>/gi,
bgneal@442 2004 'style' : /<\/style[^>]*>/gi,
bgneal@442 2005 'noscript' : /<\/noscript[^>]*>/gi
bgneal@442 2006 };
bgneal@442 2007
bgneal@442 2008 // Setup lookup tables for empty elements and boolean attributes
bgneal@442 2009 shortEndedElements = schema.getShortEndedElements();
bgneal@442 2010 selfClosing = schema.getSelfClosingElements();
bgneal@442 2011 fillAttrsMap = schema.getBoolAttrs();
bgneal@442 2012 validate = settings.validate;
bgneal@442 2013 fixSelfClosing = settings.fix_self_closing;
bgneal@442 2014
bgneal@442 2015 while (matches = tokenRegExp.exec(html)) {
bgneal@442 2016 // Text
bgneal@442 2017 if (index < matches.index)
bgneal@442 2018 self.text(decode(html.substr(index, matches.index - index)));
bgneal@442 2019
bgneal@442 2020 if (value = matches[6]) { // End element
bgneal@442 2021 processEndTag(value.toLowerCase());
bgneal@442 2022 } else if (value = matches[7]) { // Start element
bgneal@442 2023 value = value.toLowerCase();
bgneal@442 2024 isShortEnded = value in shortEndedElements;
bgneal@442 2025
bgneal@442 2026 // Is self closing tag for example an <li> after an open <li>
bgneal@442 2027 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
bgneal@442 2028 processEndTag(value);
bgneal@442 2029
bgneal@442 2030 // Validate element
bgneal@442 2031 if (!validate || (elementRule = schema.getElementRule(value))) {
bgneal@442 2032 isValidElement = true;
bgneal@442 2033
bgneal@442 2034 // Grab attributes map and patters when validation is enabled
bgneal@442 2035 if (validate) {
bgneal@442 2036 validAttributesMap = elementRule.attributes;
bgneal@442 2037 validAttributePatterns = elementRule.attributePatterns;
bgneal@442 2038 }
bgneal@442 2039
bgneal@442 2040 // Parse attributes
bgneal@442 2041 if (attribsValue = matches[8]) {
bgneal@442 2042 attrList = [];
bgneal@442 2043 attrList.map = {};
bgneal@442 2044
bgneal@442 2045 attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
bgneal@442 2046 var attrRule, i;
bgneal@442 2047
bgneal@442 2048 name = name.toLowerCase();
bgneal@442 2049 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
bgneal@442 2050
bgneal@442 2051 // Validate name and value
bgneal@442 2052 if (validate && name.indexOf('data-') !== 0) {
bgneal@442 2053 attrRule = validAttributesMap[name];
bgneal@442 2054
bgneal@442 2055 // Find rule by pattern matching
bgneal@442 2056 if (!attrRule && validAttributePatterns) {
bgneal@442 2057 i = validAttributePatterns.length;
bgneal@442 2058 while (i--) {
bgneal@442 2059 attrRule = validAttributePatterns[i];
bgneal@442 2060 if (attrRule.pattern.test(name))
bgneal@442 2061 break;
bgneal@442 2062 }
bgneal@442 2063
bgneal@442 2064 // No rule matched
bgneal@442 2065 if (i === -1)
bgneal@442 2066 attrRule = null;
bgneal@442 2067 }
bgneal@442 2068
bgneal@442 2069 // No attribute rule found
bgneal@442 2070 if (!attrRule)
bgneal@442 2071 return;
bgneal@442 2072
bgneal@442 2073 // Validate value
bgneal@442 2074 if (attrRule.validValues && !(value in attrRule.validValues))
bgneal@442 2075 return;
bgneal@442 2076 }
bgneal@442 2077
bgneal@442 2078 // Add attribute to list and map
bgneal@442 2079 attrList.map[name] = value;
bgneal@442 2080 attrList.push({
bgneal@442 2081 name: name,
bgneal@442 2082 value: value
bgneal@442 2083 });
bgneal@442 2084 });
bgneal@442 2085 } else {
bgneal@442 2086 attrList = [];
bgneal@442 2087 attrList.map = {};
bgneal@442 2088 }
bgneal@442 2089
bgneal@442 2090 // Process attributes if validation is enabled
bgneal@442 2091 if (validate) {
bgneal@442 2092 attributesRequired = elementRule.attributesRequired;
bgneal@442 2093 attributesDefault = elementRule.attributesDefault;
bgneal@442 2094 attributesForced = elementRule.attributesForced;
bgneal@442 2095
bgneal@442 2096 // Handle forced attributes
bgneal@442 2097 if (attributesForced) {
bgneal@442 2098 i = attributesForced.length;
bgneal@442 2099 while (i--) {
bgneal@442 2100 attr = attributesForced[i];
bgneal@442 2101 name = attr.name;
bgneal@442 2102 attrValue = attr.value;
bgneal@442 2103
bgneal@442 2104 if (attrValue === '{$uid}')
bgneal@442 2105 attrValue = 'mce_' + idCount++;
bgneal@442 2106
bgneal@442 2107 attrList.map[name] = attrValue;
bgneal@442 2108 attrList.push({name: name, value: attrValue});
bgneal@442 2109 }
bgneal@442 2110 }
bgneal@442 2111
bgneal@442 2112 // Handle default attributes
bgneal@442 2113 if (attributesDefault) {
bgneal@442 2114 i = attributesDefault.length;
bgneal@442 2115 while (i--) {
bgneal@442 2116 attr = attributesDefault[i];
bgneal@442 2117 name = attr.name;
bgneal@442 2118
bgneal@442 2119 if (!(name in attrList.map)) {
bgneal@442 2120 attrValue = attr.value;
bgneal@442 2121
bgneal@442 2122 if (attrValue === '{$uid}')
bgneal@442 2123 attrValue = 'mce_' + idCount++;
bgneal@442 2124
bgneal@442 2125 attrList.map[name] = attrValue;
bgneal@442 2126 attrList.push({name: name, value: attrValue});
bgneal@442 2127 }
bgneal@442 2128 }
bgneal@442 2129 }
bgneal@442 2130
bgneal@442 2131 // Handle required attributes
bgneal@442 2132 if (attributesRequired) {
bgneal@442 2133 i = attributesRequired.length;
bgneal@442 2134 while (i--) {
bgneal@442 2135 if (attributesRequired[i] in attrList.map)
bgneal@442 2136 break;
bgneal@442 2137 }
bgneal@442 2138
bgneal@442 2139 // None of the required attributes where found
bgneal@442 2140 if (i === -1)
bgneal@442 2141 isValidElement = false;
bgneal@442 2142 }
bgneal@442 2143
bgneal@442 2144 // Invalidate element if it's marked as bogus
bgneal@442 2145 if (attrList.map['data-mce-bogus'])
bgneal@442 2146 isValidElement = false;
bgneal@442 2147 }
bgneal@442 2148
bgneal@442 2149 if (isValidElement)
bgneal@442 2150 self.start(value, attrList, isShortEnded);
bgneal@442 2151 } else
bgneal@442 2152 isValidElement = false;
bgneal@442 2153
bgneal@442 2154 // Treat script, noscript and style a bit different since they may include code that looks like elements
bgneal@442 2155 if (endRegExp = specialElements[value]) {
bgneal@442 2156 endRegExp.lastIndex = index = matches.index + matches[0].length;
bgneal@442 2157
bgneal@442 2158 if (matches = endRegExp.exec(html)) {
bgneal@442 2159 if (isValidElement)
bgneal@442 2160 text = html.substr(index, matches.index - index);
bgneal@442 2161
bgneal@442 2162 index = matches.index + matches[0].length;
bgneal@442 2163 } else {
bgneal@442 2164 text = html.substr(index);
bgneal@442 2165 index = html.length;
bgneal@442 2166 }
bgneal@442 2167
bgneal@442 2168 if (isValidElement && text.length > 0)
bgneal@442 2169 self.text(text, true);
bgneal@442 2170
bgneal@442 2171 if (isValidElement)
bgneal@442 2172 self.end(value);
bgneal@442 2173
bgneal@442 2174 tokenRegExp.lastIndex = index;
bgneal@442 2175 continue;
bgneal@442 2176 }
bgneal@442 2177
bgneal@442 2178 // Push value on to stack
bgneal@442 2179 if (!isShortEnded) {
bgneal@442 2180 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
bgneal@442 2181 stack.push({name: value, valid: isValidElement});
bgneal@442 2182 else if (isValidElement)
bgneal@442 2183 self.end(value);
bgneal@442 2184 }
bgneal@442 2185 } else if (value = matches[1]) { // Comment
bgneal@442 2186 self.comment(value);
bgneal@442 2187 } else if (value = matches[2]) { // CDATA
bgneal@442 2188 self.cdata(value);
bgneal@442 2189 } else if (value = matches[3]) { // DOCTYPE
bgneal@442 2190 self.doctype(value);
bgneal@442 2191 } else if (value = matches[4]) { // PI
bgneal@442 2192 self.pi(value, matches[5]);
bgneal@442 2193 }
bgneal@442 2194
bgneal@442 2195 index = matches.index + matches[0].length;
bgneal@442 2196 }
bgneal@442 2197
bgneal@442 2198 // Text
bgneal@442 2199 if (index < html.length)
bgneal@442 2200 self.text(decode(html.substr(index)));
bgneal@442 2201
bgneal@442 2202 // Close any open elements
bgneal@442 2203 for (i = stack.length - 1; i >= 0; i--) {
bgneal@442 2204 value = stack[i];
bgneal@442 2205
bgneal@442 2206 if (value.valid)
bgneal@442 2207 self.end(value.name);
bgneal@442 2208 }
bgneal@442 2209 };
bgneal@442 2210 }
bgneal@442 2211 })(tinymce);
bgneal@442 2212
bgneal@442 2213 (function(tinymce) {
bgneal@442 2214 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
bgneal@442 2215 '#text' : 3,
bgneal@442 2216 '#comment' : 8,
bgneal@442 2217 '#cdata' : 4,
bgneal@442 2218 '#pi' : 7,
bgneal@442 2219 '#doctype' : 10,
bgneal@442 2220 '#document-fragment' : 11
bgneal@442 2221 };
bgneal@442 2222
bgneal@442 2223 // Walks the tree left/right
bgneal@442 2224 function walk(node, root_node, prev) {
bgneal@442 2225 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
bgneal@442 2226
bgneal@442 2227 // Walk into nodes if it has a start
bgneal@442 2228 if (node[startName])
bgneal@442 2229 return node[startName];
bgneal@442 2230
bgneal@442 2231 // Return the sibling if it has one
bgneal@442 2232 if (node !== root_node) {
bgneal@442 2233 sibling = node[siblingName];
bgneal@442 2234
bgneal@442 2235 if (sibling)
bgneal@442 2236 return sibling;
bgneal@442 2237
bgneal@442 2238 // Walk up the parents to look for siblings
bgneal@442 2239 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
bgneal@442 2240 sibling = parent[siblingName];
bgneal@442 2241
bgneal@442 2242 if (sibling)
bgneal@442 2243 return sibling;
bgneal@442 2244 }
bgneal@442 2245 }
bgneal@442 2246 };
bgneal@442 2247
bgneal@442 2248 function Node(name, type) {
bgneal@442 2249 this.name = name;
bgneal@442 2250 this.type = type;
bgneal@442 2251
bgneal@442 2252 if (type === 1) {
bgneal@442 2253 this.attributes = [];
bgneal@442 2254 this.attributes.map = {};
bgneal@442 2255 }
bgneal@442 2256 }
bgneal@442 2257
bgneal@442 2258 tinymce.extend(Node.prototype, {
bgneal@442 2259 replace : function(node) {
bgneal@442 2260 var self = this;
bgneal@442 2261
bgneal@442 2262 if (node.parent)
bgneal@442 2263 node.remove();
bgneal@442 2264
bgneal@442 2265 self.insert(node, self);
bgneal@442 2266 self.remove();
bgneal@442 2267
bgneal@442 2268 return self;
bgneal@442 2269 },
bgneal@442 2270
bgneal@442 2271 attr : function(name, value) {
bgneal@442 2272 var self = this, attrs, i, undef;
bgneal@442 2273
bgneal@442 2274 if (typeof name !== "string") {
bgneal@442 2275 for (i in name)
bgneal@442 2276 self.attr(i, name[i]);
bgneal@442 2277
bgneal@442 2278 return self;
bgneal@442 2279 }
bgneal@442 2280
bgneal@442 2281 if (attrs = self.attributes) {
bgneal@442 2282 if (value !== undef) {
bgneal@442 2283 // Remove attribute
bgneal@442 2284 if (value === null) {
bgneal@442 2285 if (name in attrs.map) {
bgneal@442 2286 delete attrs.map[name];
bgneal@442 2287
bgneal@442 2288 i = attrs.length;
bgneal@442 2289 while (i--) {
bgneal@442 2290 if (attrs[i].name === name) {
bgneal@442 2291 attrs = attrs.splice(i, 1);
bgneal@442 2292 return self;
bgneal@442 2293 }
bgneal@442 2294 }
bgneal@442 2295 }
bgneal@442 2296
bgneal@442 2297 return self;
bgneal@442 2298 }
bgneal@442 2299
bgneal@442 2300 // Set attribute
bgneal@442 2301 if (name in attrs.map) {
bgneal@442 2302 // Set attribute
bgneal@442 2303 i = attrs.length;
bgneal@442 2304 while (i--) {
bgneal@442 2305 if (attrs[i].name === name) {
bgneal@442 2306 attrs[i].value = value;
bgneal@442 2307 break;
bgneal@442 2308 }
bgneal@442 2309 }
bgneal@442 2310 } else
bgneal@442 2311 attrs.push({name: name, value: value});
bgneal@442 2312
bgneal@442 2313 attrs.map[name] = value;
bgneal@442 2314
bgneal@442 2315 return self;
bgneal@442 2316 } else {
bgneal@442 2317 return attrs.map[name];
bgneal@442 2318 }
bgneal@442 2319 }
bgneal@442 2320 },
bgneal@442 2321
bgneal@442 2322 clone : function() {
bgneal@442 2323 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
bgneal@442 2324
bgneal@442 2325 // Clone element attributes
bgneal@442 2326 if (selfAttrs = self.attributes) {
bgneal@442 2327 cloneAttrs = [];
bgneal@442 2328 cloneAttrs.map = {};
bgneal@442 2329
bgneal@442 2330 for (i = 0, l = selfAttrs.length; i < l; i++) {
bgneal@442 2331 selfAttr = selfAttrs[i];
bgneal@442 2332
bgneal@442 2333 // Clone everything except id
bgneal@442 2334 if (selfAttr.name !== 'id') {
bgneal@442 2335 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
bgneal@442 2336 cloneAttrs.map[selfAttr.name] = selfAttr.value;
bgneal@442 2337 }
bgneal@442 2338 }
bgneal@442 2339
bgneal@442 2340 clone.attributes = cloneAttrs;
bgneal@442 2341 }
bgneal@442 2342
bgneal@442 2343 clone.value = self.value;
bgneal@442 2344 clone.shortEnded = self.shortEnded;
bgneal@442 2345
bgneal@442 2346 return clone;
bgneal@442 2347 },
bgneal@442 2348
bgneal@442 2349 wrap : function(wrapper) {
bgneal@442 2350 var self = this;
bgneal@442 2351
bgneal@442 2352 self.parent.insert(wrapper, self);
bgneal@442 2353 wrapper.append(self);
bgneal@442 2354
bgneal@442 2355 return self;
bgneal@442 2356 },
bgneal@442 2357
bgneal@442 2358 unwrap : function() {
bgneal@442 2359 var self = this, node, next;
bgneal@442 2360
bgneal@442 2361 for (node = self.firstChild; node; ) {
bgneal@442 2362 next = node.next;
bgneal@442 2363 self.insert(node, self, true);
bgneal@442 2364 node = next;
bgneal@442 2365 }
bgneal@442 2366
bgneal@442 2367 self.remove();
bgneal@442 2368 },
bgneal@442 2369
bgneal@442 2370 remove : function() {
bgneal@442 2371 var self = this, parent = self.parent, next = self.next, prev = self.prev;
bgneal@442 2372
bgneal@442 2373 if (parent) {
bgneal@442 2374 if (parent.firstChild === self) {
bgneal@442 2375 parent.firstChild = next;
bgneal@442 2376
bgneal@442 2377 if (next)
bgneal@442 2378 next.prev = null;
bgneal@442 2379 } else {
bgneal@442 2380 prev.next = next;
bgneal@442 2381 }
bgneal@442 2382
bgneal@442 2383 if (parent.lastChild === self) {
bgneal@442 2384 parent.lastChild = prev;
bgneal@442 2385
bgneal@442 2386 if (prev)
bgneal@442 2387 prev.next = null;
bgneal@442 2388 } else {
bgneal@442 2389 next.prev = prev;
bgneal@442 2390 }
bgneal@442 2391
bgneal@442 2392 self.parent = self.next = self.prev = null;
bgneal@442 2393 }
bgneal@442 2394
bgneal@442 2395 return self;
bgneal@442 2396 },
bgneal@442 2397
bgneal@442 2398 append : function(node) {
bgneal@442 2399 var self = this, last;
bgneal@442 2400
bgneal@442 2401 if (node.parent)
bgneal@442 2402 node.remove();
bgneal@442 2403
bgneal@442 2404 last = self.lastChild;
bgneal@442 2405 if (last) {
bgneal@442 2406 last.next = node;
bgneal@442 2407 node.prev = last;
bgneal@442 2408 self.lastChild = node;
bgneal@442 2409 } else
bgneal@442 2410 self.lastChild = self.firstChild = node;
bgneal@442 2411
bgneal@442 2412 node.parent = self;
bgneal@442 2413
bgneal@442 2414 return node;
bgneal@442 2415 },
bgneal@442 2416
bgneal@442 2417 insert : function(node, ref_node, before) {
bgneal@442 2418 var parent;
bgneal@442 2419
bgneal@442 2420 if (node.parent)
bgneal@442 2421 node.remove();
bgneal@442 2422
bgneal@442 2423 parent = ref_node.parent || this;
bgneal@442 2424
bgneal@442 2425 if (before) {
bgneal@442 2426 if (ref_node === parent.firstChild)
bgneal@442 2427 parent.firstChild = node;
bgneal@442 2428 else
bgneal@442 2429 ref_node.prev.next = node;
bgneal@442 2430
bgneal@442 2431 node.prev = ref_node.prev;
bgneal@442 2432 node.next = ref_node;
bgneal@442 2433 ref_node.prev = node;
bgneal@442 2434 } else {
bgneal@442 2435 if (ref_node === parent.lastChild)
bgneal@442 2436 parent.lastChild = node;
bgneal@442 2437 else
bgneal@442 2438 ref_node.next.prev = node;
bgneal@442 2439
bgneal@442 2440 node.next = ref_node.next;
bgneal@442 2441 node.prev = ref_node;
bgneal@442 2442 ref_node.next = node;
bgneal@442 2443 }
bgneal@442 2444
bgneal@442 2445 node.parent = parent;
bgneal@442 2446
bgneal@442 2447 return node;
bgneal@442 2448 },
bgneal@442 2449
bgneal@442 2450 getAll : function(name) {
bgneal@442 2451 var self = this, node, collection = [];
bgneal@442 2452
bgneal@442 2453 for (node = self.firstChild; node; node = walk(node, self)) {
bgneal@442 2454 if (node.name === name)
bgneal@442 2455 collection.push(node);
bgneal@442 2456 }
bgneal@442 2457
bgneal@442 2458 return collection;
bgneal@442 2459 },
bgneal@442 2460
bgneal@442 2461 empty : function() {
bgneal@442 2462 var self = this, nodes, i, node;
bgneal@442 2463
bgneal@442 2464 // Remove all children
bgneal@442 2465 if (self.firstChild) {
bgneal@442 2466 nodes = [];
bgneal@442 2467
bgneal@442 2468 // Collect the children
bgneal@442 2469 for (node = self.firstChild; node; node = walk(node, self))
bgneal@442 2470 nodes.push(node);
bgneal@442 2471
bgneal@442 2472 // Remove the children
bgneal@442 2473 i = nodes.length;
bgneal@442 2474 while (i--) {
bgneal@442 2475 node = nodes[i];
bgneal@442 2476 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
bgneal@442 2477 }
bgneal@442 2478 }
bgneal@442 2479
bgneal@442 2480 self.firstChild = self.lastChild = null;
bgneal@442 2481
bgneal@442 2482 return self;
bgneal@442 2483 },
bgneal@442 2484
bgneal@442 2485 isEmpty : function(elements) {
bgneal@442 2486 var self = this, node = self.firstChild, i, name;
bgneal@442 2487
bgneal@442 2488 if (node) {
bgneal@442 2489 do {
bgneal@442 2490 if (node.type === 1) {
bgneal@442 2491 // Ignore bogus elements
bgneal@442 2492 if (node.attributes.map['data-mce-bogus'])
bgneal@442 2493 continue;
bgneal@442 2494
bgneal@442 2495 // Keep empty elements like <img />
bgneal@442 2496 if (elements[node.name])
bgneal@442 2497 return false;
bgneal@442 2498
bgneal@442 2499 // Keep elements with data attributes or name attribute like <a name="1"></a>
bgneal@442 2500 i = node.attributes.length;
bgneal@442 2501 while (i--) {
bgneal@442 2502 name = node.attributes[i].name;
bgneal@442 2503 if (name === "name" || name.indexOf('data-') === 0)
bgneal@442 2504 return false;
bgneal@442 2505 }
bgneal@442 2506 }
bgneal@442 2507
bgneal@442 2508 // Keep non whitespace text nodes
bgneal@442 2509 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
bgneal@442 2510 return false;
bgneal@442 2511 } while (node = walk(node, self));
bgneal@442 2512 }
bgneal@442 2513
bgneal@442 2514 return true;
bgneal@442 2515 }
bgneal@442 2516 });
bgneal@442 2517
bgneal@442 2518 tinymce.extend(Node, {
bgneal@442 2519 create : function(name, attrs) {
bgneal@442 2520 var node, attrName;
bgneal@442 2521
bgneal@442 2522 // Create node
bgneal@442 2523 node = new Node(name, typeLookup[name] || 1);
bgneal@442 2524
bgneal@442 2525 // Add attributes if needed
bgneal@442 2526 if (attrs) {
bgneal@442 2527 for (attrName in attrs)
bgneal@442 2528 node.attr(attrName, attrs[attrName]);
bgneal@442 2529 }
bgneal@442 2530
bgneal@442 2531 return node;
bgneal@442 2532 }
bgneal@442 2533 });
bgneal@442 2534
bgneal@442 2535 tinymce.html.Node = Node;
bgneal@442 2536 })(tinymce);
bgneal@442 2537
bgneal@442 2538 (function(tinymce) {
bgneal@442 2539 var Node = tinymce.html.Node;
bgneal@442 2540
bgneal@442 2541 tinymce.html.DomParser = function(settings, schema) {
bgneal@442 2542 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
bgneal@442 2543
bgneal@442 2544 settings = settings || {};
bgneal@442 2545 settings.validate = "validate" in settings ? settings.validate : true;
bgneal@442 2546 settings.root_name = settings.root_name || 'body';
bgneal@442 2547 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@442 2548
bgneal@442 2549 function fixInvalidChildren(nodes) {
bgneal@442 2550 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
bgneal@442 2551 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
bgneal@442 2552
bgneal@442 2553 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
bgneal@442 2554 nonEmptyElements = schema.getNonEmptyElements();
bgneal@442 2555
bgneal@442 2556 for (ni = 0; ni < nodes.length; ni++) {
bgneal@442 2557 node = nodes[ni];
bgneal@442 2558
bgneal@442 2559 // Already removed
bgneal@442 2560 if (!node.parent)
bgneal@442 2561 continue;
bgneal@442 2562
bgneal@442 2563 // Get list of all parent nodes until we find a valid parent to stick the child into
bgneal@442 2564 parents = [node];
bgneal@442 2565 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
bgneal@442 2566 parents.push(parent);
bgneal@442 2567
bgneal@442 2568 // Found a suitable parent
bgneal@442 2569 if (parent && parents.length > 1) {
bgneal@442 2570 // Reverse the array since it makes looping easier
bgneal@442 2571 parents.reverse();
bgneal@442 2572
bgneal@442 2573 // Clone the related parent and insert that after the moved node
bgneal@442 2574 newParent = currentNode = self.filterNode(parents[0].clone());
bgneal@442 2575
bgneal@442 2576 // Start cloning and moving children on the left side of the target node
bgneal@442 2577 for (i = 0; i < parents.length - 1; i++) {
bgneal@442 2578 if (schema.isValidChild(currentNode.name, parents[i].name)) {
bgneal@442 2579 tempNode = self.filterNode(parents[i].clone());
bgneal@442 2580 currentNode.append(tempNode);
bgneal@442 2581 } else
bgneal@442 2582 tempNode = currentNode;
bgneal@442 2583
bgneal@442 2584 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
bgneal@442 2585 nextNode = childNode.next;
bgneal@442 2586 tempNode.append(childNode);
bgneal@442 2587 childNode = nextNode;
bgneal@442 2588 }
bgneal@442 2589
bgneal@442 2590 currentNode = tempNode;
bgneal@442 2591 }
bgneal@442 2592
bgneal@442 2593 if (!newParent.isEmpty(nonEmptyElements)) {
bgneal@442 2594 parent.insert(newParent, parents[0], true);
bgneal@442 2595 parent.insert(node, newParent);
bgneal@442 2596 } else {
bgneal@442 2597 parent.insert(node, parents[0], true);
bgneal@442 2598 }
bgneal@442 2599
bgneal@442 2600 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
bgneal@442 2601 parent = parents[0];
bgneal@442 2602 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
bgneal@442 2603 parent.empty().remove();
bgneal@442 2604 }
bgneal@442 2605 } else if (node.parent) {
bgneal@442 2606 // If it's an LI try to find a UL/OL for it or wrap it
bgneal@442 2607 if (node.name === 'li') {
bgneal@442 2608 sibling = node.prev;
bgneal@442 2609 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
bgneal@442 2610 sibling.append(node);
bgneal@442 2611 continue;
bgneal@442 2612 }
bgneal@442 2613
bgneal@442 2614 sibling = node.next;
bgneal@442 2615 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
bgneal@442 2616 sibling.insert(node, sibling.firstChild, true);
bgneal@442 2617 continue;
bgneal@442 2618 }
bgneal@442 2619
bgneal@442 2620 node.wrap(self.filterNode(new Node('ul', 1)));
bgneal@442 2621 continue;
bgneal@442 2622 }
bgneal@442 2623
bgneal@442 2624 // Try wrapping the element in a DIV
bgneal@442 2625 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
bgneal@442 2626 node.wrap(self.filterNode(new Node('div', 1)));
bgneal@442 2627 } else {
bgneal@442 2628 // We failed wrapping it, then remove or unwrap it
bgneal@442 2629 if (node.name === 'style' || node.name === 'script')
bgneal@442 2630 node.empty().remove();
bgneal@442 2631 else
bgneal@442 2632 node.unwrap();
bgneal@442 2633 }
bgneal@442 2634 }
bgneal@442 2635 }
bgneal@442 2636 };
bgneal@442 2637
bgneal@442 2638 self.filterNode = function(node) {
bgneal@442 2639 var i, name, list;
bgneal@442 2640
bgneal@442 2641 // Run element filters
bgneal@442 2642 if (name in nodeFilters) {
bgneal@442 2643 list = matchedNodes[name];
bgneal@442 2644
bgneal@442 2645 if (list)
bgneal@442 2646 list.push(node);
bgneal@442 2647 else
bgneal@442 2648 matchedNodes[name] = [node];
bgneal@442 2649 }
bgneal@442 2650
bgneal@442 2651 // Run attribute filters
bgneal@442 2652 i = attributeFilters.length;
bgneal@442 2653 while (i--) {
bgneal@442 2654 name = attributeFilters[i].name;
bgneal@442 2655
bgneal@442 2656 if (name in node.attributes.map) {
bgneal@442 2657 list = matchedAttributes[name];
bgneal@442 2658
bgneal@442 2659 if (list)
bgneal@442 2660 list.push(node);
bgneal@442 2661 else
bgneal@442 2662 matchedAttributes[name] = [node];
bgneal@442 2663 }
bgneal@442 2664 }
bgneal@442 2665
bgneal@442 2666 return node;
bgneal@442 2667 };
bgneal@442 2668
bgneal@442 2669 self.addNodeFilter = function(name, callback) {
bgneal@442 2670 tinymce.each(tinymce.explode(name), function(name) {
bgneal@442 2671 var list = nodeFilters[name];
bgneal@442 2672
bgneal@442 2673 if (!list)
bgneal@442 2674 nodeFilters[name] = list = [];
bgneal@442 2675
bgneal@442 2676 list.push(callback);
bgneal@442 2677 });
bgneal@442 2678 };
bgneal@442 2679
bgneal@442 2680 self.addAttributeFilter = function(name, callback) {
bgneal@442 2681 tinymce.each(tinymce.explode(name), function(name) {
bgneal@442 2682 var i;
bgneal@442 2683
bgneal@442 2684 for (i = 0; i < attributeFilters.length; i++) {
bgneal@442 2685 if (attributeFilters[i].name === name) {
bgneal@442 2686 attributeFilters[i].callbacks.push(callback);
bgneal@442 2687 return;
bgneal@442 2688 }
bgneal@442 2689 }
bgneal@442 2690
bgneal@442 2691 attributeFilters.push({name: name, callbacks: [callback]});
bgneal@442 2692 });
bgneal@442 2693 };
bgneal@442 2694
bgneal@442 2695 self.parse = function(html, args) {
bgneal@442 2696 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
bgneal@442 2697 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
bgneal@442 2698 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
bgneal@442 2699
bgneal@442 2700 args = args || {};
bgneal@442 2701 matchedNodes = {};
bgneal@442 2702 matchedAttributes = {};
bgneal@442 2703 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
bgneal@442 2704 nonEmptyElements = schema.getNonEmptyElements();
bgneal@442 2705 children = schema.children;
bgneal@442 2706 validate = settings.validate;
bgneal@442 2707
bgneal@442 2708 whiteSpaceElements = schema.getWhiteSpaceElements();
bgneal@442 2709 startWhiteSpaceRegExp = /^[ \t\r\n]+/;
bgneal@442 2710 endWhiteSpaceRegExp = /[ \t\r\n]+$/;
bgneal@442 2711 allWhiteSpaceRegExp = /[ \t\r\n]+/g;
bgneal@442 2712
bgneal@442 2713 function createNode(name, type) {
bgneal@442 2714 var node = new Node(name, type), list;
bgneal@442 2715
bgneal@442 2716 if (name in nodeFilters) {
bgneal@442 2717 list = matchedNodes[name];
bgneal@442 2718
bgneal@442 2719 if (list)
bgneal@442 2720 list.push(node);
bgneal@442 2721 else
bgneal@442 2722 matchedNodes[name] = [node];
bgneal@442 2723 }
bgneal@442 2724
bgneal@442 2725 return node;
bgneal@442 2726 };
bgneal@442 2727
bgneal@442 2728 function removeWhitespaceBefore(node) {
bgneal@442 2729 var textNode, textVal, sibling;
bgneal@442 2730
bgneal@442 2731 for (textNode = node.prev; textNode && textNode.type === 3; ) {
bgneal@442 2732 textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
bgneal@442 2733
bgneal@442 2734 if (textVal.length > 0) {
bgneal@442 2735 textNode.value = textVal;
bgneal@442 2736 textNode = textNode.prev;
bgneal@442 2737 } else {
bgneal@442 2738 sibling = textNode.prev;
bgneal@442 2739 textNode.remove();
bgneal@442 2740 textNode = sibling;
bgneal@442 2741 }
bgneal@442 2742 }
bgneal@442 2743 };
bgneal@442 2744
bgneal@442 2745 parser = new tinymce.html.SaxParser({
bgneal@442 2746 validate : validate,
bgneal@442 2747 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
bgneal@442 2748
bgneal@442 2749 cdata: function(text) {
bgneal@442 2750 node.append(createNode('#cdata', 4)).value = text;
bgneal@442 2751 },
bgneal@442 2752
bgneal@442 2753 text: function(text, raw) {
bgneal@442 2754 var textNode;
bgneal@442 2755
bgneal@442 2756 // Trim all redundant whitespace on non white space elements
bgneal@442 2757 if (!whiteSpaceElements[node.name]) {
bgneal@442 2758 text = text.replace(allWhiteSpaceRegExp, ' ');
bgneal@442 2759
bgneal@442 2760 if (node.lastChild && blockElements[node.lastChild.name])
bgneal@442 2761 text = text.replace(startWhiteSpaceRegExp, '');
bgneal@442 2762 }
bgneal@442 2763
bgneal@442 2764 // Do we need to create the node
bgneal@442 2765 if (text.length !== 0) {
bgneal@442 2766 textNode = createNode('#text', 3);
bgneal@442 2767 textNode.raw = !!raw;
bgneal@442 2768 node.append(textNode).value = text;
bgneal@442 2769 }
bgneal@442 2770 },
bgneal@442 2771
bgneal@442 2772 comment: function(text) {
bgneal@442 2773 node.append(createNode('#comment', 8)).value = text;
bgneal@442 2774 },
bgneal@442 2775
bgneal@442 2776 pi: function(name, text) {
bgneal@442 2777 node.append(createNode(name, 7)).value = text;
bgneal@442 2778 removeWhitespaceBefore(node);
bgneal@442 2779 },
bgneal@442 2780
bgneal@442 2781 doctype: function(text) {
bgneal@442 2782 var newNode;
bgneal@442 2783
bgneal@442 2784 newNode = node.append(createNode('#doctype', 10));
bgneal@442 2785 newNode.value = text;
bgneal@442 2786 removeWhitespaceBefore(node);
bgneal@442 2787 },
bgneal@442 2788
bgneal@442 2789 start: function(name, attrs, empty) {
bgneal@442 2790 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
bgneal@442 2791
bgneal@442 2792 elementRule = validate ? schema.getElementRule(name) : {};
bgneal@442 2793 if (elementRule) {
bgneal@442 2794 newNode = createNode(elementRule.outputName || name, 1);
bgneal@442 2795 newNode.attributes = attrs;
bgneal@442 2796 newNode.shortEnded = empty;
bgneal@442 2797
bgneal@442 2798 node.append(newNode);
bgneal@442 2799
bgneal@442 2800 // Check if node is valid child of the parent node is the child is
bgneal@442 2801 // unknown we don't collect it since it's probably a custom element
bgneal@442 2802 parent = children[node.name];
bgneal@442 2803 if (parent && children[newNode.name] && !parent[newNode.name])
bgneal@442 2804 invalidChildren.push(newNode);
bgneal@442 2805
bgneal@442 2806 attrFiltersLen = attributeFilters.length;
bgneal@442 2807 while (attrFiltersLen--) {
bgneal@442 2808 attrName = attributeFilters[attrFiltersLen].name;
bgneal@442 2809
bgneal@442 2810 if (attrName in attrs.map) {
bgneal@442 2811 list = matchedAttributes[attrName];
bgneal@442 2812
bgneal@442 2813 if (list)
bgneal@442 2814 list.push(newNode);
bgneal@442 2815 else
bgneal@442 2816 matchedAttributes[attrName] = [newNode];
bgneal@442 2817 }
bgneal@442 2818 }
bgneal@442 2819
bgneal@442 2820 // Trim whitespace before block
bgneal@442 2821 if (blockElements[name])
bgneal@442 2822 removeWhitespaceBefore(newNode);
bgneal@442 2823
bgneal@442 2824 // Change current node if the element wasn't empty i.e not <br /> or <img />
bgneal@442 2825 if (!empty)
bgneal@442 2826 node = newNode;
bgneal@442 2827 }
bgneal@442 2828 },
bgneal@442 2829
bgneal@442 2830 end: function(name) {
bgneal@442 2831 var textNode, elementRule, text, sibling, tempNode;
bgneal@442 2832
bgneal@442 2833 elementRule = validate ? schema.getElementRule(name) : {};
bgneal@442 2834 if (elementRule) {
bgneal@442 2835 if (blockElements[name]) {
bgneal@442 2836 if (!whiteSpaceElements[node.name]) {
bgneal@442 2837 // Trim whitespace at beginning of block
bgneal@442 2838 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
bgneal@442 2839 text = textNode.value.replace(startWhiteSpaceRegExp, '');
bgneal@442 2840
bgneal@442 2841 if (text.length > 0) {
bgneal@442 2842 textNode.value = text;
bgneal@442 2843 textNode = textNode.next;
bgneal@442 2844 } else {
bgneal@442 2845 sibling = textNode.next;
bgneal@442 2846 textNode.remove();
bgneal@442 2847 textNode = sibling;
bgneal@442 2848 }
bgneal@442 2849 }
bgneal@442 2850
bgneal@442 2851 // Trim whitespace at end of block
bgneal@442 2852 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
bgneal@442 2853 text = textNode.value.replace(endWhiteSpaceRegExp, '');
bgneal@442 2854
bgneal@442 2855 if (text.length > 0) {
bgneal@442 2856 textNode.value = text;
bgneal@442 2857 textNode = textNode.prev;
bgneal@442 2858 } else {
bgneal@442 2859 sibling = textNode.prev;
bgneal@442 2860 textNode.remove();
bgneal@442 2861 textNode = sibling;
bgneal@442 2862 }
bgneal@442 2863 }
bgneal@442 2864 }
bgneal@442 2865
bgneal@442 2866 // Trim start white space
bgneal@442 2867 textNode = node.prev;
bgneal@442 2868 if (textNode && textNode.type === 3) {
bgneal@442 2869 text = textNode.value.replace(startWhiteSpaceRegExp, '');
bgneal@442 2870
bgneal@442 2871 if (text.length > 0)
bgneal@442 2872 textNode.value = text;
bgneal@442 2873 else
bgneal@442 2874 textNode.remove();
bgneal@442 2875 }
bgneal@442 2876 }
bgneal@442 2877
bgneal@442 2878 // Handle empty nodes
bgneal@442 2879 if (elementRule.removeEmpty || elementRule.paddEmpty) {
bgneal@442 2880 if (node.isEmpty(nonEmptyElements)) {
bgneal@442 2881 if (elementRule.paddEmpty)
bgneal@442 2882 node.empty().append(new Node('#text', '3')).value = '\u00a0';
bgneal@442 2883 else {
bgneal@442 2884 // Leave nodes that have a name like <a name="name">
bgneal@442 2885 if (!node.attributes.map.name) {
bgneal@442 2886 tempNode = node.parent;
bgneal@442 2887 node.empty().remove();
bgneal@442 2888 node = tempNode;
bgneal@442 2889 return;
bgneal@442 2890 }
bgneal@442 2891 }
bgneal@442 2892 }
bgneal@442 2893 }
bgneal@442 2894
bgneal@442 2895 node = node.parent;
bgneal@442 2896 }
bgneal@442 2897 }
bgneal@442 2898 }, schema);
bgneal@442 2899
bgneal@442 2900 rootNode = node = new Node(settings.root_name, 11);
bgneal@442 2901
bgneal@442 2902 parser.parse(html);
bgneal@442 2903
bgneal@442 2904 if (validate)
bgneal@442 2905 fixInvalidChildren(invalidChildren);
bgneal@442 2906
bgneal@442 2907 // Run node filters
bgneal@442 2908 for (name in matchedNodes) {
bgneal@442 2909 list = nodeFilters[name];
bgneal@442 2910 nodes = matchedNodes[name];
bgneal@442 2911
bgneal@442 2912 // Remove already removed children
bgneal@442 2913 fi = nodes.length;
bgneal@442 2914 while (fi--) {
bgneal@442 2915 if (!nodes[fi].parent)
bgneal@442 2916 nodes.splice(fi, 1);
bgneal@442 2917 }
bgneal@442 2918
bgneal@442 2919 for (i = 0, l = list.length; i < l; i++)
bgneal@442 2920 list[i](nodes, name, args);
bgneal@442 2921 }
bgneal@442 2922
bgneal@442 2923 // Run attribute filters
bgneal@442 2924 for (i = 0, l = attributeFilters.length; i < l; i++) {
bgneal@442 2925 list = attributeFilters[i];
bgneal@442 2926
bgneal@442 2927 if (list.name in matchedAttributes) {
bgneal@442 2928 nodes = matchedAttributes[list.name];
bgneal@442 2929
bgneal@442 2930 // Remove already removed children
bgneal@442 2931 fi = nodes.length;
bgneal@442 2932 while (fi--) {
bgneal@442 2933 if (!nodes[fi].parent)
bgneal@442 2934 nodes.splice(fi, 1);
bgneal@442 2935 }
bgneal@442 2936
bgneal@442 2937 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
bgneal@442 2938 list.callbacks[fi](nodes, list.name, args);
bgneal@442 2939 }
bgneal@442 2940 }
bgneal@442 2941
bgneal@442 2942 return rootNode;
bgneal@442 2943 };
bgneal@442 2944
bgneal@442 2945 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
bgneal@442 2946 // make it possible to place the caret inside empty blocks. This logic tries to remove
bgneal@442 2947 // these elements and keep br elements that where intended to be there intact
bgneal@442 2948 if (settings.remove_trailing_brs) {
bgneal@442 2949 self.addNodeFilter('br', function(nodes, name) {
bgneal@442 2950 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
bgneal@442 2951 nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
bgneal@442 2952
bgneal@442 2953 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
bgneal@442 2954 for (i = 0; i < l; i++) {
bgneal@442 2955 node = nodes[i];
bgneal@442 2956 parent = node.parent;
bgneal@442 2957
bgneal@442 2958 if (blockElements[node.parent.name] && node === parent.lastChild) {
bgneal@442 2959 // Loop all nodes to the right of the current node and check for other BR elements
bgneal@442 2960 // excluding bookmarks since they are invisible
bgneal@442 2961 prev = node.prev;
bgneal@442 2962 while (prev) {
bgneal@442 2963 prevName = prev.name;
bgneal@442 2964
bgneal@442 2965 // Ignore bookmarks
bgneal@442 2966 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
bgneal@442 2967 // Found a non BR element
bgneal@442 2968 if (prevName !== "br")
bgneal@442 2969 break;
bgneal@442 2970
bgneal@442 2971 // Found another br it's a <br><br> structure then don't remove anything
bgneal@442 2972 if (prevName === 'br') {
bgneal@442 2973 node = null;
bgneal@442 2974 break;
bgneal@442 2975 }
bgneal@442 2976 }
bgneal@442 2977
bgneal@442 2978 prev = prev.prev;
bgneal@442 2979 }
bgneal@442 2980
bgneal@442 2981 if (node) {
bgneal@442 2982 node.remove();
bgneal@442 2983
bgneal@442 2984 // Is the parent to be considered empty after we removed the BR
bgneal@442 2985 if (parent.isEmpty(nonEmptyElements)) {
bgneal@442 2986 elementRule = schema.getElementRule(parent.name);
bgneal@442 2987
bgneal@442 2988 // Remove or padd the element depending on schema rule
bgneal@442 2989 if (elementRule.removeEmpty)
bgneal@442 2990 parent.remove();
bgneal@442 2991 else if (elementRule.paddEmpty)
bgneal@442 2992 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
bgneal@442 2993 }
bgneal@442 2994 }
bgneal@442 2995 }
bgneal@442 2996 }
bgneal@442 2997 });
bgneal@442 2998 }
bgneal@442 2999 }
bgneal@442 3000 })(tinymce);
bgneal@442 3001
bgneal@442 3002 tinymce.html.Writer = function(settings) {
bgneal@442 3003 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
bgneal@442 3004
bgneal@442 3005 settings = settings || {};
bgneal@442 3006 indent = settings.indent;
bgneal@442 3007 indentBefore = tinymce.makeMap(settings.indent_before || '');
bgneal@442 3008 indentAfter = tinymce.makeMap(settings.indent_after || '');
bgneal@442 3009 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
bgneal@442 3010 htmlOutput = settings.element_format == "html";
bgneal@442 3011
bgneal@442 3012 return {
bgneal@442 3013 start: function(name, attrs, empty) {
bgneal@442 3014 var i, l, attr, value;
bgneal@442 3015
bgneal@442 3016 if (indent && indentBefore[name] && html.length > 0) {
bgneal@442 3017 value = html[html.length - 1];
bgneal@442 3018
bgneal@442 3019 if (value.length > 0 && value !== '\n')
bgneal@442 3020 html.push('\n');
bgneal@442 3021 }
bgneal@442 3022
bgneal@442 3023 html.push('<', name);
bgneal@442 3024
bgneal@442 3025 if (attrs) {
bgneal@442 3026 for (i = 0, l = attrs.length; i < l; i++) {
bgneal@442 3027 attr = attrs[i];
bgneal@442 3028 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
bgneal@442 3029 }
bgneal@442 3030 }
bgneal@442 3031
bgneal@442 3032 if (!empty || htmlOutput)
bgneal@442 3033 html[html.length] = '>';
bgneal@442 3034 else
bgneal@442 3035 html[html.length] = ' />';
bgneal@442 3036
bgneal@442 3037 if (empty && indent && indentAfter[name] && html.length > 0) {
bgneal@442 3038 value = html[html.length - 1];
bgneal@442 3039
bgneal@442 3040 if (value.length > 0 && value !== '\n')
bgneal@442 3041 html.push('\n');
bgneal@442 3042 }
bgneal@442 3043 },
bgneal@442 3044
bgneal@442 3045 end: function(name) {
bgneal@442 3046 var value;
bgneal@442 3047
bgneal@442 3048 /*if (indent && indentBefore[name] && html.length > 0) {
bgneal@442 3049 value = html[html.length - 1];
bgneal@442 3050
bgneal@442 3051 if (value.length > 0 && value !== '\n')
bgneal@442 3052 html.push('\n');
bgneal@442 3053 }*/
bgneal@442 3054
bgneal@442 3055 html.push('</', name, '>');
bgneal@442 3056
bgneal@442 3057 if (indent && indentAfter[name] && html.length > 0) {
bgneal@442 3058 value = html[html.length - 1];
bgneal@442 3059
bgneal@442 3060 if (value.length > 0 && value !== '\n')
bgneal@442 3061 html.push('\n');
bgneal@442 3062 }
bgneal@442 3063 },
bgneal@442 3064
bgneal@442 3065 text: function(text, raw) {
bgneal@442 3066 if (text.length > 0)
bgneal@442 3067 html[html.length] = raw ? text : encode(text);
bgneal@442 3068 },
bgneal@442 3069
bgneal@442 3070 cdata: function(text) {
bgneal@442 3071 html.push('<![CDATA[', text, ']]>');
bgneal@442 3072 },
bgneal@442 3073
bgneal@442 3074 comment: function(text) {
bgneal@442 3075 html.push('<!--', text, '-->');
bgneal@442 3076 },
bgneal@442 3077
bgneal@442 3078 pi: function(name, text) {
bgneal@442 3079 if (text)
bgneal@442 3080 html.push('<?', name, ' ', text, '?>');
bgneal@442 3081 else
bgneal@442 3082 html.push('<?', name, '?>');
bgneal@442 3083
bgneal@442 3084 if (indent)
bgneal@442 3085 html.push('\n');
bgneal@442 3086 },
bgneal@442 3087
bgneal@442 3088 doctype: function(text) {
bgneal@442 3089 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
bgneal@442 3090 },
bgneal@442 3091
bgneal@442 3092 reset: function() {
bgneal@442 3093 html.length = 0;
bgneal@442 3094 },
bgneal@442 3095
bgneal@442 3096 getContent: function() {
bgneal@442 3097 return html.join('').replace(/\n$/, '');
bgneal@442 3098 }
bgneal@442 3099 };
bgneal@442 3100 };
bgneal@442 3101
bgneal@442 3102 (function(tinymce) {
bgneal@442 3103 tinymce.html.Serializer = function(settings, schema) {
bgneal@442 3104 var self = this, writer = new tinymce.html.Writer(settings);
bgneal@442 3105
bgneal@442 3106 settings = settings || {};
bgneal@442 3107 settings.validate = "validate" in settings ? settings.validate : true;
bgneal@442 3108
bgneal@442 3109 self.schema = schema = schema || new tinymce.html.Schema();
bgneal@442 3110 self.writer = writer;
bgneal@442 3111
bgneal@442 3112 self.serialize = function(node) {
bgneal@442 3113 var handlers, validate;
bgneal@442 3114
bgneal@442 3115 validate = settings.validate;
bgneal@442 3116
bgneal@442 3117 handlers = {
bgneal@442 3118 // #text
bgneal@442 3119 3: function(node, raw) {
bgneal@442 3120 writer.text(node.value, node.raw);
bgneal@442 3121 },
bgneal@442 3122
bgneal@442 3123 // #comment
bgneal@442 3124 8: function(node) {
bgneal@442 3125 writer.comment(node.value);
bgneal@442 3126 },
bgneal@442 3127
bgneal@442 3128 // Processing instruction
bgneal@442 3129 7: function(node) {
bgneal@442 3130 writer.pi(node.name, node.value);
bgneal@442 3131 },
bgneal@442 3132
bgneal@442 3133 // Doctype
bgneal@442 3134 10: function(node) {
bgneal@442 3135 writer.doctype(node.value);
bgneal@442 3136 },
bgneal@442 3137
bgneal@442 3138 // CDATA
bgneal@442 3139 4: function(node) {
bgneal@442 3140 writer.cdata(node.value);
bgneal@442 3141 },
bgneal@442 3142
bgneal@442 3143 // Document fragment
bgneal@442 3144 11: function(node) {
bgneal@442 3145 if ((node = node.firstChild)) {
bgneal@442 3146 do {
bgneal@442 3147 walk(node);
bgneal@442 3148 } while (node = node.next);
bgneal@442 3149 }
bgneal@442 3150 }
bgneal@442 3151 };
bgneal@442 3152
bgneal@442 3153 writer.reset();
bgneal@442 3154
bgneal@442 3155 function walk(node) {
bgneal@442 3156 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
bgneal@442 3157
bgneal@442 3158 if (!handler) {
bgneal@442 3159 name = node.name;
bgneal@442 3160 isEmpty = node.shortEnded;
bgneal@442 3161 attrs = node.attributes;
bgneal@442 3162
bgneal@442 3163 // Sort attributes
bgneal@442 3164 if (validate && attrs && attrs.length > 1) {
bgneal@442 3165 sortedAttrs = [];
bgneal@442 3166 sortedAttrs.map = {};
bgneal@442 3167
bgneal@442 3168 elementRule = schema.getElementRule(node.name);
bgneal@442 3169 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
bgneal@442 3170 attrName = elementRule.attributesOrder[i];
bgneal@442 3171
bgneal@442 3172 if (attrName in attrs.map) {
bgneal@442 3173 attrValue = attrs.map[attrName];
bgneal@442 3174 sortedAttrs.map[attrName] = attrValue;
bgneal@442 3175 sortedAttrs.push({name: attrName, value: attrValue});
bgneal@442 3176 }
bgneal@442 3177 }
bgneal@442 3178
bgneal@442 3179 for (i = 0, l = attrs.length; i < l; i++) {
bgneal@442 3180 attrName = attrs[i].name;
bgneal@442 3181
bgneal@442 3182 if (!(attrName in sortedAttrs.map)) {
bgneal@442 3183 attrValue = attrs.map[attrName];
bgneal@442 3184 sortedAttrs.map[attrName] = attrValue;
bgneal@442 3185 sortedAttrs.push({name: attrName, value: attrValue});
bgneal@442 3186 }
bgneal@442 3187 }
bgneal@442 3188
bgneal@442 3189 attrs = sortedAttrs;
bgneal@442 3190 }
bgneal@442 3191
bgneal@442 3192 writer.start(node.name, attrs, isEmpty);
bgneal@442 3193
bgneal@442 3194 if (!isEmpty) {
bgneal@442 3195 if ((node = node.firstChild)) {
bgneal@442 3196 do {
bgneal@442 3197 walk(node);
bgneal@442 3198 } while (node = node.next);
bgneal@442 3199 }
bgneal@442 3200
bgneal@442 3201 writer.end(name);
bgneal@442 3202 }
bgneal@442 3203 } else
bgneal@442 3204 handler(node);
bgneal@442 3205 }
bgneal@442 3206
bgneal@442 3207 // Serialize element and treat all non elements as fragments
bgneal@442 3208 if (node.type == 1 && !settings.inner)
bgneal@442 3209 walk(node);
bgneal@442 3210 else
bgneal@442 3211 handlers[11](node);
bgneal@442 3212
bgneal@442 3213 return writer.getContent();
bgneal@442 3214 };
bgneal@442 3215 }
bgneal@442 3216 })(tinymce);
bgneal@442 3217
bgneal@442 3218 (function(tinymce) {
bgneal@312 3219 // Shorten names
bgneal@312 3220 var each = tinymce.each,
bgneal@312 3221 is = tinymce.is,
bgneal@312 3222 isWebKit = tinymce.isWebKit,
bgneal@312 3223 isIE = tinymce.isIE,
bgneal@442 3224 Entities = tinymce.html.Entities,
bgneal@312 3225 simpleSelectorRe = /^([a-z0-9],?)+$/i,
bgneal@442 3226 blockElementsMap = tinymce.html.Schema.blockElementsMap,
bgneal@442 3227 whiteSpaceRegExp = /^[ \t\r\n]*$/;
bgneal@312 3228
bgneal@312 3229 tinymce.create('tinymce.dom.DOMUtils', {
bgneal@312 3230 doc : null,
bgneal@312 3231 root : null,
bgneal@312 3232 files : null,
bgneal@312 3233 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
bgneal@312 3234 props : {
bgneal@312 3235 "for" : "htmlFor",
bgneal@312 3236 "class" : "className",
bgneal@312 3237 className : "className",
bgneal@312 3238 checked : "checked",
bgneal@312 3239 disabled : "disabled",
bgneal@312 3240 maxlength : "maxLength",
bgneal@312 3241 readonly : "readOnly",
bgneal@312 3242 selected : "selected",
bgneal@312 3243 value : "value",
bgneal@312 3244 id : "id",
bgneal@312 3245 name : "name",
bgneal@312 3246 type : "type"
bgneal@312 3247 },
bgneal@312 3248
bgneal@312 3249 DOMUtils : function(d, s) {
bgneal@312 3250 var t = this, globalStyle;
bgneal@312 3251
bgneal@312 3252 t.doc = d;
bgneal@312 3253 t.win = window;
bgneal@312 3254 t.files = {};
bgneal@312 3255 t.cssFlicker = false;
bgneal@312 3256 t.counter = 0;
bgneal@442 3257 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
bgneal@442 3258 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
bgneal@442 3259 t.hasOuterHTML = "outerHTML" in d.createElement("a");
bgneal@312 3260
bgneal@312 3261 t.settings = s = tinymce.extend({
bgneal@312 3262 keep_values : false,
bgneal@442 3263 hex_colors : 1
bgneal@312 3264 }, s);
bgneal@442 3265
bgneal@442 3266 t.schema = s.schema;
bgneal@442 3267 t.styles = new tinymce.html.Styles({
bgneal@442 3268 url_converter : s.url_converter,
bgneal@442 3269 url_converter_scope : s.url_converter_scope
bgneal@442 3270 }, s.schema);
bgneal@312 3271
bgneal@312 3272 // Fix IE6SP2 flicker and check it failed for pre SP2
bgneal@312 3273 if (tinymce.isIE6) {
bgneal@312 3274 try {
bgneal@312 3275 d.execCommand('BackgroundImageCache', false, true);
bgneal@312 3276 } catch (e) {
bgneal@312 3277 t.cssFlicker = true;
bgneal@312 3278 }
bgneal@312 3279 }
bgneal@312 3280
bgneal@442 3281 if (isIE) {
bgneal@442 3282 // Add missing HTML 4/5 elements to IE
bgneal@442 3283 ('abbr article aside audio canvas ' +
bgneal@442 3284 'details figcaption figure footer ' +
bgneal@442 3285 'header hgroup mark menu meter nav ' +
bgneal@442 3286 'output progress section summary ' +
bgneal@442 3287 'time video').replace(/\w+/g, function(name) {
bgneal@442 3288 d.createElement(name);
bgneal@312 3289 });
bgneal@312 3290 }
bgneal@312 3291
bgneal@312 3292 tinymce.addUnload(t.destroy, t);
bgneal@312 3293 },
bgneal@312 3294
bgneal@312 3295 getRoot : function() {
bgneal@312 3296 var t = this, s = t.settings;
bgneal@312 3297
bgneal@312 3298 return (s && t.get(s.root_element)) || t.doc.body;
bgneal@312 3299 },
bgneal@312 3300
bgneal@312 3301 getViewPort : function(w) {
bgneal@312 3302 var d, b;
bgneal@312 3303
bgneal@312 3304 w = !w ? this.win : w;
bgneal@312 3305 d = w.document;
bgneal@312 3306 b = this.boxModel ? d.documentElement : d.body;
bgneal@312 3307
bgneal@312 3308 // Returns viewport size excluding scrollbars
bgneal@312 3309 return {
bgneal@312 3310 x : w.pageXOffset || b.scrollLeft,
bgneal@312 3311 y : w.pageYOffset || b.scrollTop,
bgneal@312 3312 w : w.innerWidth || b.clientWidth,
bgneal@312 3313 h : w.innerHeight || b.clientHeight
bgneal@312 3314 };
bgneal@312 3315 },
bgneal@312 3316
bgneal@312 3317 getRect : function(e) {
bgneal@312 3318 var p, t = this, sr;
bgneal@312 3319
bgneal@312 3320 e = t.get(e);
bgneal@312 3321 p = t.getPos(e);
bgneal@312 3322 sr = t.getSize(e);
bgneal@312 3323
bgneal@312 3324 return {
bgneal@312 3325 x : p.x,
bgneal@312 3326 y : p.y,
bgneal@312 3327 w : sr.w,
bgneal@312 3328 h : sr.h
bgneal@312 3329 };
bgneal@312 3330 },
bgneal@312 3331
bgneal@312 3332 getSize : function(e) {
bgneal@312 3333 var t = this, w, h;
bgneal@312 3334
bgneal@312 3335 e = t.get(e);
bgneal@312 3336 w = t.getStyle(e, 'width');
bgneal@312 3337 h = t.getStyle(e, 'height');
bgneal@312 3338
bgneal@312 3339 // Non pixel value, then force offset/clientWidth
bgneal@312 3340 if (w.indexOf('px') === -1)
bgneal@312 3341 w = 0;
bgneal@312 3342
bgneal@312 3343 // Non pixel value, then force offset/clientWidth
bgneal@312 3344 if (h.indexOf('px') === -1)
bgneal@312 3345 h = 0;
bgneal@312 3346
bgneal@312 3347 return {
bgneal@312 3348 w : parseInt(w) || e.offsetWidth || e.clientWidth,
bgneal@312 3349 h : parseInt(h) || e.offsetHeight || e.clientHeight
bgneal@312 3350 };
bgneal@312 3351 },
bgneal@312 3352
bgneal@312 3353 getParent : function(n, f, r) {
bgneal@312 3354 return this.getParents(n, f, r, false);
bgneal@312 3355 },
bgneal@312 3356
bgneal@312 3357 getParents : function(n, f, r, c) {
bgneal@312 3358 var t = this, na, se = t.settings, o = [];
bgneal@312 3359
bgneal@312 3360 n = t.get(n);
bgneal@312 3361 c = c === undefined;
bgneal@312 3362
bgneal@312 3363 if (se.strict_root)
bgneal@312 3364 r = r || t.getRoot();
bgneal@312 3365
bgneal@312 3366 // Wrap node name as func
bgneal@312 3367 if (is(f, 'string')) {
bgneal@312 3368 na = f;
bgneal@312 3369
bgneal@312 3370 if (f === '*') {
bgneal@312 3371 f = function(n) {return n.nodeType == 1;};
bgneal@312 3372 } else {
bgneal@312 3373 f = function(n) {
bgneal@312 3374 return t.is(n, na);
bgneal@312 3375 };
bgneal@312 3376 }
bgneal@312 3377 }
bgneal@312 3378
bgneal@312 3379 while (n) {
bgneal@312 3380 if (n == r || !n.nodeType || n.nodeType === 9)
bgneal@312 3381 break;
bgneal@312 3382
bgneal@312 3383 if (!f || f(n)) {
bgneal@312 3384 if (c)
bgneal@312 3385 o.push(n);
bgneal@312 3386 else
bgneal@312 3387 return n;
bgneal@312 3388 }
bgneal@312 3389
bgneal@312 3390 n = n.parentNode;
bgneal@312 3391 }
bgneal@312 3392
bgneal@312 3393 return c ? o : null;
bgneal@312 3394 },
bgneal@312 3395
bgneal@312 3396 get : function(e) {
bgneal@312 3397 var n;
bgneal@312 3398
bgneal@312 3399 if (e && this.doc && typeof(e) == 'string') {
bgneal@312 3400 n = e;
bgneal@312 3401 e = this.doc.getElementById(e);
bgneal@312 3402
bgneal@312 3403 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
bgneal@312 3404 if (e && e.id !== n)
bgneal@312 3405 return this.doc.getElementsByName(n)[1];
bgneal@312 3406 }
bgneal@312 3407
bgneal@312 3408 return e;
bgneal@312 3409 },
bgneal@312 3410
bgneal@312 3411 getNext : function(node, selector) {
bgneal@312 3412 return this._findSib(node, selector, 'nextSibling');
bgneal@312 3413 },
bgneal@312 3414
bgneal@312 3415 getPrev : function(node, selector) {
bgneal@312 3416 return this._findSib(node, selector, 'previousSibling');
bgneal@312 3417 },
bgneal@312 3418
bgneal@312 3419
bgneal@312 3420 select : function(pa, s) {
bgneal@312 3421 var t = this;
bgneal@312 3422
bgneal@312 3423 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
bgneal@312 3424 },
bgneal@312 3425
bgneal@312 3426 is : function(n, selector) {
bgneal@312 3427 var i;
bgneal@312 3428
bgneal@312 3429 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
bgneal@312 3430 if (n.length === undefined) {
bgneal@312 3431 // Simple all selector
bgneal@312 3432 if (selector === '*')
bgneal@312 3433 return n.nodeType == 1;
bgneal@312 3434
bgneal@312 3435 // Simple selector just elements
bgneal@312 3436 if (simpleSelectorRe.test(selector)) {
bgneal@312 3437 selector = selector.toLowerCase().split(/,/);
bgneal@312 3438 n = n.nodeName.toLowerCase();
bgneal@312 3439
bgneal@312 3440 for (i = selector.length - 1; i >= 0; i--) {
bgneal@312 3441 if (selector[i] == n)
bgneal@312 3442 return true;
bgneal@312 3443 }
bgneal@312 3444
bgneal@312 3445 return false;
bgneal@312 3446 }
bgneal@312 3447 }
bgneal@312 3448
bgneal@312 3449 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
bgneal@312 3450 },
bgneal@312 3451
bgneal@312 3452
bgneal@312 3453 add : function(p, n, a, h, c) {
bgneal@312 3454 var t = this;
bgneal@312 3455
bgneal@312 3456 return this.run(p, function(p) {
bgneal@312 3457 var e, k;
bgneal@312 3458
bgneal@312 3459 e = is(n, 'string') ? t.doc.createElement(n) : n;
bgneal@312 3460 t.setAttribs(e, a);
bgneal@312 3461
bgneal@312 3462 if (h) {
bgneal@312 3463 if (h.nodeType)
bgneal@312 3464 e.appendChild(h);
bgneal@312 3465 else
bgneal@312 3466 t.setHTML(e, h);
bgneal@312 3467 }
bgneal@312 3468
bgneal@312 3469 return !c ? p.appendChild(e) : e;
bgneal@312 3470 });
bgneal@312 3471 },
bgneal@312 3472
bgneal@312 3473 create : function(n, a, h) {
bgneal@312 3474 return this.add(this.doc.createElement(n), n, a, h, 1);
bgneal@312 3475 },
bgneal@312 3476
bgneal@312 3477 createHTML : function(n, a, h) {
bgneal@312 3478 var o = '', t = this, k;
bgneal@312 3479
bgneal@312 3480 o += '<' + n;
bgneal@312 3481
bgneal@312 3482 for (k in a) {
bgneal@312 3483 if (a.hasOwnProperty(k))
bgneal@312 3484 o += ' ' + k + '="' + t.encode(a[k]) + '"';
bgneal@312 3485 }
bgneal@312 3486
bgneal@442 3487 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
bgneal@442 3488 if (typeof(h) != "undefined")
bgneal@312 3489 return o + '>' + h + '</' + n + '>';
bgneal@312 3490
bgneal@312 3491 return o + ' />';
bgneal@312 3492 },
bgneal@312 3493
bgneal@312 3494 remove : function(node, keep_children) {
bgneal@312 3495 return this.run(node, function(node) {
bgneal@442 3496 var child, parent = node.parentNode;
bgneal@312 3497
bgneal@312 3498 if (!parent)
bgneal@312 3499 return null;
bgneal@312 3500
bgneal@312 3501 if (keep_children) {
bgneal@312 3502 while (child = node.firstChild) {
bgneal@312 3503 // IE 8 will crash if you don't remove completely empty text nodes
bgneal@312 3504 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
bgneal@312 3505 parent.insertBefore(child, node);
bgneal@312 3506 else
bgneal@312 3507 node.removeChild(child);
bgneal@312 3508 }
bgneal@312 3509 }
bgneal@312 3510
bgneal@312 3511 return parent.removeChild(node);
bgneal@312 3512 });
bgneal@312 3513 },
bgneal@312 3514
bgneal@312 3515 setStyle : function(n, na, v) {
bgneal@312 3516 var t = this;
bgneal@312 3517
bgneal@312 3518 return t.run(n, function(e) {
bgneal@312 3519 var s, i;
bgneal@312 3520
bgneal@312 3521 s = e.style;
bgneal@312 3522
bgneal@312 3523 // Camelcase it, if needed
bgneal@312 3524 na = na.replace(/-(\D)/g, function(a, b){
bgneal@312 3525 return b.toUpperCase();
bgneal@312 3526 });
bgneal@312 3527
bgneal@312 3528 // Default px suffix on these
bgneal@312 3529 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
bgneal@312 3530 v += 'px';
bgneal@312 3531
bgneal@312 3532 switch (na) {
bgneal@312 3533 case 'opacity':
bgneal@312 3534 // IE specific opacity
bgneal@312 3535 if (isIE) {
bgneal@312 3536 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
bgneal@312 3537
bgneal@312 3538 if (!n.currentStyle || !n.currentStyle.hasLayout)
bgneal@312 3539 s.display = 'inline-block';
bgneal@312 3540 }
bgneal@312 3541
bgneal@312 3542 // Fix for older browsers
bgneal@312 3543 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
bgneal@312 3544 break;
bgneal@312 3545
bgneal@312 3546 case 'float':
bgneal@312 3547 isIE ? s.styleFloat = v : s.cssFloat = v;
bgneal@312 3548 break;
bgneal@312 3549
bgneal@312 3550 default:
bgneal@312 3551 s[na] = v || '';
bgneal@312 3552 }
bgneal@312 3553
bgneal@312 3554 // Force update of the style data
bgneal@312 3555 if (t.settings.update_styles)
bgneal@442 3556 t.setAttrib(e, 'data-mce-style');
bgneal@312 3557 });
bgneal@312 3558 },
bgneal@312 3559
bgneal@312 3560 getStyle : function(n, na, c) {
bgneal@312 3561 n = this.get(n);
bgneal@312 3562
bgneal@312 3563 if (!n)
bgneal@442 3564 return;
bgneal@312 3565
bgneal@312 3566 // Gecko
bgneal@312 3567 if (this.doc.defaultView && c) {
bgneal@312 3568 // Remove camelcase
bgneal@312 3569 na = na.replace(/[A-Z]/g, function(a){
bgneal@312 3570 return '-' + a;
bgneal@312 3571 });
bgneal@312 3572
bgneal@312 3573 try {
bgneal@312 3574 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
bgneal@312 3575 } catch (ex) {
bgneal@312 3576 // Old safari might fail
bgneal@312 3577 return null;
bgneal@312 3578 }
bgneal@312 3579 }
bgneal@312 3580
bgneal@312 3581 // Camelcase it, if needed
bgneal@312 3582 na = na.replace(/-(\D)/g, function(a, b){
bgneal@312 3583 return b.toUpperCase();
bgneal@312 3584 });
bgneal@312 3585
bgneal@312 3586 if (na == 'float')
bgneal@312 3587 na = isIE ? 'styleFloat' : 'cssFloat';
bgneal@312 3588
bgneal@312 3589 // IE & Opera
bgneal@312 3590 if (n.currentStyle && c)
bgneal@312 3591 return n.currentStyle[na];
bgneal@312 3592
bgneal@442 3593 return n.style ? n.style[na] : undefined;
bgneal@312 3594 },
bgneal@312 3595
bgneal@312 3596 setStyles : function(e, o) {
bgneal@312 3597 var t = this, s = t.settings, ol;
bgneal@312 3598
bgneal@312 3599 ol = s.update_styles;
bgneal@312 3600 s.update_styles = 0;
bgneal@312 3601
bgneal@312 3602 each(o, function(v, n) {
bgneal@312 3603 t.setStyle(e, n, v);
bgneal@312 3604 });
bgneal@312 3605
bgneal@312 3606 // Update style info
bgneal@312 3607 s.update_styles = ol;
bgneal@312 3608 if (s.update_styles)
bgneal@312 3609 t.setAttrib(e, s.cssText);
bgneal@312 3610 },
bgneal@312 3611
bgneal@442 3612 removeAllAttribs: function(e) {
bgneal@442 3613 return this.run(e, function(e) {
bgneal@442 3614 var i, attrs = e.attributes;
bgneal@442 3615 for (i = attrs.length - 1; i >= 0; i--) {
bgneal@442 3616 e.removeAttributeNode(attrs.item(i));
bgneal@442 3617 }
bgneal@442 3618 });
bgneal@442 3619 },
bgneal@442 3620
bgneal@312 3621 setAttrib : function(e, n, v) {
bgneal@312 3622 var t = this;
bgneal@312 3623
bgneal@312 3624 // Whats the point
bgneal@312 3625 if (!e || !n)
bgneal@312 3626 return;
bgneal@312 3627
bgneal@312 3628 // Strict XML mode
bgneal@312 3629 if (t.settings.strict)
bgneal@312 3630 n = n.toLowerCase();
bgneal@312 3631
bgneal@312 3632 return this.run(e, function(e) {
bgneal@312 3633 var s = t.settings;
bgneal@312 3634
bgneal@312 3635 switch (n) {
bgneal@312 3636 case "style":
bgneal@312 3637 if (!is(v, 'string')) {
bgneal@312 3638 each(v, function(v, n) {
bgneal@312 3639 t.setStyle(e, n, v);
bgneal@312 3640 });
bgneal@312 3641
bgneal@312 3642 return;
bgneal@312 3643 }
bgneal@312 3644
bgneal@312 3645 // No mce_style for elements with these since they might get resized by the user
bgneal@312 3646 if (s.keep_values) {
bgneal@312 3647 if (v && !t._isRes(v))
bgneal@442 3648 e.setAttribute('data-mce-style', v, 2);
bgneal@312 3649 else
bgneal@442 3650 e.removeAttribute('data-mce-style', 2);
bgneal@312 3651 }
bgneal@312 3652
bgneal@312 3653 e.style.cssText = v;
bgneal@312 3654 break;
bgneal@312 3655
bgneal@312 3656 case "class":
bgneal@312 3657 e.className = v || ''; // Fix IE null bug
bgneal@312 3658 break;
bgneal@312 3659
bgneal@312 3660 case "src":
bgneal@312 3661 case "href":
bgneal@312 3662 if (s.keep_values) {
bgneal@312 3663 if (s.url_converter)
bgneal@312 3664 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
bgneal@312 3665
bgneal@442 3666 t.setAttrib(e, 'data-mce-' + n, v, 2);
bgneal@312 3667 }
bgneal@312 3668
bgneal@312 3669 break;
bgneal@442 3670
bgneal@312 3671 case "shape":
bgneal@442 3672 e.setAttribute('data-mce-style', v);
bgneal@312 3673 break;
bgneal@312 3674 }
bgneal@312 3675
bgneal@312 3676 if (is(v) && v !== null && v.length !== 0)
bgneal@312 3677 e.setAttribute(n, '' + v, 2);
bgneal@312 3678 else
bgneal@312 3679 e.removeAttribute(n, 2);
bgneal@312 3680 });
bgneal@312 3681 },
bgneal@312 3682
bgneal@312 3683 setAttribs : function(e, o) {
bgneal@312 3684 var t = this;
bgneal@312 3685
bgneal@312 3686 return this.run(e, function(e) {
bgneal@312 3687 each(o, function(v, n) {
bgneal@312 3688 t.setAttrib(e, n, v);
bgneal@312 3689 });
bgneal@312 3690 });
bgneal@312 3691 },
bgneal@312 3692
bgneal@312 3693 getAttrib : function(e, n, dv) {
bgneal@312 3694 var v, t = this;
bgneal@312 3695
bgneal@312 3696 e = t.get(e);
bgneal@312 3697
bgneal@312 3698 if (!e || e.nodeType !== 1)
bgneal@312 3699 return false;
bgneal@312 3700
bgneal@312 3701 if (!is(dv))
bgneal@312 3702 dv = '';
bgneal@312 3703
bgneal@312 3704 // Try the mce variant for these
bgneal@312 3705 if (/^(src|href|style|coords|shape)$/.test(n)) {
bgneal@442 3706 v = e.getAttribute("data-mce-" + n);
bgneal@312 3707
bgneal@312 3708 if (v)
bgneal@312 3709 return v;
bgneal@312 3710 }
bgneal@312 3711
bgneal@312 3712 if (isIE && t.props[n]) {
bgneal@312 3713 v = e[t.props[n]];
bgneal@312 3714 v = v && v.nodeValue ? v.nodeValue : v;
bgneal@312 3715 }
bgneal@312 3716
bgneal@312 3717 if (!v)
bgneal@312 3718 v = e.getAttribute(n, 2);
bgneal@312 3719
bgneal@312 3720 // Check boolean attribs
bgneal@312 3721 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
bgneal@312 3722 if (e[t.props[n]] === true && v === '')
bgneal@312 3723 return n;
bgneal@312 3724
bgneal@312 3725 return v ? n : '';
bgneal@312 3726 }
bgneal@312 3727
bgneal@312 3728 // Inner input elements will override attributes on form elements
bgneal@312 3729 if (e.nodeName === "FORM" && e.getAttributeNode(n))
bgneal@312 3730 return e.getAttributeNode(n).nodeValue;
bgneal@312 3731
bgneal@312 3732 if (n === 'style') {
bgneal@312 3733 v = v || e.style.cssText;
bgneal@312 3734
bgneal@312 3735 if (v) {
bgneal@312 3736 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
bgneal@312 3737
bgneal@312 3738 if (t.settings.keep_values && !t._isRes(v))
bgneal@442 3739 e.setAttribute('data-mce-style', v);
bgneal@312 3740 }
bgneal@312 3741 }
bgneal@312 3742
bgneal@312 3743 // Remove Apple and WebKit stuff
bgneal@312 3744 if (isWebKit && n === "class" && v)
bgneal@312 3745 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
bgneal@312 3746
bgneal@312 3747 // Handle IE issues
bgneal@312 3748 if (isIE) {
bgneal@312 3749 switch (n) {
bgneal@312 3750 case 'rowspan':
bgneal@312 3751 case 'colspan':
bgneal@312 3752 // IE returns 1 as default value
bgneal@312 3753 if (v === 1)
bgneal@312 3754 v = '';
bgneal@312 3755
bgneal@312 3756 break;
bgneal@312 3757
bgneal@312 3758 case 'size':
bgneal@312 3759 // IE returns +0 as default value for size
bgneal@312 3760 if (v === '+0' || v === 20 || v === 0)
bgneal@312 3761 v = '';
bgneal@312 3762
bgneal@312 3763 break;
bgneal@312 3764
bgneal@312 3765 case 'width':
bgneal@312 3766 case 'height':
bgneal@312 3767 case 'vspace':
bgneal@312 3768 case 'checked':
bgneal@312 3769 case 'disabled':
bgneal@312 3770 case 'readonly':
bgneal@312 3771 if (v === 0)
bgneal@312 3772 v = '';
bgneal@312 3773
bgneal@312 3774 break;
bgneal@312 3775
bgneal@312 3776 case 'hspace':
bgneal@312 3777 // IE returns -1 as default value
bgneal@312 3778 if (v === -1)
bgneal@312 3779 v = '';
bgneal@312 3780
bgneal@312 3781 break;
bgneal@312 3782
bgneal@312 3783 case 'maxlength':
bgneal@312 3784 case 'tabindex':
bgneal@312 3785 // IE returns default value
bgneal@312 3786 if (v === 32768 || v === 2147483647 || v === '32768')
bgneal@312 3787 v = '';
bgneal@312 3788
bgneal@312 3789 break;
bgneal@312 3790
bgneal@312 3791 case 'multiple':
bgneal@312 3792 case 'compact':
bgneal@312 3793 case 'noshade':
bgneal@312 3794 case 'nowrap':
bgneal@312 3795 if (v === 65535)
bgneal@312 3796 return n;
bgneal@312 3797
bgneal@312 3798 return dv;
bgneal@312 3799
bgneal@312 3800 case 'shape':
bgneal@312 3801 v = v.toLowerCase();
bgneal@312 3802 break;
bgneal@312 3803
bgneal@312 3804 default:
bgneal@312 3805 // IE has odd anonymous function for event attributes
bgneal@312 3806 if (n.indexOf('on') === 0 && v)
bgneal@442 3807 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
bgneal@312 3808 }
bgneal@312 3809 }
bgneal@312 3810
bgneal@312 3811 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
bgneal@312 3812 },
bgneal@312 3813
bgneal@312 3814 getPos : function(n, ro) {
bgneal@312 3815 var t = this, x = 0, y = 0, e, d = t.doc, r;
bgneal@312 3816
bgneal@312 3817 n = t.get(n);
bgneal@312 3818 ro = ro || d.body;
bgneal@312 3819
bgneal@312 3820 if (n) {
bgneal@312 3821 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
bgneal@312 3822 if (isIE && !t.stdMode) {
bgneal@312 3823 n = n.getBoundingClientRect();
bgneal@312 3824 e = t.boxModel ? d.documentElement : d.body;
bgneal@312 3825 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
bgneal@312 3826 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
bgneal@312 3827
bgneal@312 3828 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
bgneal@312 3829 }
bgneal@312 3830
bgneal@312 3831 r = n;
bgneal@312 3832 while (r && r != ro && r.nodeType) {
bgneal@312 3833 x += r.offsetLeft || 0;
bgneal@312 3834 y += r.offsetTop || 0;
bgneal@312 3835 r = r.offsetParent;
bgneal@312 3836 }
bgneal@312 3837
bgneal@312 3838 r = n.parentNode;
bgneal@312 3839 while (r && r != ro && r.nodeType) {
bgneal@312 3840 x -= r.scrollLeft || 0;
bgneal@312 3841 y -= r.scrollTop || 0;
bgneal@312 3842 r = r.parentNode;
bgneal@312 3843 }
bgneal@312 3844 }
bgneal@312 3845
bgneal@312 3846 return {x : x, y : y};
bgneal@312 3847 },
bgneal@312 3848
bgneal@312 3849 parseStyle : function(st) {
bgneal@442 3850 return this.styles.parse(st);
bgneal@312 3851 },
bgneal@312 3852
bgneal@312 3853 serializeStyle : function(o, name) {
bgneal@442 3854 return this.styles.serialize(o, name);
bgneal@312 3855 },
bgneal@312 3856
bgneal@312 3857 loadCSS : function(u) {
bgneal@312 3858 var t = this, d = t.doc, head;
bgneal@312 3859
bgneal@312 3860 if (!u)
bgneal@312 3861 u = '';
bgneal@312 3862
bgneal@312 3863 head = t.select('head')[0];
bgneal@312 3864
bgneal@312 3865 each(u.split(','), function(u) {
bgneal@312 3866 var link;
bgneal@312 3867
bgneal@312 3868 if (t.files[u])
bgneal@312 3869 return;
bgneal@312 3870
bgneal@312 3871 t.files[u] = true;
bgneal@312 3872 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
bgneal@312 3873
bgneal@312 3874 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
bgneal@312 3875 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
bgneal@312 3876 // It's ugly but it seems to work fine.
bgneal@442 3877 if (isIE && d.documentMode && d.recalc) {
bgneal@312 3878 link.onload = function() {
bgneal@442 3879 if (d.recalc)
bgneal@442 3880 d.recalc();
bgneal@442 3881
bgneal@312 3882 link.onload = null;
bgneal@312 3883 };
bgneal@312 3884 }
bgneal@312 3885
bgneal@312 3886 head.appendChild(link);
bgneal@312 3887 });
bgneal@312 3888 },
bgneal@312 3889
bgneal@312 3890 addClass : function(e, c) {
bgneal@312 3891 return this.run(e, function(e) {
bgneal@312 3892 var o;
bgneal@312 3893
bgneal@312 3894 if (!c)
bgneal@312 3895 return 0;
bgneal@312 3896
bgneal@312 3897 if (this.hasClass(e, c))
bgneal@312 3898 return e.className;
bgneal@312 3899
bgneal@312 3900 o = this.removeClass(e, c);
bgneal@312 3901
bgneal@312 3902 return e.className = (o != '' ? (o + ' ') : '') + c;
bgneal@312 3903 });
bgneal@312 3904 },
bgneal@312 3905
bgneal@312 3906 removeClass : function(e, c) {
bgneal@312 3907 var t = this, re;
bgneal@312 3908
bgneal@312 3909 return t.run(e, function(e) {
bgneal@312 3910 var v;
bgneal@312 3911
bgneal@312 3912 if (t.hasClass(e, c)) {
bgneal@312 3913 if (!re)
bgneal@312 3914 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
bgneal@312 3915
bgneal@312 3916 v = e.className.replace(re, ' ');
bgneal@312 3917 v = tinymce.trim(v != ' ' ? v : '');
bgneal@312 3918
bgneal@312 3919 e.className = v;
bgneal@312 3920
bgneal@312 3921 // Empty class attr
bgneal@312 3922 if (!v) {
bgneal@312 3923 e.removeAttribute('class');
bgneal@312 3924 e.removeAttribute('className');
bgneal@312 3925 }
bgneal@312 3926
bgneal@312 3927 return v;
bgneal@312 3928 }
bgneal@312 3929
bgneal@312 3930 return e.className;
bgneal@312 3931 });
bgneal@312 3932 },
bgneal@312 3933
bgneal@312 3934 hasClass : function(n, c) {
bgneal@312 3935 n = this.get(n);
bgneal@312 3936
bgneal@312 3937 if (!n || !c)
bgneal@312 3938 return false;
bgneal@312 3939
bgneal@312 3940 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
bgneal@312 3941 },
bgneal@312 3942
bgneal@312 3943 show : function(e) {
bgneal@312 3944 return this.setStyle(e, 'display', 'block');
bgneal@312 3945 },
bgneal@312 3946
bgneal@312 3947 hide : function(e) {
bgneal@312 3948 return this.setStyle(e, 'display', 'none');
bgneal@312 3949 },
bgneal@312 3950
bgneal@312 3951 isHidden : function(e) {
bgneal@312 3952 e = this.get(e);
bgneal@312 3953
bgneal@312 3954 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
bgneal@312 3955 },
bgneal@312 3956
bgneal@312 3957 uniqueId : function(p) {
bgneal@312 3958 return (!p ? 'mce_' : p) + (this.counter++);
bgneal@312 3959 },
bgneal@312 3960
bgneal@442 3961 setHTML : function(element, html) {
bgneal@442 3962 var self = this;
bgneal@442 3963
bgneal@442 3964 return self.run(element, function(element) {
bgneal@312 3965 if (isIE) {
bgneal@442 3966 // Remove all child nodes, IE keeps empty text nodes in DOM
bgneal@442 3967 while (element.firstChild)
bgneal@442 3968 element.removeChild(element.firstChild);
bgneal@442 3969
bgneal@442 3970 try {
bgneal@442 3971 // IE will remove comments from the beginning
bgneal@442 3972 // unless you padd the contents with something
bgneal@442 3973 element.innerHTML = '<br />' + html;
bgneal@442 3974 element.removeChild(element.firstChild);
bgneal@442 3975 } catch (ex) {
bgneal@442 3976 // 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@442 3977 // This seems to fix this problem
bgneal@442 3978
bgneal@442 3979 // Create new div with HTML contents and a BR infront to keep comments
bgneal@442 3980 element = self.create('div');
bgneal@442 3981 element.innerHTML = '<br />' + html;
bgneal@442 3982
bgneal@442 3983 // Add all children from div to target
bgneal@442 3984 each (element.childNodes, function(node, i) {
bgneal@442 3985 // Skip br element
bgneal@442 3986 if (i)
bgneal@442 3987 element.appendChild(node);
bgneal@442 3988 });
bgneal@312 3989 }
bgneal@312 3990 } else
bgneal@442 3991 element.innerHTML = html;
bgneal@442 3992
bgneal@442 3993 return html;
bgneal@442 3994 });
bgneal@442 3995 },
bgneal@442 3996
bgneal@442 3997 getOuterHTML : function(elm) {
bgneal@442 3998 var doc, self = this;
bgneal@442 3999
bgneal@442 4000 elm = self.get(elm);
bgneal@442 4001
bgneal@442 4002 if (!elm)
bgneal@312 4003 return null;
bgneal@312 4004
bgneal@442 4005 if (elm.nodeType === 1 && self.hasOuterHTML)
bgneal@442 4006 return elm.outerHTML;
bgneal@442 4007
bgneal@442 4008 doc = (elm.ownerDocument || self.doc).createElement("body");
bgneal@442 4009 doc.appendChild(elm.cloneNode(true));
bgneal@442 4010
bgneal@442 4011 return doc.innerHTML;
bgneal@312 4012 },
bgneal@312 4013
bgneal@312 4014 setOuterHTML : function(e, h, d) {
bgneal@312 4015 var t = this;
bgneal@312 4016
bgneal@312 4017 function setHTML(e, h, d) {
bgneal@312 4018 var n, tp;
bgneal@312 4019
bgneal@312 4020 tp = d.createElement("body");
bgneal@312 4021 tp.innerHTML = h;
bgneal@312 4022
bgneal@312 4023 n = tp.lastChild;
bgneal@312 4024 while (n) {
bgneal@312 4025 t.insertAfter(n.cloneNode(true), e);
bgneal@312 4026 n = n.previousSibling;
bgneal@312 4027 }
bgneal@312 4028
bgneal@312 4029 t.remove(e);
bgneal@312 4030 };
bgneal@312 4031
bgneal@312 4032 return this.run(e, function(e) {
bgneal@312 4033 e = t.get(e);
bgneal@312 4034
bgneal@312 4035 // Only set HTML on elements
bgneal@312 4036 if (e.nodeType == 1) {
bgneal@312 4037 d = d || e.ownerDocument || t.doc;
bgneal@312 4038
bgneal@312 4039 if (isIE) {
bgneal@312 4040 try {
bgneal@312 4041 // Try outerHTML for IE it sometimes produces an unknown runtime error
bgneal@312 4042 if (isIE && e.nodeType == 1)
bgneal@312 4043 e.outerHTML = h;
bgneal@312 4044 else
bgneal@312 4045 setHTML(e, h, d);
bgneal@312 4046 } catch (ex) {
bgneal@312 4047 // Fix for unknown runtime error
bgneal@312 4048 setHTML(e, h, d);
bgneal@312 4049 }
bgneal@312 4050 } else
bgneal@312 4051 setHTML(e, h, d);
bgneal@312 4052 }
bgneal@312 4053 });
bgneal@312 4054 },
bgneal@312 4055
bgneal@442 4056 decode : Entities.decode,
bgneal@442 4057
bgneal@442 4058 encode : Entities.encodeAllRaw,
bgneal@312 4059
bgneal@312 4060 insertAfter : function(node, reference_node) {
bgneal@312 4061 reference_node = this.get(reference_node);
bgneal@312 4062
bgneal@312 4063 return this.run(node, function(node) {
bgneal@312 4064 var parent, nextSibling;
bgneal@312 4065
bgneal@312 4066 parent = reference_node.parentNode;
bgneal@312 4067 nextSibling = reference_node.nextSibling;
bgneal@312 4068
bgneal@312 4069 if (nextSibling)
bgneal@312 4070 parent.insertBefore(node, nextSibling);
bgneal@312 4071 else
bgneal@312 4072 parent.appendChild(node);
bgneal@312 4073
bgneal@312 4074 return node;
bgneal@312 4075 });
bgneal@312 4076 },
bgneal@312 4077
bgneal@442 4078 isBlock : function(node) {
bgneal@442 4079 var type = node.nodeType;
bgneal@442 4080
bgneal@442 4081 // If it's a node then check the type and use the nodeName
bgneal@442 4082 if (type)
bgneal@442 4083 return !!(type === 1 && blockElementsMap[node.nodeName]);
bgneal@442 4084
bgneal@442 4085 return !!blockElementsMap[node];
bgneal@312 4086 },
bgneal@312 4087
bgneal@312 4088 replace : function(n, o, k) {
bgneal@312 4089 var t = this;
bgneal@312 4090
bgneal@312 4091 if (is(o, 'array'))
bgneal@312 4092 n = n.cloneNode(true);
bgneal@312 4093
bgneal@312 4094 return t.run(o, function(o) {
bgneal@312 4095 if (k) {
bgneal@312 4096 each(tinymce.grep(o.childNodes), function(c) {
bgneal@312 4097 n.appendChild(c);
bgneal@312 4098 });
bgneal@312 4099 }
bgneal@312 4100
bgneal@312 4101 return o.parentNode.replaceChild(n, o);
bgneal@312 4102 });
bgneal@312 4103 },
bgneal@312 4104
bgneal@312 4105 rename : function(elm, name) {
bgneal@312 4106 var t = this, newElm;
bgneal@312 4107
bgneal@312 4108 if (elm.nodeName != name.toUpperCase()) {
bgneal@312 4109 // Rename block element
bgneal@312 4110 newElm = t.create(name);
bgneal@312 4111
bgneal@312 4112 // Copy attribs to new block
bgneal@312 4113 each(t.getAttribs(elm), function(attr_node) {
bgneal@312 4114 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
bgneal@312 4115 });
bgneal@312 4116
bgneal@312 4117 // Replace block
bgneal@312 4118 t.replace(newElm, elm, 1);
bgneal@312 4119 }
bgneal@312 4120
bgneal@312 4121 return newElm || elm;
bgneal@312 4122 },
bgneal@312 4123
bgneal@312 4124 findCommonAncestor : function(a, b) {
bgneal@312 4125 var ps = a, pe;
bgneal@312 4126
bgneal@312 4127 while (ps) {
bgneal@312 4128 pe = b;
bgneal@312 4129
bgneal@312 4130 while (pe && ps != pe)
bgneal@312 4131 pe = pe.parentNode;
bgneal@312 4132
bgneal@312 4133 if (ps == pe)
bgneal@312 4134 break;
bgneal@312 4135
bgneal@312 4136 ps = ps.parentNode;
bgneal@312 4137 }
bgneal@312 4138
bgneal@312 4139 if (!ps && a.ownerDocument)
bgneal@312 4140 return a.ownerDocument.documentElement;
bgneal@312 4141
bgneal@312 4142 return ps;
bgneal@312 4143 },
bgneal@312 4144
bgneal@312 4145 toHex : function(s) {
bgneal@312 4146 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
bgneal@312 4147
bgneal@312 4148 function hex(s) {
bgneal@312 4149 s = parseInt(s).toString(16);
bgneal@312 4150
bgneal@312 4151 return s.length > 1 ? s : '0' + s; // 0 -> 00
bgneal@312 4152 };
bgneal@312 4153
bgneal@312 4154 if (c) {
bgneal@312 4155 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
bgneal@312 4156
bgneal@312 4157 return s;
bgneal@312 4158 }
bgneal@312 4159
bgneal@312 4160 return s;
bgneal@312 4161 },
bgneal@312 4162
bgneal@312 4163 getClasses : function() {
bgneal@312 4164 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
bgneal@312 4165
bgneal@312 4166 if (t.classes)
bgneal@312 4167 return t.classes;
bgneal@312 4168
bgneal@312 4169 function addClasses(s) {
bgneal@312 4170 // IE style imports
bgneal@312 4171 each(s.imports, function(r) {
bgneal@312 4172 addClasses(r);
bgneal@312 4173 });
bgneal@312 4174
bgneal@312 4175 each(s.cssRules || s.rules, function(r) {
bgneal@312 4176 // Real type or fake it on IE
bgneal@312 4177 switch (r.type || 1) {
bgneal@312 4178 // Rule
bgneal@312 4179 case 1:
bgneal@312 4180 if (r.selectorText) {
bgneal@312 4181 each(r.selectorText.split(','), function(v) {
bgneal@312 4182 v = v.replace(/^\s*|\s*$|^\s\./g, "");
bgneal@312 4183
bgneal@312 4184 // Is internal or it doesn't contain a class
bgneal@312 4185 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
bgneal@312 4186 return;
bgneal@312 4187
bgneal@312 4188 // Remove everything but class name
bgneal@312 4189 ov = v;
bgneal@442 4190 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
bgneal@312 4191
bgneal@312 4192 // Filter classes
bgneal@312 4193 if (f && !(v = f(v, ov)))
bgneal@312 4194 return;
bgneal@312 4195
bgneal@312 4196 if (!lo[v]) {
bgneal@312 4197 cl.push({'class' : v});
bgneal@312 4198 lo[v] = 1;
bgneal@312 4199 }
bgneal@312 4200 });
bgneal@312 4201 }
bgneal@312 4202 break;
bgneal@312 4203
bgneal@312 4204 // Import
bgneal@312 4205 case 3:
bgneal@312 4206 addClasses(r.styleSheet);
bgneal@312 4207 break;
bgneal@312 4208 }
bgneal@312 4209 });
bgneal@312 4210 };
bgneal@312 4211
bgneal@312 4212 try {
bgneal@312 4213 each(t.doc.styleSheets, addClasses);
bgneal@312 4214 } catch (ex) {
bgneal@312 4215 // Ignore
bgneal@312 4216 }
bgneal@312 4217
bgneal@312 4218 if (cl.length > 0)
bgneal@312 4219 t.classes = cl;
bgneal@312 4220
bgneal@312 4221 return cl;
bgneal@312 4222 },
bgneal@312 4223
bgneal@312 4224 run : function(e, f, s) {
bgneal@312 4225 var t = this, o;
bgneal@312 4226
bgneal@312 4227 if (t.doc && typeof(e) === 'string')
bgneal@312 4228 e = t.get(e);
bgneal@312 4229
bgneal@312 4230 if (!e)
bgneal@312 4231 return false;
bgneal@312 4232
bgneal@312 4233 s = s || this;
bgneal@312 4234 if (!e.nodeType && (e.length || e.length === 0)) {
bgneal@312 4235 o = [];
bgneal@312 4236
bgneal@312 4237 each(e, function(e, i) {
bgneal@312 4238 if (e) {
bgneal@312 4239 if (typeof(e) == 'string')
bgneal@312 4240 e = t.doc.getElementById(e);
bgneal@312 4241
bgneal@312 4242 o.push(f.call(s, e, i));
bgneal@312 4243 }
bgneal@312 4244 });
bgneal@312 4245
bgneal@312 4246 return o;
bgneal@312 4247 }
bgneal@312 4248
bgneal@312 4249 return f.call(s, e);
bgneal@312 4250 },
bgneal@312 4251
bgneal@312 4252 getAttribs : function(n) {
bgneal@312 4253 var o;
bgneal@312 4254
bgneal@312 4255 n = this.get(n);
bgneal@312 4256
bgneal@312 4257 if (!n)
bgneal@312 4258 return [];
bgneal@312 4259
bgneal@312 4260 if (isIE) {
bgneal@312 4261 o = [];
bgneal@312 4262
bgneal@312 4263 // Object will throw exception in IE
bgneal@312 4264 if (n.nodeName == 'OBJECT')
bgneal@312 4265 return n.attributes;
bgneal@312 4266
bgneal@312 4267 // IE doesn't keep the selected attribute if you clone option elements
bgneal@312 4268 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
bgneal@312 4269 o.push({specified : 1, nodeName : 'selected'});
bgneal@312 4270
bgneal@312 4271 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
bgneal@312 4272 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
bgneal@312 4273 o.push({specified : 1, nodeName : a});
bgneal@312 4274 });
bgneal@312 4275
bgneal@312 4276 return o;
bgneal@312 4277 }
bgneal@312 4278
bgneal@312 4279 return n.attributes;
bgneal@312 4280 },
bgneal@312 4281
bgneal@442 4282 isEmpty : function(node, elements) {
bgneal@442 4283 var self = this, i, attributes, type, walker, name;
bgneal@442 4284
bgneal@442 4285 node = node.firstChild;
bgneal@442 4286 if (node) {
bgneal@442 4287 walker = new tinymce.dom.TreeWalker(node);
bgneal@442 4288 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
bgneal@442 4289
bgneal@442 4290 do {
bgneal@442 4291 type = node.nodeType;
bgneal@442 4292
bgneal@442 4293 if (type === 1) {
bgneal@442 4294 // Ignore bogus elements
bgneal@442 4295 if (node.getAttribute('data-mce-bogus'))
bgneal@442 4296 continue;
bgneal@442 4297
bgneal@442 4298 // Keep empty elements like <img />
bgneal@442 4299 if (elements && elements[node.nodeName.toLowerCase()])
bgneal@442 4300 return false;
bgneal@442 4301
bgneal@442 4302 // Keep elements with data attributes or name attribute like <a name="1"></a>
bgneal@442 4303 attributes = self.getAttribs(node);
bgneal@442 4304 i = node.attributes.length;
bgneal@442 4305 while (i--) {
bgneal@442 4306 name = node.attributes[i].nodeName;
bgneal@442 4307 if (name === "name" || name.indexOf('data-') === 0)
bgneal@442 4308 return false;
bgneal@442 4309 }
bgneal@442 4310 }
bgneal@442 4311
bgneal@442 4312 // Keep non whitespace text nodes
bgneal@442 4313 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
bgneal@442 4314 return false;
bgneal@442 4315 } while (node = walker.next());
bgneal@442 4316 }
bgneal@442 4317
bgneal@442 4318 return true;
bgneal@442 4319 },
bgneal@442 4320
bgneal@312 4321 destroy : function(s) {
bgneal@312 4322 var t = this;
bgneal@312 4323
bgneal@312 4324 if (t.events)
bgneal@312 4325 t.events.destroy();
bgneal@312 4326
bgneal@312 4327 t.win = t.doc = t.root = t.events = null;
bgneal@312 4328
bgneal@312 4329 // Manual destroy then remove unload handler
bgneal@312 4330 if (!s)
bgneal@312 4331 tinymce.removeUnload(t.destroy);
bgneal@312 4332 },
bgneal@312 4333
bgneal@312 4334 createRng : function() {
bgneal@312 4335 var d = this.doc;
bgneal@312 4336
bgneal@312 4337 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
bgneal@312 4338 },
bgneal@312 4339
bgneal@312 4340 nodeIndex : function(node, normalized) {
bgneal@442 4341 var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
bgneal@312 4342
bgneal@312 4343 if (node) {
bgneal@312 4344 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
bgneal@312 4345 nodeType = node.nodeType;
bgneal@312 4346
bgneal@312 4347 // Normalize text nodes
bgneal@312 4348 if (normalized && nodeType == 3) {
bgneal@442 4349 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
bgneal@442 4350 // (the nodeValue attribute will not exist, and will error here).
bgneal@442 4351 nodeValueExists = false;
bgneal@442 4352 try {nodeValueExists = node.nodeValue.length} catch (c) {}
bgneal@442 4353 if (nodeType == lastNodeType || !nodeValueExists)
bgneal@312 4354 continue;
bgneal@312 4355 }
bgneal@312 4356 idx++;
bgneal@312 4357 lastNodeType = nodeType;
bgneal@312 4358 }
bgneal@312 4359 }
bgneal@312 4360
bgneal@312 4361 return idx;
bgneal@312 4362 },
bgneal@312 4363
bgneal@312 4364 split : function(pe, e, re) {
bgneal@312 4365 var t = this, r = t.createRng(), bef, aft, pa;
bgneal@312 4366
bgneal@312 4367 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
bgneal@312 4368 // but we don't want that in our code since it serves no purpose for the end user
bgneal@312 4369 // For example if this is chopped:
bgneal@312 4370 // <p>text 1<span><b>CHOP</b></span>text 2</p>
bgneal@312 4371 // would produce:
bgneal@312 4372 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
bgneal@312 4373 // this function will then trim of empty edges and produce:
bgneal@312 4374 // <p>text 1</p><b>CHOP</b><p>text 2</p>
bgneal@312 4375 function trim(node) {
bgneal@442 4376 var i, children = node.childNodes, type = node.nodeType;
bgneal@442 4377
bgneal@442 4378 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
bgneal@312 4379 return;
bgneal@312 4380
bgneal@312 4381 for (i = children.length - 1; i >= 0; i--)
bgneal@312 4382 trim(children[i]);
bgneal@312 4383
bgneal@442 4384 if (type != 9) {
bgneal@312 4385 // Keep non whitespace text nodes
bgneal@442 4386 if (type == 3 && node.nodeValue.length > 0) {
bgneal@442 4387 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
bgneal@442 4388 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
bgneal@442 4389 return;
bgneal@442 4390 } else if (type == 1) {
bgneal@312 4391 // If the only child is a bookmark then move it up
bgneal@312 4392 children = node.childNodes;
bgneal@442 4393 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
bgneal@312 4394 node.parentNode.insertBefore(children[0], node);
bgneal@312 4395
bgneal@312 4396 // Keep non empty elements or img, hr etc
bgneal@312 4397 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
bgneal@312 4398 return;
bgneal@312 4399 }
bgneal@312 4400
bgneal@312 4401 t.remove(node);
bgneal@312 4402 }
bgneal@312 4403
bgneal@312 4404 return node;
bgneal@312 4405 };
bgneal@312 4406
bgneal@312 4407 if (pe && e) {
bgneal@312 4408 // Get before chunk
bgneal@312 4409 r.setStart(pe.parentNode, t.nodeIndex(pe));
bgneal@312 4410 r.setEnd(e.parentNode, t.nodeIndex(e));
bgneal@312 4411 bef = r.extractContents();
bgneal@312 4412
bgneal@312 4413 // Get after chunk
bgneal@312 4414 r = t.createRng();
bgneal@312 4415 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
bgneal@312 4416 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
bgneal@312 4417 aft = r.extractContents();
bgneal@312 4418
bgneal@312 4419 // Insert before chunk
bgneal@312 4420 pa = pe.parentNode;
bgneal@312 4421 pa.insertBefore(trim(bef), pe);
bgneal@312 4422
bgneal@312 4423 // Insert middle chunk
bgneal@312 4424 if (re)
bgneal@312 4425 pa.replaceChild(re, e);
bgneal@312 4426 else
bgneal@312 4427 pa.insertBefore(e, pe);
bgneal@312 4428
bgneal@312 4429 // Insert after chunk
bgneal@312 4430 pa.insertBefore(trim(aft), pe);
bgneal@312 4431 t.remove(pe);
bgneal@312 4432
bgneal@312 4433 return re || e;
bgneal@312 4434 }
bgneal@312 4435 },
bgneal@312 4436
bgneal@312 4437 bind : function(target, name, func, scope) {
bgneal@312 4438 var t = this;
bgneal@312 4439
bgneal@312 4440 if (!t.events)
bgneal@312 4441 t.events = new tinymce.dom.EventUtils();
bgneal@312 4442
bgneal@312 4443 return t.events.add(target, name, func, scope || this);
bgneal@312 4444 },
bgneal@312 4445
bgneal@312 4446 unbind : function(target, name, func) {
bgneal@312 4447 var t = this;
bgneal@312 4448
bgneal@312 4449 if (!t.events)
bgneal@312 4450 t.events = new tinymce.dom.EventUtils();
bgneal@312 4451
bgneal@312 4452 return t.events.remove(target, name, func);
bgneal@312 4453 },
bgneal@312 4454
bgneal@312 4455
bgneal@312 4456 _findSib : function(node, selector, name) {
bgneal@312 4457 var t = this, f = selector;
bgneal@312 4458
bgneal@312 4459 if (node) {
bgneal@312 4460 // If expression make a function of it using is
bgneal@312 4461 if (is(f, 'string')) {
bgneal@312 4462 f = function(node) {
bgneal@312 4463 return t.is(node, selector);
bgneal@312 4464 };
bgneal@312 4465 }
bgneal@312 4466
bgneal@312 4467 // Loop all siblings
bgneal@312 4468 for (node = node[name]; node; node = node[name]) {
bgneal@312 4469 if (f(node))
bgneal@312 4470 return node;
bgneal@312 4471 }
bgneal@312 4472 }
bgneal@312 4473
bgneal@312 4474 return null;
bgneal@312 4475 },
bgneal@312 4476
bgneal@312 4477 _isRes : function(c) {
bgneal@312 4478 // Is live resizble element
bgneal@312 4479 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
bgneal@312 4480 }
bgneal@312 4481
bgneal@312 4482 /*
bgneal@312 4483 walk : function(n, f, s) {
bgneal@312 4484 var d = this.doc, w;
bgneal@312 4485
bgneal@312 4486 if (d.createTreeWalker) {
bgneal@312 4487 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
bgneal@312 4488
bgneal@312 4489 while ((n = w.nextNode()) != null)
bgneal@312 4490 f.call(s || this, n);
bgneal@312 4491 } else
bgneal@312 4492 tinymce.walk(n, f, 'childNodes', s);
bgneal@312 4493 }
bgneal@312 4494 */
bgneal@312 4495
bgneal@312 4496 /*
bgneal@312 4497 toRGB : function(s) {
bgneal@312 4498 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
bgneal@312 4499
bgneal@312 4500 if (c) {
bgneal@312 4501 // #FFF -> #FFFFFF
bgneal@312 4502 if (!is(c[3]))
bgneal@312 4503 c[3] = c[2] = c[1];
bgneal@312 4504
bgneal@312 4505 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
bgneal@312 4506 }
bgneal@312 4507
bgneal@312 4508 return s;
bgneal@312 4509 }
bgneal@312 4510 */
bgneal@312 4511 });
bgneal@312 4512
bgneal@312 4513 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
bgneal@312 4514 })(tinymce);
bgneal@312 4515
bgneal@312 4516 (function(ns) {
bgneal@312 4517 // Range constructor
bgneal@312 4518 function Range(dom) {
bgneal@312 4519 var t = this,
bgneal@312 4520 doc = dom.doc,
bgneal@312 4521 EXTRACT = 0,
bgneal@312 4522 CLONE = 1,
bgneal@312 4523 DELETE = 2,
bgneal@312 4524 TRUE = true,
bgneal@312 4525 FALSE = false,
bgneal@312 4526 START_OFFSET = 'startOffset',
bgneal@312 4527 START_CONTAINER = 'startContainer',
bgneal@312 4528 END_CONTAINER = 'endContainer',
bgneal@312 4529 END_OFFSET = 'endOffset',
bgneal@312 4530 extend = tinymce.extend,
bgneal@312 4531 nodeIndex = dom.nodeIndex;
bgneal@312 4532
bgneal@312 4533 extend(t, {
bgneal@312 4534 // Inital states
bgneal@312 4535 startContainer : doc,
bgneal@312 4536 startOffset : 0,
bgneal@312 4537 endContainer : doc,
bgneal@312 4538 endOffset : 0,
bgneal@312 4539 collapsed : TRUE,
bgneal@312 4540 commonAncestorContainer : doc,
bgneal@312 4541
bgneal@312 4542 // Range constants
bgneal@312 4543 START_TO_START : 0,
bgneal@312 4544 START_TO_END : 1,
bgneal@312 4545 END_TO_END : 2,
bgneal@312 4546 END_TO_START : 3,
bgneal@312 4547
bgneal@312 4548 // Public methods
bgneal@312 4549 setStart : setStart,
bgneal@312 4550 setEnd : setEnd,
bgneal@312 4551 setStartBefore : setStartBefore,
bgneal@312 4552 setStartAfter : setStartAfter,
bgneal@312 4553 setEndBefore : setEndBefore,
bgneal@312 4554 setEndAfter : setEndAfter,
bgneal@312 4555 collapse : collapse,
bgneal@312 4556 selectNode : selectNode,
bgneal@312 4557 selectNodeContents : selectNodeContents,
bgneal@312 4558 compareBoundaryPoints : compareBoundaryPoints,
bgneal@312 4559 deleteContents : deleteContents,
bgneal@312 4560 extractContents : extractContents,
bgneal@312 4561 cloneContents : cloneContents,
bgneal@312 4562 insertNode : insertNode,
bgneal@312 4563 surroundContents : surroundContents,
bgneal@312 4564 cloneRange : cloneRange
bgneal@312 4565 });
bgneal@312 4566
bgneal@312 4567 function setStart(n, o) {
bgneal@312 4568 _setEndPoint(TRUE, n, o);
bgneal@312 4569 };
bgneal@312 4570
bgneal@312 4571 function setEnd(n, o) {
bgneal@312 4572 _setEndPoint(FALSE, n, o);
bgneal@312 4573 };
bgneal@312 4574
bgneal@312 4575 function setStartBefore(n) {
bgneal@312 4576 setStart(n.parentNode, nodeIndex(n));
bgneal@312 4577 };
bgneal@312 4578
bgneal@312 4579 function setStartAfter(n) {
bgneal@312 4580 setStart(n.parentNode, nodeIndex(n) + 1);
bgneal@312 4581 };
bgneal@312 4582
bgneal@312 4583 function setEndBefore(n) {
bgneal@312 4584 setEnd(n.parentNode, nodeIndex(n));
bgneal@312 4585 };
bgneal@312 4586
bgneal@312 4587 function setEndAfter(n) {
bgneal@312 4588 setEnd(n.parentNode, nodeIndex(n) + 1);
bgneal@312 4589 };
bgneal@312 4590
bgneal@312 4591 function collapse(ts) {
bgneal@312 4592 if (ts) {
bgneal@312 4593 t[END_CONTAINER] = t[START_CONTAINER];
bgneal@312 4594 t[END_OFFSET] = t[START_OFFSET];
bgneal@312 4595 } else {
bgneal@312 4596 t[START_CONTAINER] = t[END_CONTAINER];
bgneal@312 4597 t[START_OFFSET] = t[END_OFFSET];
bgneal@312 4598 }
bgneal@312 4599
bgneal@312 4600 t.collapsed = TRUE;
bgneal@312 4601 };
bgneal@312 4602
bgneal@312 4603 function selectNode(n) {
bgneal@312 4604 setStartBefore(n);
bgneal@312 4605 setEndAfter(n);
bgneal@312 4606 };
bgneal@312 4607
bgneal@312 4608 function selectNodeContents(n) {
bgneal@312 4609 setStart(n, 0);
bgneal@312 4610 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
bgneal@312 4611 };
bgneal@312 4612
bgneal@312 4613 function compareBoundaryPoints(h, r) {
bgneal@442 4614 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
bgneal@442 4615 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
bgneal@312 4616
bgneal@312 4617 // Check START_TO_START
bgneal@312 4618 if (h === 0)
bgneal@442 4619 return _compareBoundaryPoints(sc, so, rsc, rso);
bgneal@442 4620
bgneal@312 4621 // Check START_TO_END
bgneal@312 4622 if (h === 1)
bgneal@442 4623 return _compareBoundaryPoints(ec, eo, rsc, rso);
bgneal@442 4624
bgneal@312 4625 // Check END_TO_END
bgneal@312 4626 if (h === 2)
bgneal@442 4627 return _compareBoundaryPoints(ec, eo, rec, reo);
bgneal@442 4628
bgneal@312 4629 // Check END_TO_START
bgneal@442 4630 if (h === 3)
bgneal@442 4631 return _compareBoundaryPoints(sc, so, rec, reo);
bgneal@312 4632 };
bgneal@312 4633
bgneal@312 4634 function deleteContents() {
bgneal@312 4635 _traverse(DELETE);
bgneal@312 4636 };
bgneal@312 4637
bgneal@312 4638 function extractContents() {
bgneal@312 4639 return _traverse(EXTRACT);
bgneal@312 4640 };
bgneal@312 4641
bgneal@312 4642 function cloneContents() {
bgneal@312 4643 return _traverse(CLONE);
bgneal@312 4644 };
bgneal@312 4645
bgneal@312 4646 function insertNode(n) {
bgneal@312 4647 var startContainer = this[START_CONTAINER],
bgneal@312 4648 startOffset = this[START_OFFSET], nn, o;
bgneal@312 4649
bgneal@312 4650 // Node is TEXT_NODE or CDATA
bgneal@312 4651 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
bgneal@312 4652 if (!startOffset) {
bgneal@312 4653 // At the start of text
bgneal@312 4654 startContainer.parentNode.insertBefore(n, startContainer);
bgneal@312 4655 } else if (startOffset >= startContainer.nodeValue.length) {
bgneal@312 4656 // At the end of text
bgneal@312 4657 dom.insertAfter(n, startContainer);
bgneal@312 4658 } else {
bgneal@312 4659 // Middle, need to split
bgneal@312 4660 nn = startContainer.splitText(startOffset);
bgneal@312 4661 startContainer.parentNode.insertBefore(n, nn);
bgneal@312 4662 }
bgneal@312 4663 } else {
bgneal@312 4664 // Insert element node
bgneal@312 4665 if (startContainer.childNodes.length > 0)
bgneal@312 4666 o = startContainer.childNodes[startOffset];
bgneal@312 4667
bgneal@312 4668 if (o)
bgneal@312 4669 startContainer.insertBefore(n, o);
bgneal@312 4670 else
bgneal@312 4671 startContainer.appendChild(n);
bgneal@312 4672 }
bgneal@312 4673 };
bgneal@312 4674
bgneal@312 4675 function surroundContents(n) {
bgneal@312 4676 var f = t.extractContents();
bgneal@312 4677
bgneal@312 4678 t.insertNode(n);
bgneal@312 4679 n.appendChild(f);
bgneal@312 4680 t.selectNode(n);
bgneal@312 4681 };
bgneal@312 4682
bgneal@312 4683 function cloneRange() {
bgneal@312 4684 return extend(new Range(dom), {
bgneal@312 4685 startContainer : t[START_CONTAINER],
bgneal@312 4686 startOffset : t[START_OFFSET],
bgneal@312 4687 endContainer : t[END_CONTAINER],
bgneal@312 4688 endOffset : t[END_OFFSET],
bgneal@312 4689 collapsed : t.collapsed,
bgneal@312 4690 commonAncestorContainer : t.commonAncestorContainer
bgneal@312 4691 });
bgneal@312 4692 };
bgneal@312 4693
bgneal@312 4694 // Private methods
bgneal@312 4695
bgneal@312 4696 function _getSelectedNode(container, offset) {
bgneal@312 4697 var child;
bgneal@312 4698
bgneal@312 4699 if (container.nodeType == 3 /* TEXT_NODE */)
bgneal@312 4700 return container;
bgneal@312 4701
bgneal@312 4702 if (offset < 0)
bgneal@312 4703 return container;
bgneal@312 4704
bgneal@312 4705 child = container.firstChild;
bgneal@312 4706 while (child && offset > 0) {
bgneal@312 4707 --offset;
bgneal@312 4708 child = child.nextSibling;
bgneal@312 4709 }
bgneal@312 4710
bgneal@312 4711 if (child)
bgneal@312 4712 return child;
bgneal@312 4713
bgneal@312 4714 return container;
bgneal@312 4715 };
bgneal@312 4716
bgneal@312 4717 function _isCollapsed() {
bgneal@312 4718 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
bgneal@312 4719 };
bgneal@312 4720
bgneal@312 4721 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
bgneal@312 4722 var c, offsetC, n, cmnRoot, childA, childB;
bgneal@442 4723
bgneal@312 4724 // In the first case the boundary-points have the same container. A is before B
bgneal@312 4725 // if its offset is less than the offset of B, A is equal to B if its offset is
bgneal@312 4726 // equal to the offset of B, and A is after B if its offset is greater than the
bgneal@312 4727 // offset of B.
bgneal@312 4728 if (containerA == containerB) {
bgneal@312 4729 if (offsetA == offsetB)
bgneal@312 4730 return 0; // equal
bgneal@312 4731
bgneal@312 4732 if (offsetA < offsetB)
bgneal@312 4733 return -1; // before
bgneal@312 4734
bgneal@312 4735 return 1; // after
bgneal@312 4736 }
bgneal@312 4737
bgneal@312 4738 // In the second case a child node C of the container of A is an ancestor
bgneal@312 4739 // container of B. In this case, A is before B if the offset of A is less than or
bgneal@312 4740 // equal to the index of the child node C and A is after B otherwise.
bgneal@312 4741 c = containerB;
bgneal@312 4742 while (c && c.parentNode != containerA)
bgneal@312 4743 c = c.parentNode;
bgneal@312 4744
bgneal@312 4745 if (c) {
bgneal@312 4746 offsetC = 0;
bgneal@312 4747 n = containerA.firstChild;
bgneal@312 4748
bgneal@312 4749 while (n != c && offsetC < offsetA) {
bgneal@312 4750 offsetC++;
bgneal@312 4751 n = n.nextSibling;
bgneal@312 4752 }
bgneal@312 4753
bgneal@312 4754 if (offsetA <= offsetC)
bgneal@312 4755 return -1; // before
bgneal@312 4756
bgneal@312 4757 return 1; // after
bgneal@312 4758 }
bgneal@312 4759
bgneal@312 4760 // In the third case a child node C of the container of B is an ancestor container
bgneal@312 4761 // of A. In this case, A is before B if the index of the child node C is less than
bgneal@312 4762 // the offset of B and A is after B otherwise.
bgneal@312 4763 c = containerA;
bgneal@312 4764 while (c && c.parentNode != containerB) {
bgneal@312 4765 c = c.parentNode;
bgneal@312 4766 }
bgneal@312 4767
bgneal@312 4768 if (c) {
bgneal@312 4769 offsetC = 0;
bgneal@312 4770 n = containerB.firstChild;
bgneal@312 4771
bgneal@312 4772 while (n != c && offsetC < offsetB) {
bgneal@312 4773 offsetC++;
bgneal@312 4774 n = n.nextSibling;
bgneal@312 4775 }
bgneal@312 4776
bgneal@312 4777 if (offsetC < offsetB)
bgneal@312 4778 return -1; // before
bgneal@312 4779
bgneal@312 4780 return 1; // after
bgneal@312 4781 }
bgneal@312 4782
bgneal@312 4783 // In the fourth case, none of three other cases hold: the containers of A and B
bgneal@312 4784 // are siblings or descendants of sibling nodes. In this case, A is before B if
bgneal@312 4785 // the container of A is before the container of B in a pre-order traversal of the
bgneal@312 4786 // Ranges' context tree and A is after B otherwise.
bgneal@312 4787 cmnRoot = dom.findCommonAncestor(containerA, containerB);
bgneal@312 4788 childA = containerA;
bgneal@312 4789
bgneal@312 4790 while (childA && childA.parentNode != cmnRoot)
bgneal@312 4791 childA = childA.parentNode;
bgneal@312 4792
bgneal@312 4793 if (!childA)
bgneal@312 4794 childA = cmnRoot;
bgneal@312 4795
bgneal@312 4796 childB = containerB;
bgneal@312 4797 while (childB && childB.parentNode != cmnRoot)
bgneal@312 4798 childB = childB.parentNode;
bgneal@312 4799
bgneal@312 4800 if (!childB)
bgneal@312 4801 childB = cmnRoot;
bgneal@312 4802
bgneal@312 4803 if (childA == childB)
bgneal@312 4804 return 0; // equal
bgneal@312 4805
bgneal@312 4806 n = cmnRoot.firstChild;
bgneal@312 4807 while (n) {
bgneal@312 4808 if (n == childA)
bgneal@312 4809 return -1; // before
bgneal@312 4810
bgneal@312 4811 if (n == childB)
bgneal@312 4812 return 1; // after
bgneal@312 4813
bgneal@312 4814 n = n.nextSibling;
bgneal@312 4815 }
bgneal@312 4816 };
bgneal@312 4817
bgneal@312 4818 function _setEndPoint(st, n, o) {
bgneal@312 4819 var ec, sc;
bgneal@312 4820
bgneal@312 4821 if (st) {
bgneal@312 4822 t[START_CONTAINER] = n;
bgneal@312 4823 t[START_OFFSET] = o;
bgneal@312 4824 } else {
bgneal@312 4825 t[END_CONTAINER] = n;
bgneal@312 4826 t[END_OFFSET] = o;
bgneal@312 4827 }
bgneal@312 4828
bgneal@312 4829 // If one boundary-point of a Range is set to have a root container
bgneal@312 4830 // other than the current one for the Range, the Range is collapsed to
bgneal@312 4831 // the new position. This enforces the restriction that both boundary-
bgneal@312 4832 // points of a Range must have the same root container.
bgneal@312 4833 ec = t[END_CONTAINER];
bgneal@312 4834 while (ec.parentNode)
bgneal@312 4835 ec = ec.parentNode;
bgneal@312 4836
bgneal@312 4837 sc = t[START_CONTAINER];
bgneal@312 4838 while (sc.parentNode)
bgneal@312 4839 sc = sc.parentNode;
bgneal@312 4840
bgneal@312 4841 if (sc == ec) {
bgneal@312 4842 // The start position of a Range is guaranteed to never be after the
bgneal@312 4843 // end position. To enforce this restriction, if the start is set to
bgneal@312 4844 // be at a position after the end, the Range is collapsed to that
bgneal@312 4845 // position.
bgneal@312 4846 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
bgneal@312 4847 t.collapse(st);
bgneal@312 4848 } else
bgneal@312 4849 t.collapse(st);
bgneal@312 4850
bgneal@312 4851 t.collapsed = _isCollapsed();
bgneal@312 4852 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
bgneal@312 4853 };
bgneal@312 4854
bgneal@312 4855 function _traverse(how) {
bgneal@312 4856 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
bgneal@312 4857
bgneal@312 4858 if (t[START_CONTAINER] == t[END_CONTAINER])
bgneal@312 4859 return _traverseSameContainer(how);
bgneal@312 4860
bgneal@312 4861 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@312 4862 if (p == t[START_CONTAINER])
bgneal@312 4863 return _traverseCommonStartContainer(c, how);
bgneal@312 4864
bgneal@312 4865 ++endContainerDepth;
bgneal@312 4866 }
bgneal@312 4867
bgneal@312 4868 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
bgneal@312 4869 if (p == t[END_CONTAINER])
bgneal@312 4870 return _traverseCommonEndContainer(c, how);
bgneal@312 4871
bgneal@312 4872 ++startContainerDepth;
bgneal@312 4873 }
bgneal@312 4874
bgneal@312 4875 depthDiff = startContainerDepth - endContainerDepth;
bgneal@312 4876
bgneal@312 4877 startNode = t[START_CONTAINER];
bgneal@312 4878 while (depthDiff > 0) {
bgneal@312 4879 startNode = startNode.parentNode;
bgneal@312 4880 depthDiff--;
bgneal@312 4881 }
bgneal@312 4882
bgneal@312 4883 endNode = t[END_CONTAINER];
bgneal@312 4884 while (depthDiff < 0) {
bgneal@312 4885 endNode = endNode.parentNode;
bgneal@312 4886 depthDiff++;
bgneal@312 4887 }
bgneal@312 4888
bgneal@312 4889 // ascend the ancestor hierarchy until we have a common parent.
bgneal@312 4890 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
bgneal@312 4891 startNode = sp;
bgneal@312 4892 endNode = ep;
bgneal@312 4893 }
bgneal@312 4894
bgneal@312 4895 return _traverseCommonAncestors(startNode, endNode, how);
bgneal@312 4896 };
bgneal@312 4897
bgneal@312 4898 function _traverseSameContainer(how) {
bgneal@312 4899 var frag, s, sub, n, cnt, sibling, xferNode;
bgneal@312 4900
bgneal@312 4901 if (how != DELETE)
bgneal@312 4902 frag = doc.createDocumentFragment();
bgneal@312 4903
bgneal@312 4904 // If selection is empty, just return the fragment
bgneal@312 4905 if (t[START_OFFSET] == t[END_OFFSET])
bgneal@312 4906 return frag;
bgneal@312 4907
bgneal@312 4908 // Text node needs special case handling
bgneal@312 4909 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
bgneal@312 4910 // get the substring
bgneal@312 4911 s = t[START_CONTAINER].nodeValue;
bgneal@312 4912 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
bgneal@312 4913
bgneal@312 4914 // set the original text node to its new value
bgneal@312 4915 if (how != CLONE) {
bgneal@312 4916 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
bgneal@312 4917
bgneal@312 4918 // Nothing is partially selected, so collapse to start point
bgneal@312 4919 t.collapse(TRUE);
bgneal@312 4920 }
bgneal@312 4921
bgneal@312 4922 if (how == DELETE)
bgneal@312 4923 return;
bgneal@312 4924
bgneal@312 4925 frag.appendChild(doc.createTextNode(sub));
bgneal@312 4926 return frag;
bgneal@312 4927 }
bgneal@312 4928
bgneal@312 4929 // Copy nodes between the start/end offsets.
bgneal@312 4930 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
bgneal@312 4931 cnt = t[END_OFFSET] - t[START_OFFSET];
bgneal@312 4932
bgneal@312 4933 while (cnt > 0) {
bgneal@312 4934 sibling = n.nextSibling;
bgneal@312 4935 xferNode = _traverseFullySelected(n, how);
bgneal@312 4936
bgneal@312 4937 if (frag)
bgneal@312 4938 frag.appendChild( xferNode );
bgneal@312 4939
bgneal@312 4940 --cnt;
bgneal@312 4941 n = sibling;
bgneal@312 4942 }
bgneal@312 4943
bgneal@312 4944 // Nothing is partially selected, so collapse to start point
bgneal@312 4945 if (how != CLONE)
bgneal@312 4946 t.collapse(TRUE);
bgneal@312 4947
bgneal@312 4948 return frag;
bgneal@312 4949 };
bgneal@312 4950
bgneal@312 4951 function _traverseCommonStartContainer(endAncestor, how) {
bgneal@312 4952 var frag, n, endIdx, cnt, sibling, xferNode;
bgneal@312 4953
bgneal@312 4954 if (how != DELETE)
bgneal@312 4955 frag = doc.createDocumentFragment();
bgneal@312 4956
bgneal@312 4957 n = _traverseRightBoundary(endAncestor, how);
bgneal@312 4958
bgneal@312 4959 if (frag)
bgneal@312 4960 frag.appendChild(n);
bgneal@312 4961
bgneal@312 4962 endIdx = nodeIndex(endAncestor);
bgneal@312 4963 cnt = endIdx - t[START_OFFSET];
bgneal@312 4964
bgneal@312 4965 if (cnt <= 0) {
bgneal@312 4966 // Collapse to just before the endAncestor, which
bgneal@312 4967 // is partially selected.
bgneal@312 4968 if (how != CLONE) {
bgneal@312 4969 t.setEndBefore(endAncestor);
bgneal@312 4970 t.collapse(FALSE);
bgneal@312 4971 }
bgneal@312 4972
bgneal@312 4973 return frag;
bgneal@312 4974 }
bgneal@312 4975
bgneal@312 4976 n = endAncestor.previousSibling;
bgneal@312 4977 while (cnt > 0) {
bgneal@312 4978 sibling = n.previousSibling;
bgneal@312 4979 xferNode = _traverseFullySelected(n, how);
bgneal@312 4980
bgneal@312 4981 if (frag)
bgneal@312 4982 frag.insertBefore(xferNode, frag.firstChild);
bgneal@312 4983
bgneal@312 4984 --cnt;
bgneal@312 4985 n = sibling;
bgneal@312 4986 }
bgneal@312 4987
bgneal@312 4988 // Collapse to just before the endAncestor, which
bgneal@312 4989 // is partially selected.
bgneal@312 4990 if (how != CLONE) {
bgneal@312 4991 t.setEndBefore(endAncestor);
bgneal@312 4992 t.collapse(FALSE);
bgneal@312 4993 }
bgneal@312 4994
bgneal@312 4995 return frag;
bgneal@312 4996 };
bgneal@312 4997
bgneal@312 4998 function _traverseCommonEndContainer(startAncestor, how) {
bgneal@312 4999 var frag, startIdx, n, cnt, sibling, xferNode;
bgneal@312 5000
bgneal@312 5001 if (how != DELETE)
bgneal@312 5002 frag = doc.createDocumentFragment();
bgneal@312 5003
bgneal@312 5004 n = _traverseLeftBoundary(startAncestor, how);
bgneal@312 5005 if (frag)
bgneal@312 5006 frag.appendChild(n);
bgneal@312 5007
bgneal@312 5008 startIdx = nodeIndex(startAncestor);
bgneal@442 5009 ++startIdx; // Because we already traversed it
bgneal@312 5010
bgneal@312 5011 cnt = t[END_OFFSET] - startIdx;
bgneal@312 5012 n = startAncestor.nextSibling;
bgneal@312 5013 while (cnt > 0) {
bgneal@312 5014 sibling = n.nextSibling;
bgneal@312 5015 xferNode = _traverseFullySelected(n, how);
bgneal@312 5016
bgneal@312 5017 if (frag)
bgneal@312 5018 frag.appendChild(xferNode);
bgneal@312 5019
bgneal@312 5020 --cnt;
bgneal@312 5021 n = sibling;
bgneal@312 5022 }
bgneal@312 5023
bgneal@312 5024 if (how != CLONE) {
bgneal@312 5025 t.setStartAfter(startAncestor);
bgneal@312 5026 t.collapse(TRUE);
bgneal@312 5027 }
bgneal@312 5028
bgneal@312 5029 return frag;
bgneal@312 5030 };
bgneal@312 5031
bgneal@312 5032 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
bgneal@312 5033 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
bgneal@312 5034
bgneal@312 5035 if (how != DELETE)
bgneal@312 5036 frag = doc.createDocumentFragment();
bgneal@312 5037
bgneal@312 5038 n = _traverseLeftBoundary(startAncestor, how);
bgneal@312 5039 if (frag)
bgneal@312 5040 frag.appendChild(n);
bgneal@312 5041
bgneal@312 5042 commonParent = startAncestor.parentNode;
bgneal@312 5043 startOffset = nodeIndex(startAncestor);
bgneal@312 5044 endOffset = nodeIndex(endAncestor);
bgneal@312 5045 ++startOffset;
bgneal@312 5046
bgneal@312 5047 cnt = endOffset - startOffset;
bgneal@312 5048 sibling = startAncestor.nextSibling;
bgneal@312 5049
bgneal@312 5050 while (cnt > 0) {
bgneal@312 5051 nextSibling = sibling.nextSibling;
bgneal@312 5052 n = _traverseFullySelected(sibling, how);
bgneal@312 5053
bgneal@312 5054 if (frag)
bgneal@312 5055 frag.appendChild(n);
bgneal@312 5056
bgneal@312 5057 sibling = nextSibling;
bgneal@312 5058 --cnt;
bgneal@312 5059 }
bgneal@312 5060
bgneal@312 5061 n = _traverseRightBoundary(endAncestor, how);
bgneal@312 5062
bgneal@312 5063 if (frag)
bgneal@312 5064 frag.appendChild(n);
bgneal@312 5065
bgneal@312 5066 if (how != CLONE) {
bgneal@312 5067 t.setStartAfter(startAncestor);
bgneal@312 5068 t.collapse(TRUE);
bgneal@312 5069 }
bgneal@312 5070
bgneal@312 5071 return frag;
bgneal@312 5072 };
bgneal@312 5073
bgneal@312 5074 function _traverseRightBoundary(root, how) {
bgneal@312 5075 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
bgneal@312 5076
bgneal@312 5077 if (next == root)
bgneal@312 5078 return _traverseNode(next, isFullySelected, FALSE, how);
bgneal@312 5079
bgneal@312 5080 parent = next.parentNode;
bgneal@312 5081 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@312 5082
bgneal@312 5083 while (parent) {
bgneal@312 5084 while (next) {
bgneal@312 5085 prevSibling = next.previousSibling;
bgneal@312 5086 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
bgneal@312 5087
bgneal@312 5088 if (how != DELETE)
bgneal@312 5089 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
bgneal@312 5090
bgneal@312 5091 isFullySelected = TRUE;
bgneal@312 5092 next = prevSibling;
bgneal@312 5093 }
bgneal@312 5094
bgneal@312 5095 if (parent == root)
bgneal@312 5096 return clonedParent;
bgneal@312 5097
bgneal@312 5098 next = parent.previousSibling;
bgneal@312 5099 parent = parent.parentNode;
bgneal@312 5100
bgneal@312 5101 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
bgneal@312 5102
bgneal@312 5103 if (how != DELETE)
bgneal@312 5104 clonedGrandParent.appendChild(clonedParent);
bgneal@312 5105
bgneal@312 5106 clonedParent = clonedGrandParent;
bgneal@312 5107 }
bgneal@312 5108 };
bgneal@312 5109
bgneal@312 5110 function _traverseLeftBoundary(root, how) {
bgneal@312 5111 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
bgneal@312 5112
bgneal@312 5113 if (next == root)
bgneal@312 5114 return _traverseNode(next, isFullySelected, TRUE, how);
bgneal@312 5115
bgneal@312 5116 parent = next.parentNode;
bgneal@312 5117 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@312 5118
bgneal@312 5119 while (parent) {
bgneal@312 5120 while (next) {
bgneal@312 5121 nextSibling = next.nextSibling;
bgneal@312 5122 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
bgneal@312 5123
bgneal@312 5124 if (how != DELETE)
bgneal@312 5125 clonedParent.appendChild(clonedChild);
bgneal@312 5126
bgneal@312 5127 isFullySelected = TRUE;
bgneal@312 5128 next = nextSibling;
bgneal@312 5129 }
bgneal@312 5130
bgneal@312 5131 if (parent == root)
bgneal@312 5132 return clonedParent;
bgneal@312 5133
bgneal@312 5134 next = parent.nextSibling;
bgneal@312 5135 parent = parent.parentNode;
bgneal@312 5136
bgneal@312 5137 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
bgneal@312 5138
bgneal@312 5139 if (how != DELETE)
bgneal@312 5140 clonedGrandParent.appendChild(clonedParent);
bgneal@312 5141
bgneal@312 5142 clonedParent = clonedGrandParent;
bgneal@312 5143 }
bgneal@312 5144 };
bgneal@312 5145
bgneal@312 5146 function _traverseNode(n, isFullySelected, isLeft, how) {
bgneal@312 5147 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
bgneal@312 5148
bgneal@312 5149 if (isFullySelected)
bgneal@312 5150 return _traverseFullySelected(n, how);
bgneal@312 5151
bgneal@312 5152 if (n.nodeType == 3 /* TEXT_NODE */) {
bgneal@312 5153 txtValue = n.nodeValue;
bgneal@312 5154
bgneal@312 5155 if (isLeft) {
bgneal@312 5156 offset = t[START_OFFSET];
bgneal@312 5157 newNodeValue = txtValue.substring(offset);
bgneal@312 5158 oldNodeValue = txtValue.substring(0, offset);
bgneal@312 5159 } else {
bgneal@312 5160 offset = t[END_OFFSET];
bgneal@312 5161 newNodeValue = txtValue.substring(0, offset);
bgneal@312 5162 oldNodeValue = txtValue.substring(offset);
bgneal@312 5163 }
bgneal@312 5164
bgneal@312 5165 if (how != CLONE)
bgneal@312 5166 n.nodeValue = oldNodeValue;
bgneal@312 5167
bgneal@312 5168 if (how == DELETE)
bgneal@312 5169 return;
bgneal@312 5170
bgneal@312 5171 newNode = n.cloneNode(FALSE);
bgneal@312 5172 newNode.nodeValue = newNodeValue;
bgneal@312 5173
bgneal@312 5174 return newNode;
bgneal@312 5175 }
bgneal@312 5176
bgneal@312 5177 if (how == DELETE)
bgneal@312 5178 return;
bgneal@312 5179
bgneal@312 5180 return n.cloneNode(FALSE);
bgneal@312 5181 };
bgneal@312 5182
bgneal@312 5183 function _traverseFullySelected(n, how) {
bgneal@312 5184 if (how != DELETE)
bgneal@312 5185 return how == CLONE ? n.cloneNode(TRUE) : n;
bgneal@312 5186
bgneal@312 5187 n.parentNode.removeChild(n);
bgneal@312 5188 };
bgneal@312 5189 };
bgneal@312 5190
bgneal@312 5191 ns.Range = Range;
bgneal@312 5192 })(tinymce.dom);
bgneal@312 5193
bgneal@312 5194 (function() {
bgneal@312 5195 function Selection(selection) {
bgneal@312 5196 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
bgneal@312 5197
bgneal@312 5198 // Returns a W3C DOM compatible range object by using the IE Range API
bgneal@312 5199 function getRange() {
bgneal@312 5200 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
bgneal@312 5201
bgneal@312 5202 // If selection is outside the current document just return an empty range
bgneal@312 5203 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
bgneal@312 5204 if (element.ownerDocument != dom.doc)
bgneal@312 5205 return domRange;
bgneal@312 5206
bgneal@442 5207 collapsed = selection.isCollapsed();
bgneal@442 5208
bgneal@312 5209 // Handle control selection or text selection of a image
bgneal@312 5210 if (ieRange.item || !element.hasChildNodes()) {
bgneal@442 5211 if (collapsed) {
bgneal@442 5212 domRange.setStart(element, 0);
bgneal@442 5213 domRange.setEnd(element, 0);
bgneal@442 5214 } else {
bgneal@442 5215 domRange.setStart(element.parentNode, dom.nodeIndex(element));
bgneal@442 5216 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
bgneal@442 5217 }
bgneal@312 5218
bgneal@312 5219 return domRange;
bgneal@312 5220 }
bgneal@312 5221
bgneal@312 5222 function findEndPoint(start) {
bgneal@312 5223 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
bgneal@312 5224
bgneal@312 5225 // Setup temp range and collapse it
bgneal@312 5226 checkRng = ieRange.duplicate();
bgneal@312 5227 checkRng.collapse(start);
bgneal@312 5228
bgneal@312 5229 // Create marker and insert it at the end of the endpoints parent
bgneal@312 5230 marker = dom.create('a');
bgneal@312 5231 parent = checkRng.parentElement();
bgneal@312 5232
bgneal@312 5233 // If parent doesn't have any children then set the container to that parent and the index to 0
bgneal@312 5234 if (!parent.hasChildNodes()) {
bgneal@312 5235 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
bgneal@312 5236 return;
bgneal@312 5237 }
bgneal@312 5238
bgneal@312 5239 parent.appendChild(marker);
bgneal@312 5240 checkRng.moveToElementText(marker);
bgneal@312 5241 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@312 5242 if (position > 0) {
bgneal@312 5243 // The position is after the end of the parent element.
bgneal@312 5244 // This is the case where IE puts the caret to the left edge of a table.
bgneal@312 5245 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
bgneal@312 5246 dom.remove(marker);
bgneal@312 5247 return;
bgneal@312 5248 }
bgneal@312 5249
bgneal@312 5250 // Setup node list and endIndex
bgneal@312 5251 nodes = tinymce.grep(parent.childNodes);
bgneal@312 5252 endIndex = nodes.length - 1;
bgneal@312 5253 // Perform a binary search for the position
bgneal@312 5254 while (startIndex <= endIndex) {
bgneal@312 5255 index = Math.floor((startIndex + endIndex) / 2);
bgneal@312 5256
bgneal@312 5257 // Insert marker and check it's position relative to the selection
bgneal@312 5258 parent.insertBefore(marker, nodes[index]);
bgneal@312 5259 checkRng.moveToElementText(marker);
bgneal@312 5260 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
bgneal@312 5261 if (position > 0) {
bgneal@312 5262 // Marker is to the right
bgneal@312 5263 startIndex = index + 1;
bgneal@312 5264 } else if (position < 0) {
bgneal@312 5265 // Marker is to the left
bgneal@312 5266 endIndex = index - 1;
bgneal@312 5267 } else {
bgneal@312 5268 // Maker is where we are
bgneal@312 5269 found = true;
bgneal@312 5270 break;
bgneal@312 5271 }
bgneal@312 5272 }
bgneal@312 5273
bgneal@312 5274 // Setup container
bgneal@312 5275 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
bgneal@312 5276
bgneal@312 5277 // Handle element selection
bgneal@312 5278 if (container.nodeType == 1) {
bgneal@312 5279 dom.remove(marker);
bgneal@312 5280
bgneal@312 5281 // Find offset and container
bgneal@312 5282 offset = dom.nodeIndex(container);
bgneal@312 5283 container = container.parentNode;
bgneal@312 5284
bgneal@312 5285 // Move the offset if we are setting the end or the position is after an element
bgneal@312 5286 if (!start || index > 0)
bgneal@312 5287 offset++;
bgneal@312 5288 } else {
bgneal@312 5289 // Calculate offset within text node
bgneal@312 5290 if (position > 0 || index == 0) {
bgneal@312 5291 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@312 5292 offset = checkRng.text.length;
bgneal@312 5293 } else {
bgneal@312 5294 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
bgneal@312 5295 offset = container.nodeValue.length - checkRng.text.length;
bgneal@312 5296 }
bgneal@312 5297
bgneal@312 5298 dom.remove(marker);
bgneal@312 5299 }
bgneal@312 5300
bgneal@312 5301 domRange[start ? 'setStart' : 'setEnd'](container, offset);
bgneal@312 5302 };
bgneal@312 5303
bgneal@312 5304 // Find start point
bgneal@312 5305 findEndPoint(true);
bgneal@312 5306
bgneal@312 5307 // Find end point if needed
bgneal@312 5308 if (!collapsed)
bgneal@312 5309 findEndPoint();
bgneal@312 5310
bgneal@312 5311 return domRange;
bgneal@312 5312 };
bgneal@312 5313
bgneal@312 5314 this.addRange = function(rng) {
bgneal@312 5315 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
bgneal@312 5316
bgneal@312 5317 function setEndPoint(start) {
bgneal@312 5318 var container, offset, marker, tmpRng, nodes;
bgneal@312 5319
bgneal@312 5320 marker = dom.create('a');
bgneal@312 5321 container = start ? startContainer : endContainer;
bgneal@312 5322 offset = start ? startOffset : endOffset;
bgneal@312 5323 tmpRng = ieRng.duplicate();
bgneal@312 5324
bgneal@442 5325 if (container == doc || container == doc.documentElement) {
bgneal@312 5326 container = body;
bgneal@312 5327 offset = 0;
bgneal@312 5328 }
bgneal@312 5329
bgneal@312 5330 if (container.nodeType == 3) {
bgneal@312 5331 container.parentNode.insertBefore(marker, container);
bgneal@312 5332 tmpRng.moveToElementText(marker);
bgneal@312 5333 tmpRng.moveStart('character', offset);
bgneal@312 5334 dom.remove(marker);
bgneal@312 5335 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@312 5336 } else {
bgneal@312 5337 nodes = container.childNodes;
bgneal@312 5338
bgneal@312 5339 if (nodes.length) {
bgneal@312 5340 if (offset >= nodes.length) {
bgneal@312 5341 dom.insertAfter(marker, nodes[nodes.length - 1]);
bgneal@312 5342 } else {
bgneal@312 5343 container.insertBefore(marker, nodes[offset]);
bgneal@312 5344 }
bgneal@312 5345
bgneal@312 5346 tmpRng.moveToElementText(marker);
bgneal@312 5347 } else {
bgneal@312 5348 // Empty node selection for example <div>|</div>
bgneal@312 5349 marker = doc.createTextNode(invisibleChar);
bgneal@312 5350 container.appendChild(marker);
bgneal@312 5351 tmpRng.moveToElementText(marker.parentNode);
bgneal@312 5352 tmpRng.collapse(TRUE);
bgneal@312 5353 }
bgneal@312 5354
bgneal@312 5355 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
bgneal@312 5356 dom.remove(marker);
bgneal@312 5357 }
bgneal@312 5358 }
bgneal@312 5359
bgneal@312 5360 // Destroy cached range
bgneal@312 5361 this.destroy();
bgneal@312 5362
bgneal@312 5363 // Setup some shorter versions
bgneal@312 5364 startContainer = rng.startContainer;
bgneal@312 5365 startOffset = rng.startOffset;
bgneal@312 5366 endContainer = rng.endContainer;
bgneal@312 5367 endOffset = rng.endOffset;
bgneal@312 5368 ieRng = body.createTextRange();
bgneal@312 5369
bgneal@312 5370 // If single element selection then try making a control selection out of it
bgneal@312 5371 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
bgneal@312 5372 if (startOffset == endOffset - 1) {
bgneal@312 5373 try {
bgneal@312 5374 ctrlRng = body.createControlRange();
bgneal@312 5375 ctrlRng.addElement(startContainer.childNodes[startOffset]);
bgneal@312 5376 ctrlRng.select();
bgneal@312 5377 return;
bgneal@312 5378 } catch (ex) {
bgneal@312 5379 // Ignore
bgneal@312 5380 }
bgneal@312 5381 }
bgneal@312 5382 }
bgneal@312 5383
bgneal@312 5384 // Set start/end point of selection
bgneal@312 5385 setEndPoint(true);
bgneal@312 5386 setEndPoint();
bgneal@312 5387
bgneal@312 5388 // Select the new range and scroll it into view
bgneal@312 5389 ieRng.select();
bgneal@312 5390 };
bgneal@312 5391
bgneal@312 5392 this.getRangeAt = function() {
bgneal@312 5393 // Setup new range if the cache is empty
bgneal@312 5394 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
bgneal@312 5395 range = getRange();
bgneal@312 5396
bgneal@312 5397 // Store away text range for next call
bgneal@312 5398 lastIERng = selection.getRng();
bgneal@312 5399 }
bgneal@312 5400
bgneal@312 5401 // IE will say that the range is equal then produce an invalid argument exception
bgneal@312 5402 // if you perform specific operations in a keyup event. For example Ctrl+Del.
bgneal@312 5403 // This hack will invalidate the range cache if the exception occurs
bgneal@312 5404 try {
bgneal@312 5405 range.startContainer.nextSibling;
bgneal@312 5406 } catch (ex) {
bgneal@312 5407 range = getRange();
bgneal@312 5408 lastIERng = null;
bgneal@312 5409 }
bgneal@312 5410
bgneal@312 5411 // Return cached range
bgneal@312 5412 return range;
bgneal@312 5413 };
bgneal@312 5414
bgneal@312 5415 this.destroy = function() {
bgneal@312 5416 // Destroy cached range and last IE range to avoid memory leaks
bgneal@312 5417 lastIERng = range = null;
bgneal@312 5418 };
bgneal@312 5419 };
bgneal@312 5420
bgneal@312 5421 // Expose the selection object
bgneal@312 5422 tinymce.dom.TridentSelection = Selection;
bgneal@312 5423 })();
bgneal@312 5424
bgneal@312 5425
bgneal@312 5426 /*
bgneal@312 5427 * Sizzle CSS Selector Engine - v1.0
bgneal@312 5428 * Copyright 2009, The Dojo Foundation
bgneal@312 5429 * Released under the MIT, BSD, and GPL Licenses.
bgneal@312 5430 * More information: http://sizzlejs.com/
bgneal@312 5431 */
bgneal@312 5432 (function(){
bgneal@312 5433
bgneal@312 5434 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
bgneal@312 5435 done = 0,
bgneal@312 5436 toString = Object.prototype.toString,
bgneal@312 5437 hasDuplicate = false,
bgneal@312 5438 baseHasDuplicate = true;
bgneal@312 5439
bgneal@312 5440 // Here we check if the JavaScript engine is using some sort of
bgneal@312 5441 // optimization where it does not always call our comparision
bgneal@312 5442 // function. If that is the case, discard the hasDuplicate value.
bgneal@312 5443 // Thus far that includes Google Chrome.
bgneal@312 5444 [0, 0].sort(function(){
bgneal@312 5445 baseHasDuplicate = false;
bgneal@312 5446 return 0;
bgneal@312 5447 });
bgneal@312 5448
bgneal@312 5449 var Sizzle = function(selector, context, results, seed) {
bgneal@312 5450 results = results || [];
bgneal@312 5451 context = context || document;
bgneal@312 5452
bgneal@312 5453 var origContext = context;
bgneal@312 5454
bgneal@312 5455 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
bgneal@312 5456 return [];
bgneal@312 5457 }
bgneal@312 5458
bgneal@312 5459 if ( !selector || typeof selector !== "string" ) {
bgneal@312 5460 return results;
bgneal@312 5461 }
bgneal@312 5462
bgneal@312 5463 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
bgneal@312 5464 soFar = selector, ret, cur, pop, i;
bgneal@312 5465
bgneal@312 5466 // Reset the position of the chunker regexp (start from head)
bgneal@312 5467 do {
bgneal@312 5468 chunker.exec("");
bgneal@312 5469 m = chunker.exec(soFar);
bgneal@312 5470
bgneal@312 5471 if ( m ) {
bgneal@312 5472 soFar = m[3];
bgneal@312 5473
bgneal@312 5474 parts.push( m[1] );
bgneal@312 5475
bgneal@312 5476 if ( m[2] ) {
bgneal@312 5477 extra = m[3];
bgneal@312 5478 break;
bgneal@312 5479 }
bgneal@312 5480 }
bgneal@312 5481 } while ( m );
bgneal@312 5482
bgneal@312 5483 if ( parts.length > 1 && origPOS.exec( selector ) ) {
bgneal@312 5484 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
bgneal@312 5485 set = posProcess( parts[0] + parts[1], context );
bgneal@312 5486 } else {
bgneal@312 5487 set = Expr.relative[ parts[0] ] ?
bgneal@312 5488 [ context ] :
bgneal@312 5489 Sizzle( parts.shift(), context );
bgneal@312 5490
bgneal@312 5491 while ( parts.length ) {
bgneal@312 5492 selector = parts.shift();
bgneal@312 5493
bgneal@312 5494 if ( Expr.relative[ selector ] ) {
bgneal@312 5495 selector += parts.shift();
bgneal@312 5496 }
bgneal@312 5497
bgneal@312 5498 set = posProcess( selector, set );
bgneal@312 5499 }
bgneal@312 5500 }
bgneal@312 5501 } else {
bgneal@312 5502 // Take a shortcut and set the context if the root selector is an ID
bgneal@312 5503 // (but not if it'll be faster if the inner selector is an ID)
bgneal@312 5504 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
bgneal@312 5505 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
bgneal@312 5506 ret = Sizzle.find( parts.shift(), context, contextXML );
bgneal@312 5507 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
bgneal@312 5508 }
bgneal@312 5509
bgneal@312 5510 if ( context ) {
bgneal@312 5511 ret = seed ?
bgneal@312 5512 { expr: parts.pop(), set: makeArray(seed) } :
bgneal@312 5513 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
bgneal@312 5514 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
bgneal@312 5515
bgneal@312 5516 if ( parts.length > 0 ) {
bgneal@312 5517 checkSet = makeArray(set);
bgneal@312 5518 } else {
bgneal@312 5519 prune = false;
bgneal@312 5520 }
bgneal@312 5521
bgneal@312 5522 while ( parts.length ) {
bgneal@312 5523 cur = parts.pop();
bgneal@312 5524 pop = cur;
bgneal@312 5525
bgneal@312 5526 if ( !Expr.relative[ cur ] ) {
bgneal@312 5527 cur = "";
bgneal@312 5528 } else {
bgneal@312 5529 pop = parts.pop();
bgneal@312 5530 }
bgneal@312 5531
bgneal@312 5532 if ( pop == null ) {
bgneal@312 5533 pop = context;
bgneal@312 5534 }
bgneal@312 5535
bgneal@312 5536 Expr.relative[ cur ]( checkSet, pop, contextXML );
bgneal@312 5537 }
bgneal@312 5538 } else {
bgneal@312 5539 checkSet = parts = [];
bgneal@312 5540 }
bgneal@312 5541 }
bgneal@312 5542
bgneal@312 5543 if ( !checkSet ) {
bgneal@312 5544 checkSet = set;
bgneal@312 5545 }
bgneal@312 5546
bgneal@312 5547 if ( !checkSet ) {
bgneal@312 5548 Sizzle.error( cur || selector );
bgneal@312 5549 }
bgneal@312 5550
bgneal@312 5551 if ( toString.call(checkSet) === "[object Array]" ) {
bgneal@312 5552 if ( !prune ) {
bgneal@312 5553 results.push.apply( results, checkSet );
bgneal@312 5554 } else if ( context && context.nodeType === 1 ) {
bgneal@312 5555 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@312 5556 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
bgneal@312 5557 results.push( set[i] );
bgneal@312 5558 }
bgneal@312 5559 }
bgneal@312 5560 } else {
bgneal@312 5561 for ( i = 0; checkSet[i] != null; i++ ) {
bgneal@312 5562 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
bgneal@312 5563 results.push( set[i] );
bgneal@312 5564 }
bgneal@312 5565 }
bgneal@312 5566 }
bgneal@312 5567 } else {
bgneal@312 5568 makeArray( checkSet, results );
bgneal@312 5569 }
bgneal@312 5570
bgneal@312 5571 if ( extra ) {
bgneal@312 5572 Sizzle( extra, origContext, results, seed );
bgneal@312 5573 Sizzle.uniqueSort( results );
bgneal@312 5574 }
bgneal@312 5575
bgneal@312 5576 return results;
bgneal@312 5577 };
bgneal@312 5578
bgneal@312 5579 Sizzle.uniqueSort = function(results){
bgneal@312 5580 if ( sortOrder ) {
bgneal@312 5581 hasDuplicate = baseHasDuplicate;
bgneal@312 5582 results.sort(sortOrder);
bgneal@312 5583
bgneal@312 5584 if ( hasDuplicate ) {
bgneal@312 5585 for ( var i = 1; i < results.length; i++ ) {
bgneal@312 5586 if ( results[i] === results[i-1] ) {
bgneal@312 5587 results.splice(i--, 1);
bgneal@312 5588 }
bgneal@312 5589 }
bgneal@312 5590 }
bgneal@312 5591 }
bgneal@312 5592
bgneal@312 5593 return results;
bgneal@312 5594 };
bgneal@312 5595
bgneal@312 5596 Sizzle.matches = function(expr, set){
bgneal@312 5597 return Sizzle(expr, null, null, set);
bgneal@312 5598 };
bgneal@312 5599
bgneal@312 5600 Sizzle.find = function(expr, context, isXML){
bgneal@312 5601 var set;
bgneal@312 5602
bgneal@312 5603 if ( !expr ) {
bgneal@312 5604 return [];
bgneal@312 5605 }
bgneal@312 5606
bgneal@312 5607 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
bgneal@312 5608 var type = Expr.order[i], match;
bgneal@312 5609
bgneal@312 5610 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
bgneal@312 5611 var left = match[1];
bgneal@312 5612 match.splice(1,1);
bgneal@312 5613
bgneal@312 5614 if ( left.substr( left.length - 1 ) !== "\\" ) {
bgneal@312 5615 match[1] = (match[1] || "").replace(/\\/g, "");
bgneal@312 5616 set = Expr.find[ type ]( match, context, isXML );
bgneal@312 5617 if ( set != null ) {
bgneal@312 5618 expr = expr.replace( Expr.match[ type ], "" );
bgneal@312 5619 break;
bgneal@312 5620 }
bgneal@312 5621 }
bgneal@312 5622 }
bgneal@312 5623 }
bgneal@312 5624
bgneal@312 5625 if ( !set ) {
bgneal@312 5626 set = context.getElementsByTagName("*");
bgneal@312 5627 }
bgneal@312 5628
bgneal@312 5629 return {set: set, expr: expr};
bgneal@312 5630 };
bgneal@312 5631
bgneal@312 5632 Sizzle.filter = function(expr, set, inplace, not){
bgneal@312 5633 var old = expr, result = [], curLoop = set, match, anyFound,
bgneal@312 5634 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
bgneal@312 5635
bgneal@312 5636 while ( expr && set.length ) {
bgneal@312 5637 for ( var type in Expr.filter ) {
bgneal@312 5638 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
bgneal@312 5639 var filter = Expr.filter[ type ], found, item, left = match[1];
bgneal@312 5640 anyFound = false;
bgneal@312 5641
bgneal@312 5642 match.splice(1,1);
bgneal@312 5643
bgneal@312 5644 if ( left.substr( left.length - 1 ) === "\\" ) {
bgneal@312 5645 continue;
bgneal@312 5646 }
bgneal@312 5647
bgneal@312 5648 if ( curLoop === result ) {
bgneal@312 5649 result = [];
bgneal@312 5650 }
bgneal@312 5651
bgneal@312 5652 if ( Expr.preFilter[ type ] ) {
bgneal@312 5653 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
bgneal@312 5654
bgneal@312 5655 if ( !match ) {
bgneal@312 5656 anyFound = found = true;
bgneal@312 5657 } else if ( match === true ) {
bgneal@312 5658 continue;
bgneal@312 5659 }
bgneal@312 5660 }
bgneal@312 5661
bgneal@312 5662 if ( match ) {
bgneal@312 5663 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
bgneal@312 5664 if ( item ) {
bgneal@312 5665 found = filter( item, match, i, curLoop );
bgneal@312 5666 var pass = not ^ !!found;
bgneal@312 5667
bgneal@312 5668 if ( inplace && found != null ) {
bgneal@312 5669 if ( pass ) {
bgneal@312 5670 anyFound = true;
bgneal@312 5671 } else {
bgneal@312 5672 curLoop[i] = false;
bgneal@312 5673 }
bgneal@312 5674 } else if ( pass ) {
bgneal@312 5675 result.push( item );
bgneal@312 5676 anyFound = true;
bgneal@312 5677 }
bgneal@312 5678 }
bgneal@312 5679 }
bgneal@312 5680 }
bgneal@312 5681
bgneal@312 5682 if ( found !== undefined ) {
bgneal@312 5683 if ( !inplace ) {
bgneal@312 5684 curLoop = result;
bgneal@312 5685 }
bgneal@312 5686
bgneal@312 5687 expr = expr.replace( Expr.match[ type ], "" );
bgneal@312 5688
bgneal@312 5689 if ( !anyFound ) {
bgneal@312 5690 return [];
bgneal@312 5691 }
bgneal@312 5692
bgneal@312 5693 break;
bgneal@312 5694 }
bgneal@312 5695 }
bgneal@312 5696 }
bgneal@312 5697
bgneal@312 5698 // Improper expression
bgneal@312 5699 if ( expr === old ) {
bgneal@312 5700 if ( anyFound == null ) {
bgneal@312 5701 Sizzle.error( expr );
bgneal@312 5702 } else {
bgneal@312 5703 break;
bgneal@312 5704 }
bgneal@312 5705 }
bgneal@312 5706
bgneal@312 5707 old = expr;
bgneal@312 5708 }
bgneal@312 5709
bgneal@312 5710 return curLoop;
bgneal@312 5711 };
bgneal@312 5712
bgneal@312 5713 Sizzle.error = function( msg ) {
bgneal@312 5714 throw "Syntax error, unrecognized expression: " + msg;
bgneal@312 5715 };
bgneal@312 5716
bgneal@312 5717 var Expr = Sizzle.selectors = {
bgneal@312 5718 order: [ "ID", "NAME", "TAG" ],
bgneal@312 5719 match: {
bgneal@312 5720 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@312 5721 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
bgneal@312 5722 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
bgneal@312 5723 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
bgneal@312 5724 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
bgneal@312 5725 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
bgneal@312 5726 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
bgneal@312 5727 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
bgneal@312 5728 },
bgneal@312 5729 leftMatch: {},
bgneal@312 5730 attrMap: {
bgneal@312 5731 "class": "className",
bgneal@312 5732 "for": "htmlFor"
bgneal@312 5733 },
bgneal@312 5734 attrHandle: {
bgneal@312 5735 href: function(elem){
bgneal@312 5736 return elem.getAttribute("href");
bgneal@312 5737 }
bgneal@312 5738 },
bgneal@312 5739 relative: {
bgneal@312 5740 "+": function(checkSet, part){
bgneal@312 5741 var isPartStr = typeof part === "string",
bgneal@312 5742 isTag = isPartStr && !/\W/.test(part),
bgneal@312 5743 isPartStrNotTag = isPartStr && !isTag;
bgneal@312 5744
bgneal@312 5745 if ( isTag ) {
bgneal@312 5746 part = part.toLowerCase();
bgneal@312 5747 }
bgneal@312 5748
bgneal@312 5749 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
bgneal@312 5750 if ( (elem = checkSet[i]) ) {
bgneal@312 5751 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
bgneal@312 5752
bgneal@312 5753 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
bgneal@312 5754 elem || false :
bgneal@312 5755 elem === part;
bgneal@312 5756 }
bgneal@312 5757 }
bgneal@312 5758
bgneal@312 5759 if ( isPartStrNotTag ) {
bgneal@312 5760 Sizzle.filter( part, checkSet, true );
bgneal@312 5761 }
bgneal@312 5762 },
bgneal@312 5763 ">": function(checkSet, part){
bgneal@312 5764 var isPartStr = typeof part === "string",
bgneal@312 5765 elem, i = 0, l = checkSet.length;
bgneal@312 5766
bgneal@312 5767 if ( isPartStr && !/\W/.test(part) ) {
bgneal@312 5768 part = part.toLowerCase();
bgneal@312 5769
bgneal@312 5770 for ( ; i < l; i++ ) {
bgneal@312 5771 elem = checkSet[i];
bgneal@312 5772 if ( elem ) {
bgneal@312 5773 var parent = elem.parentNode;
bgneal@312 5774 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
bgneal@312 5775 }
bgneal@312 5776 }
bgneal@312 5777 } else {
bgneal@312 5778 for ( ; i < l; i++ ) {
bgneal@312 5779 elem = checkSet[i];
bgneal@312 5780 if ( elem ) {
bgneal@312 5781 checkSet[i] = isPartStr ?
bgneal@312 5782 elem.parentNode :
bgneal@312 5783 elem.parentNode === part;
bgneal@312 5784 }
bgneal@312 5785 }
bgneal@312 5786
bgneal@312 5787 if ( isPartStr ) {
bgneal@312 5788 Sizzle.filter( part, checkSet, true );
bgneal@312 5789 }
bgneal@312 5790 }
bgneal@312 5791 },
bgneal@312 5792 "": function(checkSet, part, isXML){
bgneal@312 5793 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@312 5794
bgneal@312 5795 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@312 5796 part = part.toLowerCase();
bgneal@312 5797 nodeCheck = part;
bgneal@312 5798 checkFn = dirNodeCheck;
bgneal@312 5799 }
bgneal@312 5800
bgneal@312 5801 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
bgneal@312 5802 },
bgneal@312 5803 "~": function(checkSet, part, isXML){
bgneal@312 5804 var doneName = done++, checkFn = dirCheck, nodeCheck;
bgneal@312 5805
bgneal@312 5806 if ( typeof part === "string" && !/\W/.test(part) ) {
bgneal@312 5807 part = part.toLowerCase();
bgneal@312 5808 nodeCheck = part;
bgneal@312 5809 checkFn = dirNodeCheck;
bgneal@312 5810 }
bgneal@312 5811
bgneal@312 5812 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
bgneal@312 5813 }
bgneal@312 5814 },
bgneal@312 5815 find: {
bgneal@312 5816 ID: function(match, context, isXML){
bgneal@312 5817 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@312 5818 var m = context.getElementById(match[1]);
bgneal@312 5819 return m ? [m] : [];
bgneal@312 5820 }
bgneal@312 5821 },
bgneal@312 5822 NAME: function(match, context){
bgneal@312 5823 if ( typeof context.getElementsByName !== "undefined" ) {
bgneal@312 5824 var ret = [], results = context.getElementsByName(match[1]);
bgneal@312 5825
bgneal@312 5826 for ( var i = 0, l = results.length; i < l; i++ ) {
bgneal@312 5827 if ( results[i].getAttribute("name") === match[1] ) {
bgneal@312 5828 ret.push( results[i] );
bgneal@312 5829 }
bgneal@312 5830 }
bgneal@312 5831
bgneal@312 5832 return ret.length === 0 ? null : ret;
bgneal@312 5833 }
bgneal@312 5834 },
bgneal@312 5835 TAG: function(match, context){
bgneal@312 5836 return context.getElementsByTagName(match[1]);
bgneal@312 5837 }
bgneal@312 5838 },
bgneal@312 5839 preFilter: {
bgneal@312 5840 CLASS: function(match, curLoop, inplace, result, not, isXML){
bgneal@312 5841 match = " " + match[1].replace(/\\/g, "") + " ";
bgneal@312 5842
bgneal@312 5843 if ( isXML ) {
bgneal@312 5844 return match;
bgneal@312 5845 }
bgneal@312 5846
bgneal@312 5847 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
bgneal@312 5848 if ( elem ) {
bgneal@312 5849 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
bgneal@312 5850 if ( !inplace ) {
bgneal@312 5851 result.push( elem );
bgneal@312 5852 }
bgneal@312 5853 } else if ( inplace ) {
bgneal@312 5854 curLoop[i] = false;
bgneal@312 5855 }
bgneal@312 5856 }
bgneal@312 5857 }
bgneal@312 5858
bgneal@312 5859 return false;
bgneal@312 5860 },
bgneal@312 5861 ID: function(match){
bgneal@312 5862 return match[1].replace(/\\/g, "");
bgneal@312 5863 },
bgneal@312 5864 TAG: function(match, curLoop){
bgneal@312 5865 return match[1].toLowerCase();
bgneal@312 5866 },
bgneal@312 5867 CHILD: function(match){
bgneal@312 5868 if ( match[1] === "nth" ) {
bgneal@312 5869 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
bgneal@312 5870 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
bgneal@312 5871 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
bgneal@312 5872 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
bgneal@312 5873
bgneal@312 5874 // calculate the numbers (first)n+(last) including if they are negative
bgneal@312 5875 match[2] = (test[1] + (test[2] || 1)) - 0;
bgneal@312 5876 match[3] = test[3] - 0;
bgneal@312 5877 }
bgneal@312 5878
bgneal@312 5879 // TODO: Move to normal caching system
bgneal@312 5880 match[0] = done++;
bgneal@312 5881
bgneal@312 5882 return match;
bgneal@312 5883 },
bgneal@312 5884 ATTR: function(match, curLoop, inplace, result, not, isXML){
bgneal@312 5885 var name = match[1].replace(/\\/g, "");
bgneal@312 5886
bgneal@312 5887 if ( !isXML && Expr.attrMap[name] ) {
bgneal@312 5888 match[1] = Expr.attrMap[name];
bgneal@312 5889 }
bgneal@312 5890
bgneal@312 5891 if ( match[2] === "~=" ) {
bgneal@312 5892 match[4] = " " + match[4] + " ";
bgneal@312 5893 }
bgneal@312 5894
bgneal@312 5895 return match;
bgneal@312 5896 },
bgneal@312 5897 PSEUDO: function(match, curLoop, inplace, result, not){
bgneal@312 5898 if ( match[1] === "not" ) {
bgneal@312 5899 // If we're dealing with a complex expression, or a simple one
bgneal@312 5900 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
bgneal@312 5901 match[3] = Sizzle(match[3], null, null, curLoop);
bgneal@312 5902 } else {
bgneal@312 5903 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
bgneal@312 5904 if ( !inplace ) {
bgneal@312 5905 result.push.apply( result, ret );
bgneal@312 5906 }
bgneal@312 5907 return false;
bgneal@312 5908 }
bgneal@312 5909 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
bgneal@312 5910 return true;
bgneal@312 5911 }
bgneal@312 5912
bgneal@312 5913 return match;
bgneal@312 5914 },
bgneal@312 5915 POS: function(match){
bgneal@312 5916 match.unshift( true );
bgneal@312 5917 return match;
bgneal@312 5918 }
bgneal@312 5919 },
bgneal@312 5920 filters: {
bgneal@312 5921 enabled: function(elem){
bgneal@312 5922 return elem.disabled === false && elem.type !== "hidden";
bgneal@312 5923 },
bgneal@312 5924 disabled: function(elem){
bgneal@312 5925 return elem.disabled === true;
bgneal@312 5926 },
bgneal@312 5927 checked: function(elem){
bgneal@312 5928 return elem.checked === true;
bgneal@312 5929 },
bgneal@312 5930 selected: function(elem){
bgneal@312 5931 // Accessing this property makes selected-by-default
bgneal@312 5932 // options in Safari work properly
bgneal@312 5933 elem.parentNode.selectedIndex;
bgneal@312 5934 return elem.selected === true;
bgneal@312 5935 },
bgneal@312 5936 parent: function(elem){
bgneal@312 5937 return !!elem.firstChild;
bgneal@312 5938 },
bgneal@312 5939 empty: function(elem){
bgneal@312 5940 return !elem.firstChild;
bgneal@312 5941 },
bgneal@312 5942 has: function(elem, i, match){
bgneal@312 5943 return !!Sizzle( match[3], elem ).length;
bgneal@312 5944 },
bgneal@312 5945 header: function(elem){
bgneal@312 5946 return (/h\d/i).test( elem.nodeName );
bgneal@312 5947 },
bgneal@312 5948 text: function(elem){
bgneal@312 5949 return "text" === elem.type;
bgneal@312 5950 },
bgneal@312 5951 radio: function(elem){
bgneal@312 5952 return "radio" === elem.type;
bgneal@312 5953 },
bgneal@312 5954 checkbox: function(elem){
bgneal@312 5955 return "checkbox" === elem.type;
bgneal@312 5956 },
bgneal@312 5957 file: function(elem){
bgneal@312 5958 return "file" === elem.type;
bgneal@312 5959 },
bgneal@312 5960 password: function(elem){
bgneal@312 5961 return "password" === elem.type;
bgneal@312 5962 },
bgneal@312 5963 submit: function(elem){
bgneal@312 5964 return "submit" === elem.type;
bgneal@312 5965 },
bgneal@312 5966 image: function(elem){
bgneal@312 5967 return "image" === elem.type;
bgneal@312 5968 },
bgneal@312 5969 reset: function(elem){
bgneal@312 5970 return "reset" === elem.type;
bgneal@312 5971 },
bgneal@312 5972 button: function(elem){
bgneal@312 5973 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
bgneal@312 5974 },
bgneal@312 5975 input: function(elem){
bgneal@312 5976 return (/input|select|textarea|button/i).test(elem.nodeName);
bgneal@312 5977 }
bgneal@312 5978 },
bgneal@312 5979 setFilters: {
bgneal@312 5980 first: function(elem, i){
bgneal@312 5981 return i === 0;
bgneal@312 5982 },
bgneal@312 5983 last: function(elem, i, match, array){
bgneal@312 5984 return i === array.length - 1;
bgneal@312 5985 },
bgneal@312 5986 even: function(elem, i){
bgneal@312 5987 return i % 2 === 0;
bgneal@312 5988 },
bgneal@312 5989 odd: function(elem, i){
bgneal@312 5990 return i % 2 === 1;
bgneal@312 5991 },
bgneal@312 5992 lt: function(elem, i, match){
bgneal@312 5993 return i < match[3] - 0;
bgneal@312 5994 },
bgneal@312 5995 gt: function(elem, i, match){
bgneal@312 5996 return i > match[3] - 0;
bgneal@312 5997 },
bgneal@312 5998 nth: function(elem, i, match){
bgneal@312 5999 return match[3] - 0 === i;
bgneal@312 6000 },
bgneal@312 6001 eq: function(elem, i, match){
bgneal@312 6002 return match[3] - 0 === i;
bgneal@312 6003 }
bgneal@312 6004 },
bgneal@312 6005 filter: {
bgneal@312 6006 PSEUDO: function(elem, match, i, array){
bgneal@312 6007 var name = match[1], filter = Expr.filters[ name ];
bgneal@312 6008
bgneal@312 6009 if ( filter ) {
bgneal@312 6010 return filter( elem, i, match, array );
bgneal@312 6011 } else if ( name === "contains" ) {
bgneal@312 6012 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
bgneal@312 6013 } else if ( name === "not" ) {
bgneal@312 6014 var not = match[3];
bgneal@312 6015
bgneal@312 6016 for ( var j = 0, l = not.length; j < l; j++ ) {
bgneal@312 6017 if ( not[j] === elem ) {
bgneal@312 6018 return false;
bgneal@312 6019 }
bgneal@312 6020 }
bgneal@312 6021
bgneal@312 6022 return true;
bgneal@312 6023 } else {
bgneal@312 6024 Sizzle.error( "Syntax error, unrecognized expression: " + name );
bgneal@312 6025 }
bgneal@312 6026 },
bgneal@312 6027 CHILD: function(elem, match){
bgneal@312 6028 var type = match[1], node = elem;
bgneal@312 6029 switch (type) {
bgneal@312 6030 case 'only':
bgneal@312 6031 case 'first':
bgneal@312 6032 while ( (node = node.previousSibling) ) {
bgneal@312 6033 if ( node.nodeType === 1 ) {
bgneal@312 6034 return false;
bgneal@312 6035 }
bgneal@312 6036 }
bgneal@312 6037 if ( type === "first" ) {
bgneal@312 6038 return true;
bgneal@312 6039 }
bgneal@312 6040 node = elem;
bgneal@312 6041 case 'last':
bgneal@312 6042 while ( (node = node.nextSibling) ) {
bgneal@312 6043 if ( node.nodeType === 1 ) {
bgneal@312 6044 return false;
bgneal@312 6045 }
bgneal@312 6046 }
bgneal@312 6047 return true;
bgneal@312 6048 case 'nth':
bgneal@312 6049 var first = match[2], last = match[3];
bgneal@312 6050
bgneal@312 6051 if ( first === 1 && last === 0 ) {
bgneal@312 6052 return true;
bgneal@312 6053 }
bgneal@312 6054
bgneal@312 6055 var doneName = match[0],
bgneal@312 6056 parent = elem.parentNode;
bgneal@312 6057
bgneal@312 6058 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
bgneal@312 6059 var count = 0;
bgneal@312 6060 for ( node = parent.firstChild; node; node = node.nextSibling ) {
bgneal@312 6061 if ( node.nodeType === 1 ) {
bgneal@312 6062 node.nodeIndex = ++count;
bgneal@312 6063 }
bgneal@312 6064 }
bgneal@312 6065 parent.sizcache = doneName;
bgneal@312 6066 }
bgneal@312 6067
bgneal@312 6068 var diff = elem.nodeIndex - last;
bgneal@312 6069 if ( first === 0 ) {
bgneal@312 6070 return diff === 0;
bgneal@312 6071 } else {
bgneal@312 6072 return ( diff % first === 0 && diff / first >= 0 );
bgneal@312 6073 }
bgneal@312 6074 }
bgneal@312 6075 },
bgneal@312 6076 ID: function(elem, match){
bgneal@312 6077 return elem.nodeType === 1 && elem.getAttribute("id") === match;
bgneal@312 6078 },
bgneal@312 6079 TAG: function(elem, match){
bgneal@312 6080 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
bgneal@312 6081 },
bgneal@312 6082 CLASS: function(elem, match){
bgneal@312 6083 return (" " + (elem.className || elem.getAttribute("class")) + " ")
bgneal@312 6084 .indexOf( match ) > -1;
bgneal@312 6085 },
bgneal@312 6086 ATTR: function(elem, match){
bgneal@312 6087 var name = match[1],
bgneal@312 6088 result = Expr.attrHandle[ name ] ?
bgneal@312 6089 Expr.attrHandle[ name ]( elem ) :
bgneal@312 6090 elem[ name ] != null ?
bgneal@312 6091 elem[ name ] :
bgneal@312 6092 elem.getAttribute( name ),
bgneal@312 6093 value = result + "",
bgneal@312 6094 type = match[2],
bgneal@312 6095 check = match[4];
bgneal@312 6096
bgneal@312 6097 return result == null ?
bgneal@312 6098 type === "!=" :
bgneal@312 6099 type === "=" ?
bgneal@312 6100 value === check :
bgneal@312 6101 type === "*=" ?
bgneal@312 6102 value.indexOf(check) >= 0 :
bgneal@312 6103 type === "~=" ?
bgneal@312 6104 (" " + value + " ").indexOf(check) >= 0 :
bgneal@312 6105 !check ?
bgneal@312 6106 value && result !== false :
bgneal@312 6107 type === "!=" ?
bgneal@312 6108 value !== check :
bgneal@312 6109 type === "^=" ?
bgneal@312 6110 value.indexOf(check) === 0 :
bgneal@312 6111 type === "$=" ?
bgneal@312 6112 value.substr(value.length - check.length) === check :
bgneal@312 6113 type === "|=" ?
bgneal@312 6114 value === check || value.substr(0, check.length + 1) === check + "-" :
bgneal@312 6115 false;
bgneal@312 6116 },
bgneal@312 6117 POS: function(elem, match, i, array){
bgneal@312 6118 var name = match[2], filter = Expr.setFilters[ name ];
bgneal@312 6119
bgneal@312 6120 if ( filter ) {
bgneal@312 6121 return filter( elem, i, match, array );
bgneal@312 6122 }
bgneal@312 6123 }
bgneal@312 6124 }
bgneal@312 6125 };
bgneal@312 6126
bgneal@312 6127 var origPOS = Expr.match.POS,
bgneal@312 6128 fescape = function(all, num){
bgneal@312 6129 return "\\" + (num - 0 + 1);
bgneal@312 6130 };
bgneal@312 6131
bgneal@312 6132 for ( var type in Expr.match ) {
bgneal@312 6133 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
bgneal@312 6134 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
bgneal@312 6135 }
bgneal@312 6136
bgneal@312 6137 var makeArray = function(array, results) {
bgneal@312 6138 array = Array.prototype.slice.call( array, 0 );
bgneal@312 6139
bgneal@312 6140 if ( results ) {
bgneal@312 6141 results.push.apply( results, array );
bgneal@312 6142 return results;
bgneal@312 6143 }
bgneal@312 6144
bgneal@312 6145 return array;
bgneal@312 6146 };
bgneal@312 6147
bgneal@312 6148 // Perform a simple check to determine if the browser is capable of
bgneal@312 6149 // converting a NodeList to an array using builtin methods.
bgneal@312 6150 // Also verifies that the returned array holds DOM nodes
bgneal@312 6151 // (which is not the case in the Blackberry browser)
bgneal@312 6152 try {
bgneal@312 6153 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
bgneal@312 6154
bgneal@312 6155 // Provide a fallback method if it does not work
bgneal@312 6156 } catch(e){
bgneal@312 6157 makeArray = function(array, results) {
bgneal@312 6158 var ret = results || [], i = 0;
bgneal@312 6159
bgneal@312 6160 if ( toString.call(array) === "[object Array]" ) {
bgneal@312 6161 Array.prototype.push.apply( ret, array );
bgneal@312 6162 } else {
bgneal@312 6163 if ( typeof array.length === "number" ) {
bgneal@312 6164 for ( var l = array.length; i < l; i++ ) {
bgneal@312 6165 ret.push( array[i] );
bgneal@312 6166 }
bgneal@312 6167 } else {
bgneal@312 6168 for ( ; array[i]; i++ ) {
bgneal@312 6169 ret.push( array[i] );
bgneal@312 6170 }
bgneal@312 6171 }
bgneal@312 6172 }
bgneal@312 6173
bgneal@312 6174 return ret;
bgneal@312 6175 };
bgneal@312 6176 }
bgneal@312 6177
bgneal@312 6178 var sortOrder;
bgneal@312 6179
bgneal@312 6180 if ( document.documentElement.compareDocumentPosition ) {
bgneal@312 6181 sortOrder = function( a, b ) {
bgneal@312 6182 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
bgneal@312 6183 if ( a == b ) {
bgneal@312 6184 hasDuplicate = true;
bgneal@312 6185 }
bgneal@312 6186 return a.compareDocumentPosition ? -1 : 1;
bgneal@312 6187 }
bgneal@312 6188
bgneal@312 6189 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
bgneal@312 6190 if ( ret === 0 ) {
bgneal@312 6191 hasDuplicate = true;
bgneal@312 6192 }
bgneal@312 6193 return ret;
bgneal@312 6194 };
bgneal@312 6195 } else if ( "sourceIndex" in document.documentElement ) {
bgneal@312 6196 sortOrder = function( a, b ) {
bgneal@312 6197 if ( !a.sourceIndex || !b.sourceIndex ) {
bgneal@312 6198 if ( a == b ) {
bgneal@312 6199 hasDuplicate = true;
bgneal@312 6200 }
bgneal@312 6201 return a.sourceIndex ? -1 : 1;
bgneal@312 6202 }
bgneal@312 6203
bgneal@312 6204 var ret = a.sourceIndex - b.sourceIndex;
bgneal@312 6205 if ( ret === 0 ) {
bgneal@312 6206 hasDuplicate = true;
bgneal@312 6207 }
bgneal@312 6208 return ret;
bgneal@312 6209 };
bgneal@312 6210 } else if ( document.createRange ) {
bgneal@312 6211 sortOrder = function( a, b ) {
bgneal@312 6212 if ( !a.ownerDocument || !b.ownerDocument ) {
bgneal@312 6213 if ( a == b ) {
bgneal@312 6214 hasDuplicate = true;
bgneal@312 6215 }
bgneal@312 6216 return a.ownerDocument ? -1 : 1;
bgneal@312 6217 }
bgneal@312 6218
bgneal@312 6219 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
bgneal@312 6220 aRange.setStart(a, 0);
bgneal@312 6221 aRange.setEnd(a, 0);
bgneal@312 6222 bRange.setStart(b, 0);
bgneal@312 6223 bRange.setEnd(b, 0);
bgneal@312 6224 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
bgneal@312 6225 if ( ret === 0 ) {
bgneal@312 6226 hasDuplicate = true;
bgneal@312 6227 }
bgneal@312 6228 return ret;
bgneal@312 6229 };
bgneal@312 6230 }
bgneal@312 6231
bgneal@312 6232 // Utility function for retreiving the text value of an array of DOM nodes
bgneal@312 6233 Sizzle.getText = function( elems ) {
bgneal@312 6234 var ret = "", elem;
bgneal@312 6235
bgneal@312 6236 for ( var i = 0; elems[i]; i++ ) {
bgneal@312 6237 elem = elems[i];
bgneal@312 6238
bgneal@312 6239 // Get the text from text nodes and CDATA nodes
bgneal@312 6240 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
bgneal@312 6241 ret += elem.nodeValue;
bgneal@312 6242
bgneal@312 6243 // Traverse everything else, except comment nodes
bgneal@312 6244 } else if ( elem.nodeType !== 8 ) {
bgneal@312 6245 ret += Sizzle.getText( elem.childNodes );
bgneal@312 6246 }
bgneal@312 6247 }
bgneal@312 6248
bgneal@312 6249 return ret;
bgneal@312 6250 };
bgneal@312 6251
bgneal@312 6252 // Check to see if the browser returns elements by name when
bgneal@312 6253 // querying by getElementById (and provide a workaround)
bgneal@312 6254 (function(){
bgneal@312 6255 // We're going to inject a fake input element with a specified name
bgneal@312 6256 var form = document.createElement("div"),
bgneal@312 6257 id = "script" + (new Date()).getTime();
bgneal@312 6258 form.innerHTML = "<a name='" + id + "'/>";
bgneal@312 6259
bgneal@312 6260 // Inject it into the root element, check its status, and remove it quickly
bgneal@312 6261 var root = document.documentElement;
bgneal@312 6262 root.insertBefore( form, root.firstChild );
bgneal@312 6263
bgneal@312 6264 // The workaround has to do additional checks after a getElementById
bgneal@312 6265 // Which slows things down for other browsers (hence the branching)
bgneal@312 6266 if ( document.getElementById( id ) ) {
bgneal@312 6267 Expr.find.ID = function(match, context, isXML){
bgneal@312 6268 if ( typeof context.getElementById !== "undefined" && !isXML ) {
bgneal@312 6269 var m = context.getElementById(match[1]);
bgneal@312 6270 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
bgneal@312 6271 }
bgneal@312 6272 };
bgneal@312 6273
bgneal@312 6274 Expr.filter.ID = function(elem, match){
bgneal@312 6275 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
bgneal@312 6276 return elem.nodeType === 1 && node && node.nodeValue === match;
bgneal@312 6277 };
bgneal@312 6278 }
bgneal@312 6279
bgneal@312 6280 root.removeChild( form );
bgneal@312 6281 root = form = null; // release memory in IE
bgneal@312 6282 })();
bgneal@312 6283
bgneal@312 6284 (function(){
bgneal@312 6285 // Check to see if the browser returns only elements
bgneal@312 6286 // when doing getElementsByTagName("*")
bgneal@312 6287
bgneal@312 6288 // Create a fake element
bgneal@312 6289 var div = document.createElement("div");
bgneal@312 6290 div.appendChild( document.createComment("") );
bgneal@312 6291
bgneal@312 6292 // Make sure no comments are found
bgneal@312 6293 if ( div.getElementsByTagName("*").length > 0 ) {
bgneal@312 6294 Expr.find.TAG = function(match, context){
bgneal@312 6295 var results = context.getElementsByTagName(match[1]);
bgneal@312 6296
bgneal@312 6297 // Filter out possible comments
bgneal@312 6298 if ( match[1] === "*" ) {
bgneal@312 6299 var tmp = [];
bgneal@312 6300
bgneal@312 6301 for ( var i = 0; results[i]; i++ ) {
bgneal@312 6302 if ( results[i].nodeType === 1 ) {
bgneal@312 6303 tmp.push( results[i] );
bgneal@312 6304 }
bgneal@312 6305 }
bgneal@312 6306
bgneal@312 6307 results = tmp;
bgneal@312 6308 }
bgneal@312 6309
bgneal@312 6310 return results;
bgneal@312 6311 };
bgneal@312 6312 }
bgneal@312 6313
bgneal@312 6314 // Check to see if an attribute returns normalized href attributes
bgneal@312 6315 div.innerHTML = "<a href='#'></a>";
bgneal@312 6316 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
bgneal@312 6317 div.firstChild.getAttribute("href") !== "#" ) {
bgneal@312 6318 Expr.attrHandle.href = function(elem){
bgneal@312 6319 return elem.getAttribute("href", 2);
bgneal@312 6320 };
bgneal@312 6321 }
bgneal@312 6322
bgneal@312 6323 div = null; // release memory in IE
bgneal@312 6324 })();
bgneal@312 6325
bgneal@312 6326 if ( document.querySelectorAll ) {
bgneal@312 6327 (function(){
bgneal@312 6328 var oldSizzle = Sizzle, div = document.createElement("div");
bgneal@312 6329 div.innerHTML = "<p class='TEST'></p>";
bgneal@312 6330
bgneal@312 6331 // Safari can't handle uppercase or unicode characters when
bgneal@312 6332 // in quirks mode.
bgneal@312 6333 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
bgneal@312 6334 return;
bgneal@312 6335 }
bgneal@312 6336
bgneal@312 6337 Sizzle = function(query, context, extra, seed){
bgneal@312 6338 context = context || document;
bgneal@312 6339
bgneal@312 6340 // Only use querySelectorAll on non-XML documents
bgneal@312 6341 // (ID selectors don't work in non-HTML documents)
bgneal@312 6342 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
bgneal@312 6343 try {
bgneal@312 6344 return makeArray( context.querySelectorAll(query), extra );
bgneal@312 6345 } catch(e){}
bgneal@312 6346 }
bgneal@312 6347
bgneal@312 6348 return oldSizzle(query, context, extra, seed);
bgneal@312 6349 };
bgneal@312 6350
bgneal@312 6351 for ( var prop in oldSizzle ) {
bgneal@312 6352 Sizzle[ prop ] = oldSizzle[ prop ];
bgneal@312 6353 }
bgneal@312 6354
bgneal@312 6355 div = null; // release memory in IE
bgneal@312 6356 })();
bgneal@312 6357 }
bgneal@312 6358
bgneal@312 6359 (function(){
bgneal@312 6360 var div = document.createElement("div");
bgneal@312 6361
bgneal@312 6362 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
bgneal@312 6363
bgneal@312 6364 // Opera can't find a second classname (in 9.6)
bgneal@312 6365 // Also, make sure that getElementsByClassName actually exists
bgneal@312 6366 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
bgneal@312 6367 return;
bgneal@312 6368 }
bgneal@312 6369
bgneal@312 6370 // Safari caches class attributes, doesn't catch changes (in 3.2)
bgneal@312 6371 div.lastChild.className = "e";
bgneal@312 6372
bgneal@312 6373 if ( div.getElementsByClassName("e").length === 1 ) {
bgneal@312 6374 return;
bgneal@312 6375 }
bgneal@312 6376
bgneal@312 6377 Expr.order.splice(1, 0, "CLASS");
bgneal@312 6378 Expr.find.CLASS = function(match, context, isXML) {
bgneal@312 6379 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
bgneal@312 6380 return context.getElementsByClassName(match[1]);
bgneal@312 6381 }
bgneal@312 6382 };
bgneal@312 6383
bgneal@312 6384 div = null; // release memory in IE
bgneal@312 6385 })();
bgneal@312 6386
bgneal@312 6387 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@312 6388 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@312 6389 var elem = checkSet[i];
bgneal@312 6390 if ( elem ) {
bgneal@312 6391 elem = elem[dir];
bgneal@312 6392 var match = false;
bgneal@312 6393
bgneal@312 6394 while ( elem ) {
bgneal@312 6395 if ( elem.sizcache === doneName ) {
bgneal@312 6396 match = checkSet[elem.sizset];
bgneal@312 6397 break;
bgneal@312 6398 }
bgneal@312 6399
bgneal@312 6400 if ( elem.nodeType === 1 && !isXML ){
bgneal@312 6401 elem.sizcache = doneName;
bgneal@312 6402 elem.sizset = i;
bgneal@312 6403 }
bgneal@312 6404
bgneal@312 6405 if ( elem.nodeName.toLowerCase() === cur ) {
bgneal@312 6406 match = elem;
bgneal@312 6407 break;
bgneal@312 6408 }
bgneal@312 6409
bgneal@312 6410 elem = elem[dir];
bgneal@312 6411 }
bgneal@312 6412
bgneal@312 6413 checkSet[i] = match;
bgneal@312 6414 }
bgneal@312 6415 }
bgneal@312 6416 }
bgneal@312 6417
bgneal@312 6418 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
bgneal@312 6419 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
bgneal@312 6420 var elem = checkSet[i];
bgneal@312 6421 if ( elem ) {
bgneal@312 6422 elem = elem[dir];
bgneal@312 6423 var match = false;
bgneal@312 6424
bgneal@312 6425 while ( elem ) {
bgneal@312 6426 if ( elem.sizcache === doneName ) {
bgneal@312 6427 match = checkSet[elem.sizset];
bgneal@312 6428 break;
bgneal@312 6429 }
bgneal@312 6430
bgneal@312 6431 if ( elem.nodeType === 1 ) {
bgneal@312 6432 if ( !isXML ) {
bgneal@312 6433 elem.sizcache = doneName;
bgneal@312 6434 elem.sizset = i;
bgneal@312 6435 }
bgneal@312 6436 if ( typeof cur !== "string" ) {
bgneal@312 6437 if ( elem === cur ) {
bgneal@312 6438 match = true;
bgneal@312 6439 break;
bgneal@312 6440 }
bgneal@312 6441
bgneal@312 6442 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
bgneal@312 6443 match = elem;
bgneal@312 6444 break;
bgneal@312 6445 }
bgneal@312 6446 }
bgneal@312 6447
bgneal@312 6448 elem = elem[dir];
bgneal@312 6449 }
bgneal@312 6450
bgneal@312 6451 checkSet[i] = match;
bgneal@312 6452 }
bgneal@312 6453 }
bgneal@312 6454 }
bgneal@312 6455
bgneal@312 6456 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
bgneal@312 6457 return !!(a.compareDocumentPosition(b) & 16);
bgneal@312 6458 } : function(a, b){
bgneal@312 6459 return a !== b && (a.contains ? a.contains(b) : true);
bgneal@312 6460 };
bgneal@312 6461
bgneal@312 6462 Sizzle.isXML = function(elem){
bgneal@312 6463 // documentElement is verified for cases where it doesn't yet exist
bgneal@312 6464 // (such as loading iframes in IE - #4833)
bgneal@312 6465 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
bgneal@312 6466 return documentElement ? documentElement.nodeName !== "HTML" : false;
bgneal@312 6467 };
bgneal@312 6468
bgneal@312 6469 var posProcess = function(selector, context){
bgneal@312 6470 var tmpSet = [], later = "", match,
bgneal@312 6471 root = context.nodeType ? [context] : context;
bgneal@312 6472
bgneal@312 6473 // Position selectors must be done after the filter
bgneal@312 6474 // And so must :not(positional) so we move all PSEUDOs to the end
bgneal@312 6475 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
bgneal@312 6476 later += match[0];
bgneal@312 6477 selector = selector.replace( Expr.match.PSEUDO, "" );
bgneal@312 6478 }
bgneal@312 6479
bgneal@312 6480 selector = Expr.relative[selector] ? selector + "*" : selector;
bgneal@312 6481
bgneal@312 6482 for ( var i = 0, l = root.length; i < l; i++ ) {
bgneal@312 6483 Sizzle( selector, root[i], tmpSet );
bgneal@312 6484 }
bgneal@312 6485
bgneal@312 6486 return Sizzle.filter( later, tmpSet );
bgneal@312 6487 };
bgneal@312 6488
bgneal@312 6489 // EXPOSE
bgneal@312 6490
bgneal@312 6491 window.tinymce.dom.Sizzle = Sizzle;
bgneal@312 6492
bgneal@312 6493 })();
bgneal@312 6494
bgneal@312 6495
bgneal@312 6496 (function(tinymce) {
bgneal@312 6497 // Shorten names
bgneal@312 6498 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
bgneal@312 6499
bgneal@312 6500 tinymce.create('tinymce.dom.EventUtils', {
bgneal@312 6501 EventUtils : function() {
bgneal@312 6502 this.inits = [];
bgneal@312 6503 this.events = [];
bgneal@312 6504 },
bgneal@312 6505
bgneal@312 6506 add : function(o, n, f, s) {
bgneal@312 6507 var cb, t = this, el = t.events, r;
bgneal@312 6508
bgneal@312 6509 if (n instanceof Array) {
bgneal@312 6510 r = [];
bgneal@312 6511
bgneal@312 6512 each(n, function(n) {
bgneal@312 6513 r.push(t.add(o, n, f, s));
bgneal@312 6514 });
bgneal@312 6515
bgneal@312 6516 return r;
bgneal@312 6517 }
bgneal@312 6518
bgneal@312 6519 // Handle array
bgneal@312 6520 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@312 6521 r = [];
bgneal@312 6522
bgneal@312 6523 each(o, function(o) {
bgneal@312 6524 o = DOM.get(o);
bgneal@312 6525 r.push(t.add(o, n, f, s));
bgneal@312 6526 });
bgneal@312 6527
bgneal@312 6528 return r;
bgneal@312 6529 }
bgneal@312 6530
bgneal@312 6531 o = DOM.get(o);
bgneal@312 6532
bgneal@312 6533 if (!o)
bgneal@312 6534 return;
bgneal@312 6535
bgneal@312 6536 // Setup event callback
bgneal@312 6537 cb = function(e) {
bgneal@312 6538 // Is all events disabled
bgneal@312 6539 if (t.disabled)
bgneal@312 6540 return;
bgneal@312 6541
bgneal@312 6542 e = e || window.event;
bgneal@312 6543
bgneal@312 6544 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
bgneal@312 6545 if (e && isIE) {
bgneal@312 6546 if (!e.target)
bgneal@312 6547 e.target = e.srcElement;
bgneal@312 6548
bgneal@312 6549 // Patch in preventDefault, stopPropagation methods for W3C compatibility
bgneal@312 6550 tinymce.extend(e, t._stoppers);
bgneal@312 6551 }
bgneal@312 6552
bgneal@312 6553 if (!s)
bgneal@312 6554 return f(e);
bgneal@312 6555
bgneal@312 6556 return f.call(s, e);
bgneal@312 6557 };
bgneal@312 6558
bgneal@312 6559 if (n == 'unload') {
bgneal@312 6560 tinymce.unloads.unshift({func : cb});
bgneal@312 6561 return cb;
bgneal@312 6562 }
bgneal@312 6563
bgneal@312 6564 if (n == 'init') {
bgneal@312 6565 if (t.domLoaded)
bgneal@312 6566 cb();
bgneal@312 6567 else
bgneal@312 6568 t.inits.push(cb);
bgneal@312 6569
bgneal@312 6570 return cb;
bgneal@312 6571 }
bgneal@312 6572
bgneal@312 6573 // Store away listener reference
bgneal@312 6574 el.push({
bgneal@312 6575 obj : o,
bgneal@312 6576 name : n,
bgneal@312 6577 func : f,
bgneal@312 6578 cfunc : cb,
bgneal@312 6579 scope : s
bgneal@312 6580 });
bgneal@312 6581
bgneal@312 6582 t._add(o, n, cb);
bgneal@312 6583
bgneal@312 6584 return f;
bgneal@312 6585 },
bgneal@312 6586
bgneal@312 6587 remove : function(o, n, f) {
bgneal@312 6588 var t = this, a = t.events, s = false, r;
bgneal@312 6589
bgneal@312 6590 // Handle array
bgneal@312 6591 if (o && o.hasOwnProperty && o instanceof Array) {
bgneal@312 6592 r = [];
bgneal@312 6593
bgneal@312 6594 each(o, function(o) {
bgneal@312 6595 o = DOM.get(o);
bgneal@312 6596 r.push(t.remove(o, n, f));
bgneal@312 6597 });
bgneal@312 6598
bgneal@312 6599 return r;
bgneal@312 6600 }
bgneal@312 6601
bgneal@312 6602 o = DOM.get(o);
bgneal@312 6603
bgneal@312 6604 each(a, function(e, i) {
bgneal@312 6605 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
bgneal@312 6606 a.splice(i, 1);
bgneal@312 6607 t._remove(o, n, e.cfunc);
bgneal@312 6608 s = true;
bgneal@312 6609 return false;
bgneal@312 6610 }
bgneal@312 6611 });
bgneal@312 6612
bgneal@312 6613 return s;
bgneal@312 6614 },
bgneal@312 6615
bgneal@312 6616 clear : function(o) {
bgneal@312 6617 var t = this, a = t.events, i, e;
bgneal@312 6618
bgneal@312 6619 if (o) {
bgneal@312 6620 o = DOM.get(o);
bgneal@312 6621
bgneal@312 6622 for (i = a.length - 1; i >= 0; i--) {
bgneal@312 6623 e = a[i];
bgneal@312 6624
bgneal@312 6625 if (e.obj === o) {
bgneal@312 6626 t._remove(e.obj, e.name, e.cfunc);
bgneal@312 6627 e.obj = e.cfunc = null;
bgneal@312 6628 a.splice(i, 1);
bgneal@312 6629 }
bgneal@312 6630 }
bgneal@312 6631 }
bgneal@312 6632 },
bgneal@312 6633
bgneal@312 6634 cancel : function(e) {
bgneal@312 6635 if (!e)
bgneal@312 6636 return false;
bgneal@312 6637
bgneal@312 6638 this.stop(e);
bgneal@312 6639
bgneal@312 6640 return this.prevent(e);
bgneal@312 6641 },
bgneal@312 6642
bgneal@312 6643 stop : function(e) {
bgneal@312 6644 if (e.stopPropagation)
bgneal@312 6645 e.stopPropagation();
bgneal@312 6646 else
bgneal@312 6647 e.cancelBubble = true;
bgneal@312 6648
bgneal@312 6649 return false;
bgneal@312 6650 },
bgneal@312 6651
bgneal@312 6652 prevent : function(e) {
bgneal@312 6653 if (e.preventDefault)
bgneal@312 6654 e.preventDefault();
bgneal@312 6655 else
bgneal@312 6656 e.returnValue = false;
bgneal@312 6657
bgneal@312 6658 return false;
bgneal@312 6659 },
bgneal@312 6660
bgneal@312 6661 destroy : function() {
bgneal@312 6662 var t = this;
bgneal@312 6663
bgneal@312 6664 each(t.events, function(e, i) {
bgneal@312 6665 t._remove(e.obj, e.name, e.cfunc);
bgneal@312 6666 e.obj = e.cfunc = null;
bgneal@312 6667 });
bgneal@312 6668
bgneal@312 6669 t.events = [];
bgneal@312 6670 t = null;
bgneal@312 6671 },
bgneal@312 6672
bgneal@312 6673 _add : function(o, n, f) {
bgneal@312 6674 if (o.attachEvent)
bgneal@312 6675 o.attachEvent('on' + n, f);
bgneal@312 6676 else if (o.addEventListener)
bgneal@312 6677 o.addEventListener(n, f, false);
bgneal@312 6678 else
bgneal@312 6679 o['on' + n] = f;
bgneal@312 6680 },
bgneal@312 6681
bgneal@312 6682 _remove : function(o, n, f) {
bgneal@312 6683 if (o) {
bgneal@312 6684 try {
bgneal@312 6685 if (o.detachEvent)
bgneal@312 6686 o.detachEvent('on' + n, f);
bgneal@312 6687 else if (o.removeEventListener)
bgneal@312 6688 o.removeEventListener(n, f, false);
bgneal@312 6689 else
bgneal@312 6690 o['on' + n] = null;
bgneal@312 6691 } catch (ex) {
bgneal@312 6692 // Might fail with permission denined on IE so we just ignore that
bgneal@312 6693 }
bgneal@312 6694 }
bgneal@312 6695 },
bgneal@312 6696
bgneal@312 6697 _pageInit : function(win) {
bgneal@312 6698 var t = this;
bgneal@312 6699
bgneal@312 6700 // Keep it from running more than once
bgneal@312 6701 if (t.domLoaded)
bgneal@312 6702 return;
bgneal@312 6703
bgneal@312 6704 t.domLoaded = true;
bgneal@312 6705
bgneal@312 6706 each(t.inits, function(c) {
bgneal@312 6707 c();
bgneal@312 6708 });
bgneal@312 6709
bgneal@312 6710 t.inits = [];
bgneal@312 6711 },
bgneal@312 6712
bgneal@312 6713 _wait : function(win) {
bgneal@312 6714 var t = this, doc = win.document;
bgneal@312 6715
bgneal@312 6716 // No need since the document is already loaded
bgneal@312 6717 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
bgneal@312 6718 t.domLoaded = 1;
bgneal@312 6719 return;
bgneal@312 6720 }
bgneal@312 6721
bgneal@312 6722 // Use IE method
bgneal@312 6723 if (doc.attachEvent) {
bgneal@312 6724 doc.attachEvent("onreadystatechange", function() {
bgneal@312 6725 if (doc.readyState === "complete") {
bgneal@312 6726 doc.detachEvent("onreadystatechange", arguments.callee);
bgneal@312 6727 t._pageInit(win);
bgneal@312 6728 }
bgneal@312 6729 });
bgneal@312 6730
bgneal@312 6731 if (doc.documentElement.doScroll && win == win.top) {
bgneal@312 6732 (function() {
bgneal@312 6733 if (t.domLoaded)
bgneal@312 6734 return;
bgneal@312 6735
bgneal@312 6736 try {
bgneal@312 6737 // If IE is used, use the trick by Diego Perini
bgneal@312 6738 // http://javascript.nwbox.com/IEContentLoaded/
bgneal@312 6739 doc.documentElement.doScroll("left");
bgneal@312 6740 } catch (ex) {
bgneal@312 6741 setTimeout(arguments.callee, 0);
bgneal@312 6742 return;
bgneal@312 6743 }
bgneal@312 6744
bgneal@312 6745 t._pageInit(win);
bgneal@312 6746 })();
bgneal@312 6747 }
bgneal@312 6748 } else if (doc.addEventListener) {
bgneal@312 6749 t._add(win, 'DOMContentLoaded', function() {
bgneal@312 6750 t._pageInit(win);
bgneal@312 6751 });
bgneal@312 6752 }
bgneal@312 6753
bgneal@312 6754 t._add(win, 'load', function() {
bgneal@312 6755 t._pageInit(win);
bgneal@312 6756 });
bgneal@312 6757 },
bgneal@312 6758
bgneal@312 6759 _stoppers : {
bgneal@442 6760 preventDefault : function() {
bgneal@312 6761 this.returnValue = false;
bgneal@312 6762 },
bgneal@312 6763
bgneal@312 6764 stopPropagation : function() {
bgneal@312 6765 this.cancelBubble = true;
bgneal@312 6766 }
bgneal@312 6767 }
bgneal@312 6768 });
bgneal@312 6769
bgneal@312 6770 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
bgneal@312 6771
bgneal@312 6772 // Dispatch DOM content loaded event for IE and Safari
bgneal@312 6773 Event._wait(window);
bgneal@312 6774
bgneal@312 6775 tinymce.addUnload(function() {
bgneal@312 6776 Event.destroy();
bgneal@312 6777 });
bgneal@312 6778 })(tinymce);
bgneal@312 6779
bgneal@312 6780 (function(tinymce) {
bgneal@312 6781 tinymce.dom.Element = function(id, settings) {
bgneal@312 6782 var t = this, dom, el;
bgneal@312 6783
bgneal@312 6784 t.settings = settings = settings || {};
bgneal@312 6785 t.id = id;
bgneal@312 6786 t.dom = dom = settings.dom || tinymce.DOM;
bgneal@312 6787
bgneal@312 6788 // Only IE leaks DOM references, this is a lot faster
bgneal@312 6789 if (!tinymce.isIE)
bgneal@312 6790 el = dom.get(t.id);
bgneal@312 6791
bgneal@312 6792 tinymce.each(
bgneal@312 6793 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
bgneal@312 6794 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
bgneal@312 6795 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
bgneal@312 6796 'isHidden,setHTML,get').split(/,/)
bgneal@312 6797 , function(k) {
bgneal@312 6798 t[k] = function() {
bgneal@312 6799 var a = [id], i;
bgneal@312 6800
bgneal@312 6801 for (i = 0; i < arguments.length; i++)
bgneal@312 6802 a.push(arguments[i]);
bgneal@312 6803
bgneal@312 6804 a = dom[k].apply(dom, a);
bgneal@312 6805 t.update(k);
bgneal@312 6806
bgneal@312 6807 return a;
bgneal@312 6808 };
bgneal@312 6809 });
bgneal@312 6810
bgneal@312 6811 tinymce.extend(t, {
bgneal@312 6812 on : function(n, f, s) {
bgneal@312 6813 return tinymce.dom.Event.add(t.id, n, f, s);
bgneal@312 6814 },
bgneal@312 6815
bgneal@312 6816 getXY : function() {
bgneal@312 6817 return {
bgneal@312 6818 x : parseInt(t.getStyle('left')),
bgneal@312 6819 y : parseInt(t.getStyle('top'))
bgneal@312 6820 };
bgneal@312 6821 },
bgneal@312 6822
bgneal@312 6823 getSize : function() {
bgneal@312 6824 var n = dom.get(t.id);
bgneal@312 6825
bgneal@312 6826 return {
bgneal@312 6827 w : parseInt(t.getStyle('width') || n.clientWidth),
bgneal@312 6828 h : parseInt(t.getStyle('height') || n.clientHeight)
bgneal@312 6829 };
bgneal@312 6830 },
bgneal@312 6831
bgneal@312 6832 moveTo : function(x, y) {
bgneal@312 6833 t.setStyles({left : x, top : y});
bgneal@312 6834 },
bgneal@312 6835
bgneal@312 6836 moveBy : function(x, y) {
bgneal@312 6837 var p = t.getXY();
bgneal@312 6838
bgneal@312 6839 t.moveTo(p.x + x, p.y + y);
bgneal@312 6840 },
bgneal@312 6841
bgneal@312 6842 resizeTo : function(w, h) {
bgneal@312 6843 t.setStyles({width : w, height : h});
bgneal@312 6844 },
bgneal@312 6845
bgneal@312 6846 resizeBy : function(w, h) {
bgneal@312 6847 var s = t.getSize();
bgneal@312 6848
bgneal@312 6849 t.resizeTo(s.w + w, s.h + h);
bgneal@312 6850 },
bgneal@312 6851
bgneal@312 6852 update : function(k) {
bgneal@312 6853 var b;
bgneal@312 6854
bgneal@312 6855 if (tinymce.isIE6 && settings.blocker) {
bgneal@312 6856 k = k || '';
bgneal@312 6857
bgneal@312 6858 // Ignore getters
bgneal@312 6859 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
bgneal@312 6860 return;
bgneal@312 6861
bgneal@312 6862 // Remove blocker on remove
bgneal@312 6863 if (k == 'remove') {
bgneal@312 6864 dom.remove(t.blocker);
bgneal@312 6865 return;
bgneal@312 6866 }
bgneal@312 6867
bgneal@312 6868 if (!t.blocker) {
bgneal@312 6869 t.blocker = dom.uniqueId();
bgneal@312 6870 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
bgneal@312 6871 dom.setStyle(b, 'opacity', 0);
bgneal@312 6872 } else
bgneal@312 6873 b = dom.get(t.blocker);
bgneal@312 6874
bgneal@312 6875 dom.setStyles(b, {
bgneal@312 6876 left : t.getStyle('left', 1),
bgneal@312 6877 top : t.getStyle('top', 1),
bgneal@312 6878 width : t.getStyle('width', 1),
bgneal@312 6879 height : t.getStyle('height', 1),
bgneal@312 6880 display : t.getStyle('display', 1),
bgneal@312 6881 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
bgneal@312 6882 });
bgneal@312 6883 }
bgneal@312 6884 }
bgneal@312 6885 });
bgneal@312 6886 };
bgneal@312 6887 })(tinymce);
bgneal@312 6888
bgneal@312 6889 (function(tinymce) {
bgneal@312 6890 function trimNl(s) {
bgneal@312 6891 return s.replace(/[\n\r]+/g, '');
bgneal@312 6892 };
bgneal@312 6893
bgneal@312 6894 // Shorten names
bgneal@312 6895 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
bgneal@312 6896
bgneal@312 6897 tinymce.create('tinymce.dom.Selection', {
bgneal@312 6898 Selection : function(dom, win, serializer) {
bgneal@312 6899 var t = this;
bgneal@312 6900
bgneal@312 6901 t.dom = dom;
bgneal@312 6902 t.win = win;
bgneal@312 6903 t.serializer = serializer;
bgneal@312 6904
bgneal@312 6905 // Add events
bgneal@312 6906 each([
bgneal@312 6907 'onBeforeSetContent',
bgneal@442 6908
bgneal@312 6909 'onBeforeGetContent',
bgneal@442 6910
bgneal@312 6911 'onSetContent',
bgneal@442 6912
bgneal@312 6913 'onGetContent'
bgneal@312 6914 ], function(e) {
bgneal@312 6915 t[e] = new tinymce.util.Dispatcher(t);
bgneal@312 6916 });
bgneal@312 6917
bgneal@312 6918 // No W3C Range support
bgneal@312 6919 if (!t.win.getSelection)
bgneal@312 6920 t.tridentSel = new tinymce.dom.TridentSelection(t);
bgneal@312 6921
bgneal@442 6922 if (tinymce.isIE && dom.boxModel)
bgneal@442 6923 this._fixIESelection();
bgneal@442 6924
bgneal@312 6925 // Prevent leaks
bgneal@312 6926 tinymce.addUnload(t.destroy, t);
bgneal@312 6927 },
bgneal@312 6928
bgneal@312 6929 getContent : function(s) {
bgneal@312 6930 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
bgneal@312 6931
bgneal@312 6932 s = s || {};
bgneal@312 6933 wb = wa = '';
bgneal@312 6934 s.get = true;
bgneal@312 6935 s.format = s.format || 'html';
bgneal@312 6936 t.onBeforeGetContent.dispatch(t, s);
bgneal@312 6937
bgneal@312 6938 if (s.format == 'text')
bgneal@312 6939 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
bgneal@312 6940
bgneal@312 6941 if (r.cloneContents) {
bgneal@312 6942 n = r.cloneContents();
bgneal@312 6943
bgneal@312 6944 if (n)
bgneal@312 6945 e.appendChild(n);
bgneal@312 6946 } else if (is(r.item) || is(r.htmlText))
bgneal@312 6947 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
bgneal@312 6948 else
bgneal@312 6949 e.innerHTML = r.toString();
bgneal@312 6950
bgneal@312 6951 // Keep whitespace before and after
bgneal@312 6952 if (/^\s/.test(e.innerHTML))
bgneal@312 6953 wb = ' ';
bgneal@312 6954
bgneal@312 6955 if (/\s+$/.test(e.innerHTML))
bgneal@312 6956 wa = ' ';
bgneal@312 6957
bgneal@312 6958 s.getInner = true;
bgneal@312 6959
bgneal@312 6960 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
bgneal@312 6961 t.onGetContent.dispatch(t, s);
bgneal@312 6962
bgneal@312 6963 return s.content;
bgneal@312 6964 },
bgneal@312 6965
bgneal@442 6966 setContent : function(content, args) {
bgneal@442 6967 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
bgneal@442 6968
bgneal@442 6969 args = args || {format : 'html'};
bgneal@442 6970 args.set = true;
bgneal@442 6971 content = args.content = content;
bgneal@312 6972
bgneal@312 6973 // Dispatch before set content event
bgneal@442 6974 if (!args.no_events)
bgneal@442 6975 self.onBeforeSetContent.dispatch(self, args);
bgneal@442 6976
bgneal@442 6977 content = args.content;
bgneal@442 6978
bgneal@442 6979 if (rng.insertNode) {
bgneal@312 6980 // Make caret marker since insertNode places the caret in the beginning of text after insert
bgneal@442 6981 content += '<span id="__caret">_</span>';
bgneal@312 6982
bgneal@312 6983 // Delete and insert new node
bgneal@442 6984 if (rng.startContainer == doc && rng.endContainer == doc) {
bgneal@312 6985 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
bgneal@442 6986 doc.body.innerHTML = content;
bgneal@312 6987 } else {
bgneal@442 6988 rng.deleteContents();
bgneal@442 6989
bgneal@442 6990 if (doc.body.childNodes.length == 0) {
bgneal@442 6991 doc.body.innerHTML = content;
bgneal@312 6992 } else {
bgneal@442 6993 // createContextualFragment doesn't exists in IE 9 DOMRanges
bgneal@442 6994 if (rng.createContextualFragment) {
bgneal@442 6995 rng.insertNode(rng.createContextualFragment(content));
bgneal@442 6996 } else {
bgneal@442 6997 // Fake createContextualFragment call in IE 9
bgneal@442 6998 frag = doc.createDocumentFragment();
bgneal@442 6999 temp = doc.createElement('div');
bgneal@442 7000
bgneal@442 7001 frag.appendChild(temp);
bgneal@442 7002 temp.outerHTML = content;
bgneal@442 7003
bgneal@442 7004 rng.insertNode(frag);
bgneal@442 7005 }
bgneal@312 7006 }
bgneal@312 7007 }
bgneal@312 7008
bgneal@312 7009 // Move to caret marker
bgneal@442 7010 caretNode = self.dom.get('__caret');
bgneal@442 7011
bgneal@312 7012 // Make sure we wrap it compleatly, Opera fails with a simple select call
bgneal@442 7013 rng = doc.createRange();
bgneal@442 7014 rng.setStartBefore(caretNode);
bgneal@442 7015 rng.setEndBefore(caretNode);
bgneal@442 7016 self.setRng(rng);
bgneal@312 7017
bgneal@312 7018 // Remove the caret position
bgneal@442 7019 self.dom.remove('__caret');
bgneal@442 7020 self.setRng(rng);
bgneal@312 7021 } else {
bgneal@442 7022 if (rng.item) {
bgneal@312 7023 // Delete content and get caret text selection
bgneal@442 7024 doc.execCommand('Delete', false, null);
bgneal@442 7025 rng = self.getRng();
bgneal@442 7026 }
bgneal@442 7027
bgneal@442 7028 rng.pasteHTML(content);
bgneal@312 7029 }
bgneal@312 7030
bgneal@312 7031 // Dispatch set content event
bgneal@442 7032 if (!args.no_events)
bgneal@442 7033 self.onSetContent.dispatch(self, args);
bgneal@312 7034 },
bgneal@312 7035
bgneal@312 7036 getStart : function() {
bgneal@312 7037 var rng = this.getRng(), startElement, parentElement, checkRng, node;
bgneal@312 7038
bgneal@312 7039 if (rng.duplicate || rng.item) {
bgneal@312 7040 // Control selection, return first item
bgneal@312 7041 if (rng.item)
bgneal@312 7042 return rng.item(0);
bgneal@312 7043
bgneal@312 7044 // Get start element
bgneal@312 7045 checkRng = rng.duplicate();
bgneal@312 7046 checkRng.collapse(1);
bgneal@312 7047 startElement = checkRng.parentElement();
bgneal@312 7048
bgneal@312 7049 // Check if range parent is inside the start element, then return the inner parent element
bgneal@312 7050 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
bgneal@312 7051 parentElement = node = rng.parentElement();
bgneal@312 7052 while (node = node.parentNode) {
bgneal@312 7053 if (node == startElement) {
bgneal@312 7054 startElement = parentElement;
bgneal@312 7055 break;
bgneal@312 7056 }
bgneal@312 7057 }
bgneal@312 7058
bgneal@312 7059 return startElement;
bgneal@312 7060 } else {
bgneal@312 7061 startElement = rng.startContainer;
bgneal@312 7062
bgneal@312 7063 if (startElement.nodeType == 1 && startElement.hasChildNodes())
bgneal@312 7064 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
bgneal@312 7065
bgneal@312 7066 if (startElement && startElement.nodeType == 3)
bgneal@312 7067 return startElement.parentNode;
bgneal@312 7068
bgneal@312 7069 return startElement;
bgneal@312 7070 }
bgneal@312 7071 },
bgneal@312 7072
bgneal@312 7073 getEnd : function() {
bgneal@312 7074 var t = this, r = t.getRng(), e, eo;
bgneal@312 7075
bgneal@312 7076 if (r.duplicate || r.item) {
bgneal@312 7077 if (r.item)
bgneal@312 7078 return r.item(0);
bgneal@312 7079
bgneal@312 7080 r = r.duplicate();
bgneal@312 7081 r.collapse(0);
bgneal@312 7082 e = r.parentElement();
bgneal@312 7083
bgneal@312 7084 if (e && e.nodeName == 'BODY')
bgneal@312 7085 return e.lastChild || e;
bgneal@312 7086
bgneal@312 7087 return e;
bgneal@312 7088 } else {
bgneal@312 7089 e = r.endContainer;
bgneal@312 7090 eo = r.endOffset;
bgneal@312 7091
bgneal@312 7092 if (e.nodeType == 1 && e.hasChildNodes())
bgneal@312 7093 e = e.childNodes[eo > 0 ? eo - 1 : eo];
bgneal@312 7094
bgneal@312 7095 if (e && e.nodeType == 3)
bgneal@312 7096 return e.parentNode;
bgneal@312 7097
bgneal@312 7098 return e;
bgneal@312 7099 }
bgneal@312 7100 },
bgneal@312 7101
bgneal@312 7102 getBookmark : function(type, normalized) {
bgneal@312 7103 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
bgneal@312 7104
bgneal@312 7105 function findIndex(name, element) {
bgneal@312 7106 var index = 0;
bgneal@312 7107
bgneal@312 7108 each(dom.select(name), function(node, i) {
bgneal@312 7109 if (node == element)
bgneal@312 7110 index = i;
bgneal@312 7111 });
bgneal@312 7112
bgneal@312 7113 return index;
bgneal@312 7114 };
bgneal@312 7115
bgneal@312 7116 if (type == 2) {
bgneal@312 7117 function getLocation() {
bgneal@312 7118 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
bgneal@312 7119
bgneal@312 7120 function getPoint(rng, start) {
bgneal@312 7121 var container = rng[start ? 'startContainer' : 'endContainer'],
bgneal@312 7122 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
bgneal@312 7123
bgneal@312 7124 if (container.nodeType == 3) {
bgneal@312 7125 if (normalized) {
bgneal@312 7126 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
bgneal@312 7127 offset += node.nodeValue.length;
bgneal@312 7128 }
bgneal@312 7129
bgneal@312 7130 point.push(offset);
bgneal@312 7131 } else {
bgneal@312 7132 childNodes = container.childNodes;
bgneal@312 7133
bgneal@312 7134 if (offset >= childNodes.length && childNodes.length) {
bgneal@312 7135 after = 1;
bgneal@312 7136 offset = Math.max(0, childNodes.length - 1);
bgneal@312 7137 }
bgneal@312 7138
bgneal@312 7139 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
bgneal@312 7140 }
bgneal@312 7141
bgneal@312 7142 for (; container && container != root; container = container.parentNode)
bgneal@312 7143 point.push(t.dom.nodeIndex(container, normalized));
bgneal@312 7144
bgneal@312 7145 return point;
bgneal@312 7146 };
bgneal@312 7147
bgneal@312 7148 bookmark.start = getPoint(rng, true);
bgneal@312 7149
bgneal@312 7150 if (!t.isCollapsed())
bgneal@312 7151 bookmark.end = getPoint(rng);
bgneal@312 7152
bgneal@312 7153 return bookmark;
bgneal@312 7154 };
bgneal@312 7155
bgneal@312 7156 return getLocation();
bgneal@312 7157 }
bgneal@312 7158
bgneal@312 7159 // Handle simple range
bgneal@312 7160 if (type)
bgneal@312 7161 return {rng : t.getRng()};
bgneal@312 7162
bgneal@312 7163 rng = t.getRng();
bgneal@312 7164 id = dom.uniqueId();
bgneal@312 7165 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
bgneal@312 7166 styles = 'overflow:hidden;line-height:0px';
bgneal@312 7167
bgneal@312 7168 // Explorer method
bgneal@312 7169 if (rng.duplicate || rng.item) {
bgneal@312 7170 // Text selection
bgneal@312 7171 if (!rng.item) {
bgneal@312 7172 rng2 = rng.duplicate();
bgneal@312 7173
bgneal@442 7174 try {
bgneal@442 7175 // Insert start marker
bgneal@442 7176 rng.collapse();
bgneal@442 7177 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
bgneal@442 7178
bgneal@442 7179 // Insert end marker
bgneal@442 7180 if (!collapsed) {
bgneal@442 7181 rng2.collapse(false);
bgneal@442 7182
bgneal@442 7183 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
bgneal@442 7184 rng.moveToElementText(rng2.parentElement());
bgneal@442 7185 if (rng.compareEndPoints('StartToEnd', rng2) == 0)
bgneal@442 7186 rng2.move('character', -1);
bgneal@442 7187
bgneal@442 7188 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
bgneal@442 7189 }
bgneal@442 7190 } catch (ex) {
bgneal@442 7191 // IE might throw unspecified error so lets ignore it
bgneal@442 7192 return null;
bgneal@312 7193 }
bgneal@312 7194 } else {
bgneal@312 7195 // Control selection
bgneal@312 7196 element = rng.item(0);
bgneal@312 7197 name = element.nodeName;
bgneal@312 7198
bgneal@312 7199 return {name : name, index : findIndex(name, element)};
bgneal@312 7200 }
bgneal@312 7201 } else {
bgneal@312 7202 element = t.getNode();
bgneal@312 7203 name = element.nodeName;
bgneal@312 7204 if (name == 'IMG')
bgneal@312 7205 return {name : name, index : findIndex(name, element)};
bgneal@312 7206
bgneal@312 7207 // W3C method
bgneal@312 7208 rng2 = rng.cloneRange();
bgneal@312 7209
bgneal@312 7210 // Insert end marker
bgneal@312 7211 if (!collapsed) {
bgneal@312 7212 rng2.collapse(false);
bgneal@442 7213 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
bgneal@312 7214 }
bgneal@312 7215
bgneal@312 7216 rng.collapse(true);
bgneal@442 7217 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
bgneal@312 7218 }
bgneal@312 7219
bgneal@312 7220 t.moveToBookmark({id : id, keep : 1});
bgneal@312 7221
bgneal@312 7222 return {id : id};
bgneal@312 7223 },
bgneal@312 7224
bgneal@312 7225 moveToBookmark : function(bookmark) {
bgneal@312 7226 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
bgneal@312 7227
bgneal@312 7228 // Clear selection cache
bgneal@312 7229 if (t.tridentSel)
bgneal@312 7230 t.tridentSel.destroy();
bgneal@312 7231
bgneal@312 7232 if (bookmark) {
bgneal@312 7233 if (bookmark.start) {
bgneal@312 7234 rng = dom.createRng();
bgneal@312 7235 root = dom.getRoot();
bgneal@312 7236
bgneal@312 7237 function setEndPoint(start) {
bgneal@312 7238 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
bgneal@312 7239
bgneal@312 7240 if (point) {
bgneal@442 7241 offset = point[0];
bgneal@442 7242
bgneal@312 7243 // Find container node
bgneal@312 7244 for (node = root, i = point.length - 1; i >= 1; i--) {
bgneal@312 7245 children = node.childNodes;
bgneal@312 7246
bgneal@442 7247 if (point[i] > children.length - 1)
bgneal@442 7248 return;
bgneal@442 7249
bgneal@442 7250 node = children[point[i]];
bgneal@312 7251 }
bgneal@312 7252
bgneal@442 7253 // Move text offset to best suitable location
bgneal@442 7254 if (node.nodeType === 3)
bgneal@442 7255 offset = Math.min(point[0], node.nodeValue.length);
bgneal@442 7256
bgneal@442 7257 // Move element offset to best suitable location
bgneal@442 7258 if (node.nodeType === 1)
bgneal@442 7259 offset = Math.min(point[0], node.childNodes.length);
bgneal@442 7260
bgneal@312 7261 // Set offset within container node
bgneal@312 7262 if (start)
bgneal@442 7263 rng.setStart(node, offset);
bgneal@312 7264 else
bgneal@442 7265 rng.setEnd(node, offset);
bgneal@442 7266 }
bgneal@442 7267
bgneal@442 7268 return true;
bgneal@312 7269 };
bgneal@312 7270
bgneal@442 7271 if (setEndPoint(true) && setEndPoint()) {
bgneal@442 7272 t.setRng(rng);
bgneal@442 7273 }
bgneal@312 7274 } else if (bookmark.id) {
bgneal@312 7275 function restoreEndPoint(suffix) {
bgneal@312 7276 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
bgneal@312 7277
bgneal@312 7278 if (marker) {
bgneal@312 7279 node = marker.parentNode;
bgneal@312 7280
bgneal@312 7281 if (suffix == 'start') {
bgneal@312 7282 if (!keep) {
bgneal@312 7283 idx = dom.nodeIndex(marker);
bgneal@312 7284 } else {
bgneal@312 7285 node = marker.firstChild;
bgneal@312 7286 idx = 1;
bgneal@312 7287 }
bgneal@312 7288
bgneal@312 7289 startContainer = endContainer = node;
bgneal@312 7290 startOffset = endOffset = idx;
bgneal@312 7291 } else {
bgneal@312 7292 if (!keep) {
bgneal@312 7293 idx = dom.nodeIndex(marker);
bgneal@312 7294 } else {
bgneal@312 7295 node = marker.firstChild;
bgneal@312 7296 idx = 1;
bgneal@312 7297 }
bgneal@312 7298
bgneal@312 7299 endContainer = node;
bgneal@312 7300 endOffset = idx;
bgneal@312 7301 }
bgneal@312 7302
bgneal@312 7303 if (!keep) {
bgneal@312 7304 prev = marker.previousSibling;
bgneal@312 7305 next = marker.nextSibling;
bgneal@312 7306
bgneal@312 7307 // Remove all marker text nodes
bgneal@312 7308 each(tinymce.grep(marker.childNodes), function(node) {
bgneal@312 7309 if (node.nodeType == 3)
bgneal@312 7310 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
bgneal@312 7311 });
bgneal@312 7312
bgneal@312 7313 // Remove marker but keep children if for example contents where inserted into the marker
bgneal@312 7314 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
bgneal@312 7315 while (marker = dom.get(bookmark.id + '_' + suffix))
bgneal@312 7316 dom.remove(marker, 1);
bgneal@312 7317
bgneal@442 7318 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
bgneal@442 7319 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
bgneal@442 7320 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
bgneal@312 7321 idx = prev.nodeValue.length;
bgneal@312 7322 prev.appendData(next.nodeValue);
bgneal@312 7323 dom.remove(next);
bgneal@312 7324
bgneal@312 7325 if (suffix == 'start') {
bgneal@312 7326 startContainer = endContainer = prev;
bgneal@312 7327 startOffset = endOffset = idx;
bgneal@312 7328 } else {
bgneal@312 7329 endContainer = prev;
bgneal@312 7330 endOffset = idx;
bgneal@312 7331 }
bgneal@312 7332 }
bgneal@312 7333 }
bgneal@312 7334 }
bgneal@312 7335 };
bgneal@312 7336
bgneal@312 7337 function addBogus(node) {
bgneal@442 7338 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
bgneal@442 7339 if (dom.isBlock(node) && !node.innerHTML)
bgneal@442 7340 node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
bgneal@312 7341
bgneal@312 7342 return node;
bgneal@312 7343 };
bgneal@312 7344
bgneal@312 7345 // Restore start/end points
bgneal@312 7346 restoreEndPoint('start');
bgneal@312 7347 restoreEndPoint('end');
bgneal@312 7348
bgneal@312 7349 if (startContainer) {
bgneal@312 7350 rng = dom.createRng();
bgneal@312 7351 rng.setStart(addBogus(startContainer), startOffset);
bgneal@312 7352 rng.setEnd(addBogus(endContainer), endOffset);
bgneal@312 7353 t.setRng(rng);
bgneal@312 7354 }
bgneal@312 7355 } else if (bookmark.name) {
bgneal@312 7356 t.select(dom.select(bookmark.name)[bookmark.index]);
bgneal@312 7357 } else if (bookmark.rng)
bgneal@312 7358 t.setRng(bookmark.rng);
bgneal@312 7359 }
bgneal@312 7360 },
bgneal@312 7361
bgneal@312 7362 select : function(node, content) {
bgneal@312 7363 var t = this, dom = t.dom, rng = dom.createRng(), idx;
bgneal@312 7364
bgneal@442 7365 if (node) {
bgneal@442 7366 idx = dom.nodeIndex(node);
bgneal@442 7367 rng.setStart(node.parentNode, idx);
bgneal@442 7368 rng.setEnd(node.parentNode, idx + 1);
bgneal@442 7369
bgneal@442 7370 // Find first/last text node or BR element
bgneal@442 7371 if (content) {
bgneal@442 7372 function setPoint(node, start) {
bgneal@442 7373 var walker = new tinymce.dom.TreeWalker(node, node);
bgneal@442 7374
bgneal@442 7375 do {
bgneal@442 7376 // Text node
bgneal@442 7377 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
bgneal@442 7378 if (start)
bgneal@442 7379 rng.setStart(node, 0);
bgneal@442 7380 else
bgneal@442 7381 rng.setEnd(node, node.nodeValue.length);
bgneal@442 7382
bgneal@442 7383 return;
bgneal@442 7384 }
bgneal@442 7385
bgneal@442 7386 // BR element
bgneal@442 7387 if (node.nodeName == 'BR') {
bgneal@442 7388 if (start)
bgneal@442 7389 rng.setStartBefore(node);
bgneal@442 7390 else
bgneal@442 7391 rng.setEndBefore(node);
bgneal@442 7392
bgneal@442 7393 return;
bgneal@442 7394 }
bgneal@442 7395 } while (node = (start ? walker.next() : walker.prev()));
bgneal@442 7396 };
bgneal@442 7397
bgneal@442 7398 setPoint(node, 1);
bgneal@442 7399 setPoint(node);
bgneal@442 7400 }
bgneal@442 7401
bgneal@442 7402 t.setRng(rng);
bgneal@442 7403 }
bgneal@312 7404
bgneal@312 7405 return node;
bgneal@312 7406 },
bgneal@312 7407
bgneal@312 7408 isCollapsed : function() {
bgneal@312 7409 var t = this, r = t.getRng(), s = t.getSel();
bgneal@312 7410
bgneal@312 7411 if (!r || r.item)
bgneal@312 7412 return false;
bgneal@312 7413
bgneal@312 7414 if (r.compareEndPoints)
bgneal@312 7415 return r.compareEndPoints('StartToEnd', r) === 0;
bgneal@312 7416
bgneal@312 7417 return !s || r.collapsed;
bgneal@312 7418 },
bgneal@312 7419
bgneal@442 7420 collapse : function(to_start) {
bgneal@442 7421 var self = this, rng = self.getRng(), node;
bgneal@312 7422
bgneal@312 7423 // Control range on IE
bgneal@442 7424 if (rng.item) {
bgneal@442 7425 node = rng.item(0);
bgneal@442 7426 rng = self.win.document.body.createTextRange();
bgneal@442 7427 rng.moveToElementText(node);
bgneal@442 7428 }
bgneal@442 7429
bgneal@442 7430 rng.collapse(!!to_start);
bgneal@442 7431 self.setRng(rng);
bgneal@312 7432 },
bgneal@312 7433
bgneal@312 7434 getSel : function() {
bgneal@312 7435 var t = this, w = this.win;
bgneal@312 7436
bgneal@312 7437 return w.getSelection ? w.getSelection() : w.document.selection;
bgneal@312 7438 },
bgneal@312 7439
bgneal@312 7440 getRng : function(w3c) {
bgneal@442 7441 var t = this, s, r, elm, doc = t.win.document;
bgneal@312 7442
bgneal@312 7443 // Found tridentSel object then we need to use that one
bgneal@312 7444 if (w3c && t.tridentSel)
bgneal@312 7445 return t.tridentSel.getRangeAt(0);
bgneal@312 7446
bgneal@312 7447 try {
bgneal@312 7448 if (s = t.getSel())
bgneal@442 7449 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
bgneal@312 7450 } catch (ex) {
bgneal@312 7451 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
bgneal@312 7452 }
bgneal@312 7453
bgneal@442 7454 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
bgneal@442 7455 if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
bgneal@442 7456 elm = doc.selection.createRange().item(0);
bgneal@442 7457 r = doc.createRange();
bgneal@442 7458 r.setStartBefore(elm);
bgneal@442 7459 r.setEndAfter(elm);
bgneal@442 7460 }
bgneal@442 7461
bgneal@312 7462 // No range found then create an empty one
bgneal@312 7463 // This can occur when the editor is placed in a hidden container element on Gecko
bgneal@312 7464 // Or on IE when there was an exception
bgneal@312 7465 if (!r)
bgneal@442 7466 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
bgneal@312 7467
bgneal@312 7468 if (t.selectedRange && t.explicitRange) {
bgneal@312 7469 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
bgneal@312 7470 // Safari, Opera and Chrome only ever select text which causes the range to change.
bgneal@312 7471 // This lets us use the originally set range if the selection hasn't been changed by the user.
bgneal@312 7472 r = t.explicitRange;
bgneal@312 7473 } else {
bgneal@312 7474 t.selectedRange = null;
bgneal@312 7475 t.explicitRange = null;
bgneal@312 7476 }
bgneal@312 7477 }
bgneal@442 7478
bgneal@312 7479 return r;
bgneal@312 7480 },
bgneal@312 7481
bgneal@312 7482 setRng : function(r) {
bgneal@312 7483 var s, t = this;
bgneal@312 7484
bgneal@312 7485 if (!t.tridentSel) {
bgneal@312 7486 s = t.getSel();
bgneal@312 7487
bgneal@312 7488 if (s) {
bgneal@312 7489 t.explicitRange = r;
bgneal@442 7490
bgneal@442 7491 try {
bgneal@442 7492 s.removeAllRanges();
bgneal@442 7493 } catch (ex) {
bgneal@442 7494 // IE9 might throw errors here don't know why
bgneal@442 7495 }
bgneal@442 7496
bgneal@312 7497 s.addRange(r);
bgneal@312 7498 t.selectedRange = s.getRangeAt(0);
bgneal@312 7499 }
bgneal@312 7500 } else {
bgneal@312 7501 // Is W3C Range
bgneal@312 7502 if (r.cloneRange) {
bgneal@312 7503 t.tridentSel.addRange(r);
bgneal@312 7504 return;
bgneal@312 7505 }
bgneal@312 7506
bgneal@312 7507 // Is IE specific range
bgneal@312 7508 try {
bgneal@312 7509 r.select();
bgneal@312 7510 } catch (ex) {
bgneal@312 7511 // Needed for some odd IE bug #1843306
bgneal@312 7512 }
bgneal@312 7513 }
bgneal@312 7514 },
bgneal@312 7515
bgneal@312 7516 setNode : function(n) {
bgneal@312 7517 var t = this;
bgneal@312 7518
bgneal@312 7519 t.setContent(t.dom.getOuterHTML(n));
bgneal@312 7520
bgneal@312 7521 return n;
bgneal@312 7522 },
bgneal@312 7523
bgneal@312 7524 getNode : function() {
bgneal@442 7525 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
bgneal@442 7526
bgneal@442 7527 // Range maybe lost after the editor is made visible again
bgneal@442 7528 if (!rng)
bgneal@442 7529 return t.dom.getRoot();
bgneal@312 7530
bgneal@312 7531 if (rng.setStart) {
bgneal@312 7532 elm = rng.commonAncestorContainer;
bgneal@312 7533
bgneal@312 7534 // Handle selection a image or other control like element such as anchors
bgneal@312 7535 if (!rng.collapsed) {
bgneal@312 7536 if (rng.startContainer == rng.endContainer) {
bgneal@442 7537 if (rng.endOffset - rng.startOffset < 2) {
bgneal@312 7538 if (rng.startContainer.hasChildNodes())
bgneal@312 7539 elm = rng.startContainer.childNodes[rng.startOffset];
bgneal@312 7540 }
bgneal@312 7541 }
bgneal@312 7542
bgneal@312 7543 // If the anchor node is a element instead of a text node then return this element
bgneal@442 7544 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
bgneal@442 7545 // return sel.anchorNode.childNodes[sel.anchorOffset];
bgneal@442 7546
bgneal@442 7547 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
bgneal@442 7548 // This happens when you double click an underlined word in FireFox.
bgneal@442 7549 if (start.nodeType === 3 && end.nodeType === 3) {
bgneal@442 7550 function skipEmptyTextNodes(n, forwards) {
bgneal@442 7551 var orig = n;
bgneal@442 7552 while (n && n.nodeType === 3 && n.length === 0) {
bgneal@442 7553 n = forwards ? n.nextSibling : n.previousSibling;
bgneal@442 7554 }
bgneal@442 7555 return n || orig;
bgneal@442 7556 }
bgneal@442 7557 if (start.length === rng.startOffset) {
bgneal@442 7558 start = skipEmptyTextNodes(start.nextSibling, true);
bgneal@442 7559 } else {
bgneal@442 7560 start = start.parentNode;
bgneal@442 7561 }
bgneal@442 7562 if (rng.endOffset === 0) {
bgneal@442 7563 end = skipEmptyTextNodes(end.previousSibling, false);
bgneal@442 7564 } else {
bgneal@442 7565 end = end.parentNode;
bgneal@442 7566 }
bgneal@442 7567
bgneal@442 7568 if (start && start === end)
bgneal@442 7569 return start;
bgneal@442 7570 }
bgneal@312 7571 }
bgneal@312 7572
bgneal@312 7573 if (elm && elm.nodeType == 3)
bgneal@312 7574 return elm.parentNode;
bgneal@312 7575
bgneal@312 7576 return elm;
bgneal@312 7577 }
bgneal@312 7578
bgneal@312 7579 return rng.item ? rng.item(0) : rng.parentElement();
bgneal@312 7580 },
bgneal@312 7581
bgneal@312 7582 getSelectedBlocks : function(st, en) {
bgneal@312 7583 var t = this, dom = t.dom, sb, eb, n, bl = [];
bgneal@312 7584
bgneal@312 7585 sb = dom.getParent(st || t.getStart(), dom.isBlock);
bgneal@312 7586 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
bgneal@312 7587
bgneal@312 7588 if (sb)
bgneal@312 7589 bl.push(sb);
bgneal@312 7590
bgneal@312 7591 if (sb && eb && sb != eb) {
bgneal@312 7592 n = sb;
bgneal@312 7593
bgneal@312 7594 while ((n = n.nextSibling) && n != eb) {
bgneal@312 7595 if (dom.isBlock(n))
bgneal@312 7596 bl.push(n);
bgneal@312 7597 }
bgneal@312 7598 }
bgneal@312 7599
bgneal@312 7600 if (eb && sb != eb)
bgneal@312 7601 bl.push(eb);
bgneal@312 7602
bgneal@312 7603 return bl;
bgneal@312 7604 },
bgneal@312 7605
bgneal@312 7606 destroy : function(s) {
bgneal@312 7607 var t = this;
bgneal@312 7608
bgneal@312 7609 t.win = null;
bgneal@312 7610
bgneal@312 7611 if (t.tridentSel)
bgneal@312 7612 t.tridentSel.destroy();
bgneal@312 7613
bgneal@312 7614 // Manual destroy then remove unload handler
bgneal@312 7615 if (!s)
bgneal@312 7616 tinymce.removeUnload(t.destroy);
bgneal@442 7617 },
bgneal@442 7618
bgneal@442 7619 // 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@442 7620 _fixIESelection : function() {
bgneal@442 7621 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
bgneal@442 7622
bgneal@442 7623 // Make HTML element unselectable since we are going to handle selection by hand
bgneal@442 7624 doc.documentElement.unselectable = true;
bgneal@442 7625
bgneal@442 7626 // Return range from point or null if it failed
bgneal@442 7627 function rngFromPoint(x, y) {
bgneal@442 7628 var rng = body.createTextRange();
bgneal@442 7629
bgneal@442 7630 try {
bgneal@442 7631 rng.moveToPoint(x, y);
bgneal@442 7632 } catch (ex) {
bgneal@442 7633 // IE sometimes throws and exception, so lets just ignore it
bgneal@442 7634 rng = null;
bgneal@442 7635 }
bgneal@442 7636
bgneal@442 7637 return rng;
bgneal@442 7638 };
bgneal@442 7639
bgneal@442 7640 // Fires while the selection is changing
bgneal@442 7641 function selectionChange(e) {
bgneal@442 7642 var pointRng;
bgneal@442 7643
bgneal@442 7644 // Check if the button is down or not
bgneal@442 7645 if (e.button) {
bgneal@442 7646 // Create range from mouse position
bgneal@442 7647 pointRng = rngFromPoint(e.x, e.y);
bgneal@442 7648
bgneal@442 7649 if (pointRng) {
bgneal@442 7650 // Check if pointRange is before/after selection then change the endPoint
bgneal@442 7651 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
bgneal@442 7652 pointRng.setEndPoint('StartToStart', startRng);
bgneal@442 7653 else
bgneal@442 7654 pointRng.setEndPoint('EndToEnd', startRng);
bgneal@442 7655
bgneal@442 7656 pointRng.select();
bgneal@442 7657 }
bgneal@442 7658 } else
bgneal@442 7659 endSelection();
bgneal@442 7660 }
bgneal@442 7661
bgneal@442 7662 // Removes listeners
bgneal@442 7663 function endSelection() {
bgneal@442 7664 var rng = doc.selection.createRange();
bgneal@442 7665
bgneal@442 7666 // If the range is collapsed then use the last start range
bgneal@442 7667 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
bgneal@442 7668 startRng.select();
bgneal@442 7669
bgneal@442 7670 dom.unbind(doc, 'mouseup', endSelection);
bgneal@442 7671 dom.unbind(doc, 'mousemove', selectionChange);
bgneal@442 7672 startRng = started = 0;
bgneal@442 7673 };
bgneal@442 7674
bgneal@442 7675 // Detect when user selects outside BODY
bgneal@442 7676 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
bgneal@442 7677 if (e.target.nodeName === 'HTML') {
bgneal@442 7678 if (started)
bgneal@442 7679 endSelection();
bgneal@442 7680
bgneal@442 7681 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
bgneal@442 7682 htmlElm = doc.documentElement;
bgneal@442 7683 if (htmlElm.scrollHeight > htmlElm.clientHeight)
bgneal@442 7684 return;
bgneal@442 7685
bgneal@442 7686 started = 1;
bgneal@442 7687 // Setup start position
bgneal@442 7688 startRng = rngFromPoint(e.x, e.y);
bgneal@442 7689 if (startRng) {
bgneal@442 7690 // Listen for selection change events
bgneal@442 7691 dom.bind(doc, 'mouseup', endSelection);
bgneal@442 7692 dom.bind(doc, 'mousemove', selectionChange);
bgneal@442 7693
bgneal@442 7694 dom.win.focus();
bgneal@442 7695 startRng.select();
bgneal@442 7696 }
bgneal@442 7697 }
bgneal@442 7698 });
bgneal@312 7699 }
bgneal@312 7700 });
bgneal@312 7701 })(tinymce);
bgneal@312 7702
bgneal@312 7703 (function(tinymce) {
bgneal@442 7704 tinymce.dom.Serializer = function(settings, dom, schema) {
bgneal@442 7705 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
bgneal@442 7706
bgneal@442 7707 // Support the old apply_source_formatting option
bgneal@442 7708 if (!settings.apply_source_formatting)
bgneal@442 7709 settings.indent = false;
bgneal@442 7710
bgneal@442 7711 settings.remove_trailing_brs = true;
bgneal@442 7712
bgneal@442 7713 // Default DOM and Schema if they are undefined
bgneal@442 7714 dom = dom || tinymce.DOM;
bgneal@442 7715 schema = schema || new tinymce.html.Schema(settings);
bgneal@442 7716 settings.entity_encoding = settings.entity_encoding || 'named';
bgneal@442 7717
bgneal@442 7718 onPreProcess = new tinymce.util.Dispatcher(self);
bgneal@442 7719
bgneal@442 7720 onPostProcess = new tinymce.util.Dispatcher(self);
bgneal@442 7721
bgneal@442 7722 htmlParser = new tinymce.html.DomParser(settings, schema);
bgneal@442 7723
bgneal@442 7724 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
bgneal@442 7725 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
bgneal@442 7726 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
bgneal@442 7727
bgneal@442 7728 while (i--) {
bgneal@442 7729 node = nodes[i];
bgneal@442 7730
bgneal@442 7731 value = node.attributes.map[internalName];
bgneal@442 7732 if (value !== undef) {
bgneal@442 7733 // Set external name to internal value and remove internal
bgneal@442 7734 node.attr(name, value.length > 0 ? value : null);
bgneal@442 7735 node.attr(internalName, null);
bgneal@442 7736 } else {
bgneal@442 7737 // No internal attribute found then convert the value we have in the DOM
bgneal@442 7738 value = node.attributes.map[name];
bgneal@442 7739
bgneal@442 7740 if (name === "style")
bgneal@442 7741 value = dom.serializeStyle(dom.parseStyle(value), node.name);
bgneal@442 7742 else if (urlConverter)
bgneal@442 7743 value = urlConverter.call(urlConverterScope, value, name, node.name);
bgneal@442 7744
bgneal@442 7745 node.attr(name, value.length > 0 ? value : null);
bgneal@442 7746 }
bgneal@442 7747 }
bgneal@442 7748 });
bgneal@442 7749
bgneal@442 7750 // Remove internal classes mceItem<..>
bgneal@442 7751 htmlParser.addAttributeFilter('class', function(nodes, name) {
bgneal@442 7752 var i = nodes.length, node, value;
bgneal@442 7753
bgneal@442 7754 while (i--) {
bgneal@442 7755 node = nodes[i];
bgneal@442 7756 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
bgneal@442 7757 node.attr('class', value.length > 0 ? value : null);
bgneal@442 7758 }
bgneal@442 7759 });
bgneal@442 7760
bgneal@442 7761 // Remove bookmark elements
bgneal@442 7762 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
bgneal@442 7763 var i = nodes.length, node;
bgneal@442 7764
bgneal@442 7765 while (i--) {
bgneal@442 7766 node = nodes[i];
bgneal@442 7767
bgneal@442 7768 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
bgneal@442 7769 node.remove();
bgneal@442 7770 }
bgneal@442 7771 });
bgneal@442 7772
bgneal@442 7773 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
bgneal@442 7774 htmlParser.addNodeFilter('script,style', function(nodes, name) {
bgneal@442 7775 var i = nodes.length, node, value;
bgneal@442 7776
bgneal@442 7777 function trim(value) {
bgneal@442 7778 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
bgneal@442 7779 .replace(/^[\r\n]*|[\r\n]*$/g, '')
bgneal@442 7780 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
bgneal@442 7781 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
bgneal@442 7782 };
bgneal@442 7783
bgneal@442 7784 while (i--) {
bgneal@442 7785 node = nodes[i];
bgneal@442 7786 value = node.firstChild ? node.firstChild.value : '';
bgneal@442 7787
bgneal@442 7788 if (name === "script") {
bgneal@442 7789 // Remove mce- prefix from script elements
bgneal@442 7790 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
bgneal@442 7791
bgneal@442 7792 if (value.length > 0)
bgneal@442 7793 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
bgneal@442 7794 } else {
bgneal@442 7795 if (value.length > 0)
bgneal@442 7796 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
bgneal@442 7797 }
bgneal@442 7798 }
bgneal@442 7799 });
bgneal@442 7800
bgneal@442 7801 // Convert comments to cdata and handle protected comments
bgneal@442 7802 htmlParser.addNodeFilter('#comment', function(nodes, name) {
bgneal@442 7803 var i = nodes.length, node;
bgneal@442 7804
bgneal@442 7805 while (i--) {
bgneal@442 7806 node = nodes[i];
bgneal@442 7807
bgneal@442 7808 if (node.value.indexOf('[CDATA[') === 0) {
bgneal@442 7809 node.name = '#cdata';
bgneal@442 7810 node.type = 4;
bgneal@442 7811 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
bgneal@442 7812 } else if (node.value.indexOf('mce:protected ') === 0) {
bgneal@442 7813 node.name = "#text";
bgneal@442 7814 node.type = 3;
bgneal@442 7815 node.raw = true;
bgneal@442 7816 node.value = unescape(node.value).substr(14);
bgneal@442 7817 }
bgneal@442 7818 }
bgneal@442 7819 });
bgneal@442 7820
bgneal@442 7821 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
bgneal@442 7822 var i = nodes.length, node;
bgneal@442 7823
bgneal@442 7824 while (i--) {
bgneal@442 7825 node = nodes[i];
bgneal@442 7826 if (node.type === 7)
bgneal@442 7827 node.remove();
bgneal@442 7828 else if (node.type === 1) {
bgneal@442 7829 if (name === "input" && !("type" in node.attributes.map))
bgneal@442 7830 node.attr('type', 'text');
bgneal@442 7831 }
bgneal@442 7832 }
bgneal@442 7833 });
bgneal@442 7834
bgneal@442 7835 // Fix list elements, TODO: Replace this later
bgneal@442 7836 if (settings.fix_list_elements) {
bgneal@442 7837 htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
bgneal@442 7838 var i = nodes.length, node, parentNode;
bgneal@442 7839
bgneal@442 7840 while (i--) {
bgneal@442 7841 node = nodes[i];
bgneal@442 7842 parentNode = node.parent;
bgneal@442 7843
bgneal@442 7844 if (parentNode.name === 'ul' || parentNode.name === 'ol') {
bgneal@442 7845 if (node.prev && node.prev.name === 'li') {
bgneal@442 7846 node.prev.append(node);
bgneal@442 7847 }
bgneal@442 7848 }
bgneal@442 7849 }
bgneal@442 7850 });
bgneal@442 7851 }
bgneal@442 7852
bgneal@442 7853 // Remove internal data attributes
bgneal@442 7854 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
bgneal@442 7855 var i = nodes.length;
bgneal@442 7856
bgneal@442 7857 while (i--) {
bgneal@442 7858 nodes[i].attr(name, null);
bgneal@442 7859 }
bgneal@442 7860 });
bgneal@442 7861
bgneal@442 7862 // Return public methods
bgneal@442 7863 return {
bgneal@442 7864 schema : schema,
bgneal@442 7865
bgneal@442 7866 addNodeFilter : htmlParser.addNodeFilter,
bgneal@442 7867
bgneal@442 7868 addAttributeFilter : htmlParser.addAttributeFilter,
bgneal@442 7869
bgneal@442 7870 onPreProcess : onPreProcess,
bgneal@442 7871
bgneal@442 7872 onPostProcess : onPostProcess,
bgneal@442 7873
bgneal@442 7874 serialize : function(node, args) {
bgneal@442 7875 var impl, doc, oldDoc, htmlSerializer, content;
bgneal@442 7876
bgneal@442 7877 // Explorer won't clone contents of script and style and the
bgneal@442 7878 // selected index of select elements are cleared on a clone operation.
bgneal@442 7879 if (isIE && dom.select('script,style,select').length > 0) {
bgneal@442 7880 content = node.innerHTML;
bgneal@442 7881 node = node.cloneNode(false);
bgneal@442 7882 dom.setHTML(node, content);
bgneal@312 7883 } else
bgneal@442 7884 node = node.cloneNode(true);
bgneal@442 7885
bgneal@442 7886 // Nodes needs to be attached to something in WebKit/Opera
bgneal@442 7887 // Older builds of Opera crashes if you attach the node to an document created dynamically
bgneal@442 7888 // and since we can't feature detect a crash we need to sniff the acutal build number
bgneal@442 7889 // This fix will make DOM ranges and make Sizzle happy!
bgneal@442 7890 impl = node.ownerDocument.implementation;
bgneal@442 7891 if (impl.createHTMLDocument) {
bgneal@442 7892 // Create an empty HTML document
bgneal@442 7893 doc = impl.createHTMLDocument("");
bgneal@442 7894
bgneal@442 7895 // Add the element or it's children if it's a body element to the new document
bgneal@442 7896 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
bgneal@442 7897 doc.body.appendChild(doc.importNode(node, true));
bgneal@442 7898 });
bgneal@442 7899
bgneal@442 7900 // Grab first child or body element for serialization
bgneal@442 7901 if (node.nodeName != 'BODY')
bgneal@442 7902 node = doc.body.firstChild;
bgneal@442 7903 else
bgneal@442 7904 node = doc.body;
bgneal@442 7905
bgneal@442 7906 // set the new document in DOMUtils so createElement etc works
bgneal@442 7907 oldDoc = dom.doc;
bgneal@442 7908 dom.doc = doc;
bgneal@442 7909 }
bgneal@442 7910
bgneal@442 7911 args = args || {};
bgneal@442 7912 args.format = args.format || 'html';
bgneal@442 7913
bgneal@442 7914 // Pre process
bgneal@442 7915 if (!args.no_events) {
bgneal@442 7916 args.node = node;
bgneal@442 7917 onPreProcess.dispatch(self, args);
bgneal@442 7918 }
bgneal@442 7919
bgneal@442 7920 // Setup serializer
bgneal@442 7921 htmlSerializer = new tinymce.html.Serializer(settings, schema);
bgneal@442 7922
bgneal@442 7923 // Parse and serialize HTML
bgneal@442 7924 args.content = htmlSerializer.serialize(
bgneal@442 7925 htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
bgneal@442 7926 );
bgneal@442 7927
bgneal@442 7928 // Replace all BOM characters for now until we can find a better solution
bgneal@442 7929 if (!args.cleanup)
bgneal@442 7930 args.content = args.content.replace(/\uFEFF/g, '');
bgneal@442 7931
bgneal@442 7932 // Post process
bgneal@442 7933 if (!args.no_events)
bgneal@442 7934 onPostProcess.dispatch(self, args);
bgneal@442 7935
bgneal@442 7936 // Restore the old document if it was changed
bgneal@442 7937 if (oldDoc)
bgneal@442 7938 dom.doc = oldDoc;
bgneal@442 7939
bgneal@442 7940 args.node = null;
bgneal@442 7941
bgneal@442 7942 return args.content;
bgneal@442 7943 },
bgneal@442 7944
bgneal@442 7945 addRules : function(rules) {
bgneal@442 7946 schema.addValidElements(rules);
bgneal@442 7947 },
bgneal@442 7948
bgneal@442 7949 setRules : function(rules) {
bgneal@442 7950 schema.setValidElements(rules);
bgneal@442 7951 }
bgneal@442 7952 };
bgneal@442 7953 };
bgneal@312 7954 })(tinymce);
bgneal@312 7955 (function(tinymce) {
bgneal@312 7956 tinymce.dom.ScriptLoader = function(settings) {
bgneal@312 7957 var QUEUED = 0,
bgneal@312 7958 LOADING = 1,
bgneal@312 7959 LOADED = 2,
bgneal@312 7960 states = {},
bgneal@312 7961 queue = [],
bgneal@312 7962 scriptLoadedCallbacks = {},
bgneal@312 7963 queueLoadedCallbacks = [],
bgneal@312 7964 loading = 0,
bgneal@312 7965 undefined;
bgneal@312 7966
bgneal@312 7967 function loadScript(url, callback) {
bgneal@312 7968 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
bgneal@312 7969
bgneal@312 7970 // Execute callback when script is loaded
bgneal@312 7971 function done() {
bgneal@312 7972 dom.remove(id);
bgneal@312 7973
bgneal@312 7974 if (elm)
bgneal@312 7975 elm.onreadystatechange = elm.onload = elm = null;
bgneal@312 7976
bgneal@312 7977 callback();
bgneal@312 7978 };
bgneal@442 7979
bgneal@442 7980 function error() {
bgneal@442 7981 // Report the error so it's easier for people to spot loading errors
bgneal@442 7982 if (typeof(console) !== "undefined" && console.log)
bgneal@442 7983 console.log("Failed to load: " + url);
bgneal@442 7984
bgneal@442 7985 // We can't mark it as done if there is a load error since
bgneal@442 7986 // A) We don't want to produce 404 errors on the server and
bgneal@442 7987 // B) the onerror event won't fire on all browsers.
bgneal@442 7988 // done();
bgneal@442 7989 };
bgneal@312 7990
bgneal@312 7991 id = dom.uniqueId();
bgneal@312 7992
bgneal@312 7993 if (tinymce.isIE6) {
bgneal@312 7994 uri = new tinymce.util.URI(url);
bgneal@312 7995 loc = location;
bgneal@312 7996
bgneal@312 7997 // If script is from same domain and we
bgneal@312 7998 // use IE 6 then use XHR since it's more reliable
bgneal@442 7999 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
bgneal@312 8000 tinymce.util.XHR.send({
bgneal@312 8001 url : tinymce._addVer(uri.getURI()),
bgneal@312 8002 success : function(content) {
bgneal@312 8003 // Create new temp script element
bgneal@312 8004 var script = dom.create('script', {
bgneal@312 8005 type : 'text/javascript'
bgneal@312 8006 });
bgneal@312 8007
bgneal@312 8008 // Evaluate script in global scope
bgneal@312 8009 script.text = content;
bgneal@312 8010 document.getElementsByTagName('head')[0].appendChild(script);
bgneal@312 8011 dom.remove(script);
bgneal@312 8012
bgneal@312 8013 done();
bgneal@442 8014 },
bgneal@442 8015
bgneal@442 8016 error : error
bgneal@312 8017 });
bgneal@312 8018
bgneal@312 8019 return;
bgneal@312 8020 }
bgneal@312 8021 }
bgneal@312 8022
bgneal@312 8023 // Create new script element
bgneal@312 8024 elm = dom.create('script', {
bgneal@312 8025 id : id,
bgneal@312 8026 type : 'text/javascript',
bgneal@312 8027 src : tinymce._addVer(url)
bgneal@312 8028 });
bgneal@312 8029
bgneal@442 8030 // Add onload listener for non IE browsers since IE9
bgneal@442 8031 // fires onload event before the script is parsed and executed
bgneal@442 8032 if (!tinymce.isIE)
bgneal@442 8033 elm.onload = done;
bgneal@442 8034
bgneal@442 8035 // Add onerror event will get fired on some browsers but not all of them
bgneal@442 8036 elm.onerror = error;
bgneal@442 8037
bgneal@442 8038 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
bgneal@442 8039 if (!tinymce.isOpera) {
bgneal@442 8040 elm.onreadystatechange = function() {
bgneal@442 8041 var state = elm.readyState;
bgneal@442 8042
bgneal@442 8043 // Loaded state is passed on IE 6 however there
bgneal@442 8044 // are known issues with this method but we can't use
bgneal@442 8045 // XHR in a cross domain loading
bgneal@442 8046 if (state == 'complete' || state == 'loaded')
bgneal@442 8047 done();
bgneal@442 8048 };
bgneal@442 8049 }
bgneal@312 8050
bgneal@312 8051 // Most browsers support this feature so we report errors
bgneal@312 8052 // for those at least to help users track their missing plugins etc
bgneal@312 8053 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
bgneal@312 8054 /*elm.onerror = function() {
bgneal@312 8055 alert('Failed to load: ' + url);
bgneal@312 8056 };*/
bgneal@312 8057
bgneal@312 8058 // Add script to document
bgneal@312 8059 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
bgneal@312 8060 };
bgneal@312 8061
bgneal@312 8062 this.isDone = function(url) {
bgneal@312 8063 return states[url] == LOADED;
bgneal@312 8064 };
bgneal@312 8065
bgneal@312 8066 this.markDone = function(url) {
bgneal@312 8067 states[url] = LOADED;
bgneal@312 8068 };
bgneal@312 8069
bgneal@312 8070 this.add = this.load = function(url, callback, scope) {
bgneal@312 8071 var item, state = states[url];
bgneal@312 8072
bgneal@312 8073 // Add url to load queue
bgneal@312 8074 if (state == undefined) {
bgneal@312 8075 queue.push(url);
bgneal@312 8076 states[url] = QUEUED;
bgneal@312 8077 }
bgneal@312 8078
bgneal@312 8079 if (callback) {
bgneal@312 8080 // Store away callback for later execution
bgneal@312 8081 if (!scriptLoadedCallbacks[url])
bgneal@312 8082 scriptLoadedCallbacks[url] = [];
bgneal@312 8083
bgneal@312 8084 scriptLoadedCallbacks[url].push({
bgneal@312 8085 func : callback,
bgneal@312 8086 scope : scope || this
bgneal@312 8087 });
bgneal@312 8088 }
bgneal@312 8089 };
bgneal@312 8090
bgneal@312 8091 this.loadQueue = function(callback, scope) {
bgneal@312 8092 this.loadScripts(queue, callback, scope);
bgneal@312 8093 };
bgneal@312 8094
bgneal@312 8095 this.loadScripts = function(scripts, callback, scope) {
bgneal@312 8096 var loadScripts;
bgneal@312 8097
bgneal@312 8098 function execScriptLoadedCallbacks(url) {
bgneal@312 8099 // Execute URL callback functions
bgneal@312 8100 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
bgneal@312 8101 callback.func.call(callback.scope);
bgneal@312 8102 });
bgneal@312 8103
bgneal@312 8104 scriptLoadedCallbacks[url] = undefined;
bgneal@312 8105 };
bgneal@312 8106
bgneal@312 8107 queueLoadedCallbacks.push({
bgneal@312 8108 func : callback,
bgneal@312 8109 scope : scope || this
bgneal@312 8110 });
bgneal@312 8111
bgneal@312 8112 loadScripts = function() {
bgneal@312 8113 var loadingScripts = tinymce.grep(scripts);
bgneal@312 8114
bgneal@312 8115 // Current scripts has been handled
bgneal@312 8116 scripts.length = 0;
bgneal@312 8117
bgneal@312 8118 // Load scripts that needs to be loaded
bgneal@312 8119 tinymce.each(loadingScripts, function(url) {
bgneal@312 8120 // Script is already loaded then execute script callbacks directly
bgneal@312 8121 if (states[url] == LOADED) {
bgneal@312 8122 execScriptLoadedCallbacks(url);
bgneal@312 8123 return;
bgneal@312 8124 }
bgneal@312 8125
bgneal@312 8126 // Is script not loading then start loading it
bgneal@312 8127 if (states[url] != LOADING) {
bgneal@312 8128 states[url] = LOADING;
bgneal@312 8129 loading++;
bgneal@312 8130
bgneal@312 8131 loadScript(url, function() {
bgneal@312 8132 states[url] = LOADED;
bgneal@312 8133 loading--;
bgneal@312 8134
bgneal@312 8135 execScriptLoadedCallbacks(url);
bgneal@312 8136
bgneal@312 8137 // Load more scripts if they where added by the recently loaded script
bgneal@312 8138 loadScripts();
bgneal@312 8139 });
bgneal@312 8140 }
bgneal@312 8141 });
bgneal@312 8142
bgneal@312 8143 // No scripts are currently loading then execute all pending queue loaded callbacks
bgneal@312 8144 if (!loading) {
bgneal@312 8145 tinymce.each(queueLoadedCallbacks, function(callback) {
bgneal@312 8146 callback.func.call(callback.scope);
bgneal@312 8147 });
bgneal@312 8148
bgneal@312 8149 queueLoadedCallbacks.length = 0;
bgneal@312 8150 }
bgneal@312 8151 };
bgneal@312 8152
bgneal@312 8153 loadScripts();
bgneal@312 8154 };
bgneal@312 8155 };
bgneal@312 8156
bgneal@312 8157 // Global script loader
bgneal@312 8158 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
bgneal@312 8159 })(tinymce);
bgneal@312 8160
bgneal@312 8161 tinymce.dom.TreeWalker = function(start_node, root_node) {
bgneal@312 8162 var node = start_node;
bgneal@312 8163
bgneal@312 8164 function findSibling(node, start_name, sibling_name, shallow) {
bgneal@312 8165 var sibling, parent;
bgneal@312 8166
bgneal@312 8167 if (node) {
bgneal@312 8168 // Walk into nodes if it has a start
bgneal@312 8169 if (!shallow && node[start_name])
bgneal@312 8170 return node[start_name];
bgneal@312 8171
bgneal@312 8172 // Return the sibling if it has one
bgneal@312 8173 if (node != root_node) {
bgneal@312 8174 sibling = node[sibling_name];
bgneal@312 8175 if (sibling)
bgneal@312 8176 return sibling;
bgneal@312 8177
bgneal@312 8178 // Walk up the parents to look for siblings
bgneal@312 8179 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
bgneal@312 8180 sibling = parent[sibling_name];
bgneal@312 8181 if (sibling)
bgneal@312 8182 return sibling;
bgneal@312 8183 }
bgneal@312 8184 }
bgneal@312 8185 }
bgneal@312 8186 };
bgneal@312 8187
bgneal@312 8188 this.current = function() {
bgneal@312 8189 return node;
bgneal@312 8190 };
bgneal@312 8191
bgneal@312 8192 this.next = function(shallow) {
bgneal@312 8193 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
bgneal@312 8194 };
bgneal@312 8195
bgneal@312 8196 this.prev = function(shallow) {
bgneal@442 8197 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
bgneal@312 8198 };
bgneal@312 8199 };
bgneal@312 8200
bgneal@312 8201 (function(tinymce) {
bgneal@312 8202 tinymce.dom.RangeUtils = function(dom) {
bgneal@312 8203 var INVISIBLE_CHAR = '\uFEFF';
bgneal@312 8204
bgneal@312 8205 this.walk = function(rng, callback) {
bgneal@312 8206 var startContainer = rng.startContainer,
bgneal@312 8207 startOffset = rng.startOffset,
bgneal@312 8208 endContainer = rng.endContainer,
bgneal@312 8209 endOffset = rng.endOffset,
bgneal@312 8210 ancestor, startPoint,
bgneal@312 8211 endPoint, node, parent, siblings, nodes;
bgneal@312 8212
bgneal@312 8213 // Handle table cell selection the table plugin enables
bgneal@312 8214 // you to fake select table cells and perform formatting actions on them
bgneal@312 8215 nodes = dom.select('td.mceSelected,th.mceSelected');
bgneal@312 8216 if (nodes.length > 0) {
bgneal@312 8217 tinymce.each(nodes, function(node) {
bgneal@312 8218 callback([node]);
bgneal@312 8219 });
bgneal@312 8220
bgneal@312 8221 return;
bgneal@312 8222 }
bgneal@312 8223
bgneal@312 8224 function collectSiblings(node, name, end_node) {
bgneal@312 8225 var siblings = [];
bgneal@312 8226
bgneal@312 8227 for (; node && node != end_node; node = node[name])
bgneal@312 8228 siblings.push(node);
bgneal@312 8229
bgneal@312 8230 return siblings;
bgneal@312 8231 };
bgneal@312 8232
bgneal@312 8233 function findEndPoint(node, root) {
bgneal@312 8234 do {
bgneal@312 8235 if (node.parentNode == root)
bgneal@312 8236 return node;
bgneal@312 8237
bgneal@312 8238 node = node.parentNode;
bgneal@312 8239 } while(node);
bgneal@312 8240 };
bgneal@312 8241
bgneal@312 8242 function walkBoundary(start_node, end_node, next) {
bgneal@312 8243 var siblingName = next ? 'nextSibling' : 'previousSibling';
bgneal@312 8244
bgneal@312 8245 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
bgneal@312 8246 parent = node.parentNode;
bgneal@312 8247 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
bgneal@312 8248
bgneal@312 8249 if (siblings.length) {
bgneal@312 8250 if (!next)
bgneal@312 8251 siblings.reverse();
bgneal@312 8252
bgneal@312 8253 callback(siblings);
bgneal@312 8254 }
bgneal@312 8255 }
bgneal@312 8256 };
bgneal@312 8257
bgneal@312 8258 // If index based start position then resolve it
bgneal@312 8259 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
bgneal@312 8260 startContainer = startContainer.childNodes[startOffset];
bgneal@312 8261
bgneal@312 8262 // If index based end position then resolve it
bgneal@312 8263 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
bgneal@442 8264 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
bgneal@312 8265
bgneal@312 8266 // Find common ancestor and end points
bgneal@312 8267 ancestor = dom.findCommonAncestor(startContainer, endContainer);
bgneal@312 8268
bgneal@312 8269 // Same container
bgneal@312 8270 if (startContainer == endContainer)
bgneal@312 8271 return callback([startContainer]);
bgneal@312 8272
bgneal@312 8273 // Process left side
bgneal@312 8274 for (node = startContainer; node; node = node.parentNode) {
bgneal@312 8275 if (node == endContainer)
bgneal@312 8276 return walkBoundary(startContainer, ancestor, true);
bgneal@312 8277
bgneal@312 8278 if (node == ancestor)
bgneal@312 8279 break;
bgneal@312 8280 }
bgneal@312 8281
bgneal@312 8282 // Process right side
bgneal@312 8283 for (node = endContainer; node; node = node.parentNode) {
bgneal@312 8284 if (node == startContainer)
bgneal@312 8285 return walkBoundary(endContainer, ancestor);
bgneal@312 8286
bgneal@312 8287 if (node == ancestor)
bgneal@312 8288 break;
bgneal@312 8289 }
bgneal@312 8290
bgneal@312 8291 // Find start/end point
bgneal@312 8292 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
bgneal@312 8293 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
bgneal@312 8294
bgneal@312 8295 // Walk left leaf
bgneal@312 8296 walkBoundary(startContainer, startPoint, true);
bgneal@312 8297
bgneal@312 8298 // Walk the middle from start to end point
bgneal@312 8299 siblings = collectSiblings(
bgneal@312 8300 startPoint == startContainer ? startPoint : startPoint.nextSibling,
bgneal@312 8301 'nextSibling',
bgneal@312 8302 endPoint == endContainer ? endPoint.nextSibling : endPoint
bgneal@312 8303 );
bgneal@312 8304
bgneal@312 8305 if (siblings.length)
bgneal@312 8306 callback(siblings);
bgneal@312 8307
bgneal@312 8308 // Walk right leaf
bgneal@312 8309 walkBoundary(endContainer, endPoint);
bgneal@312 8310 };
bgneal@312 8311
bgneal@312 8312 /* this.split = function(rng) {
bgneal@312 8313 var startContainer = rng.startContainer,
bgneal@312 8314 startOffset = rng.startOffset,
bgneal@312 8315 endContainer = rng.endContainer,
bgneal@312 8316 endOffset = rng.endOffset;
bgneal@312 8317
bgneal@312 8318 function splitText(node, offset) {
bgneal@312 8319 if (offset == node.nodeValue.length)
bgneal@312 8320 node.appendData(INVISIBLE_CHAR);
bgneal@312 8321
bgneal@312 8322 node = node.splitText(offset);
bgneal@312 8323
bgneal@312 8324 if (node.nodeValue === INVISIBLE_CHAR)
bgneal@312 8325 node.nodeValue = '';
bgneal@312 8326
bgneal@312 8327 return node;
bgneal@312 8328 };
bgneal@312 8329
bgneal@312 8330 // Handle single text node
bgneal@312 8331 if (startContainer == endContainer) {
bgneal@312 8332 if (startContainer.nodeType == 3) {
bgneal@312 8333 if (startOffset != 0)
bgneal@312 8334 startContainer = endContainer = splitText(startContainer, startOffset);
bgneal@312 8335
bgneal@312 8336 if (endOffset - startOffset != startContainer.nodeValue.length)
bgneal@312 8337 splitText(startContainer, endOffset - startOffset);
bgneal@312 8338 }
bgneal@312 8339 } else {
bgneal@312 8340 // Split startContainer text node if needed
bgneal@312 8341 if (startContainer.nodeType == 3 && startOffset != 0) {
bgneal@312 8342 startContainer = splitText(startContainer, startOffset);
bgneal@312 8343 startOffset = 0;
bgneal@312 8344 }
bgneal@312 8345
bgneal@312 8346 // Split endContainer text node if needed
bgneal@312 8347 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
bgneal@312 8348 endContainer = splitText(endContainer, endOffset).previousSibling;
bgneal@312 8349 endOffset = endContainer.nodeValue.length;
bgneal@312 8350 }
bgneal@312 8351 }
bgneal@312 8352
bgneal@312 8353 return {
bgneal@312 8354 startContainer : startContainer,
bgneal@312 8355 startOffset : startOffset,
bgneal@312 8356 endContainer : endContainer,
bgneal@312 8357 endOffset : endOffset
bgneal@312 8358 };
bgneal@312 8359 };
bgneal@312 8360 */
bgneal@312 8361 };
bgneal@312 8362
bgneal@312 8363 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
bgneal@312 8364 if (rng1 && rng2) {
bgneal@312 8365 // Compare native IE ranges
bgneal@312 8366 if (rng1.item || rng1.duplicate) {
bgneal@312 8367 // Both are control ranges and the selected element matches
bgneal@312 8368 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
bgneal@312 8369 return true;
bgneal@312 8370
bgneal@312 8371 // Both are text ranges and the range matches
bgneal@312 8372 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
bgneal@312 8373 return true;
bgneal@312 8374 } else {
bgneal@312 8375 // Compare w3c ranges
bgneal@312 8376 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
bgneal@312 8377 }
bgneal@312 8378 }
bgneal@312 8379
bgneal@312 8380 return false;
bgneal@312 8381 };
bgneal@312 8382 })(tinymce);
bgneal@312 8383
bgneal@312 8384 (function(tinymce) {
bgneal@442 8385 var Event = tinymce.dom.Event, each = tinymce.each;
bgneal@442 8386
bgneal@442 8387 tinymce.create('tinymce.ui.KeyboardNavigation', {
bgneal@442 8388 KeyboardNavigation: function(settings, dom) {
bgneal@442 8389 var t = this, root = settings.root, items = settings.items,
bgneal@442 8390 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
bgneal@442 8391 excludeFromTabOrder = settings.excludeFromTabOrder,
bgneal@442 8392 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
bgneal@442 8393
bgneal@442 8394 dom = dom || tinymce.DOM;
bgneal@442 8395
bgneal@442 8396 itemFocussed = function(evt) {
bgneal@442 8397 focussedId = evt.target.id;
bgneal@442 8398 };
bgneal@442 8399
bgneal@442 8400 itemBlurred = function(evt) {
bgneal@442 8401 dom.setAttrib(evt.target.id, 'tabindex', '-1');
bgneal@442 8402 };
bgneal@442 8403
bgneal@442 8404 rootFocussed = function(evt) {
bgneal@442 8405 var item = dom.get(focussedId);
bgneal@442 8406 dom.setAttrib(item, 'tabindex', '0');
bgneal@442 8407 item.focus();
bgneal@442 8408 };
bgneal@442 8409
bgneal@442 8410 t.focus = function() {
bgneal@442 8411 dom.get(focussedId).focus();
bgneal@442 8412 };
bgneal@442 8413
bgneal@442 8414 t.destroy = function() {
bgneal@442 8415 each(items, function(item) {
bgneal@442 8416 dom.unbind(dom.get(item.id), 'focus', itemFocussed);
bgneal@442 8417 dom.unbind(dom.get(item.id), 'blur', itemBlurred);
bgneal@442 8418 });
bgneal@442 8419
bgneal@442 8420 dom.unbind(dom.get(root), 'focus', rootFocussed);
bgneal@442 8421 dom.unbind(dom.get(root), 'keydown', rootKeydown);
bgneal@442 8422
bgneal@442 8423 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
bgneal@442 8424 t.destroy = function() {};
bgneal@442 8425 };
bgneal@442 8426
bgneal@442 8427 t.moveFocus = function(dir, evt) {
bgneal@442 8428 var idx = -1, controls = t.controls, newFocus;
bgneal@442 8429
bgneal@442 8430 if (!focussedId)
bgneal@442 8431 return;
bgneal@442 8432
bgneal@442 8433 each(items, function(item, index) {
bgneal@442 8434 if (item.id === focussedId) {
bgneal@442 8435 idx = index;
bgneal@442 8436 return false;
bgneal@442 8437 }
bgneal@442 8438 });
bgneal@442 8439
bgneal@442 8440 idx += dir;
bgneal@442 8441 if (idx < 0) {
bgneal@442 8442 idx = items.length - 1;
bgneal@442 8443 } else if (idx >= items.length) {
bgneal@442 8444 idx = 0;
bgneal@442 8445 }
bgneal@442 8446
bgneal@442 8447 newFocus = items[idx];
bgneal@442 8448 dom.setAttrib(focussedId, 'tabindex', '-1');
bgneal@442 8449 dom.setAttrib(newFocus.id, 'tabindex', '0');
bgneal@442 8450 dom.get(newFocus.id).focus();
bgneal@442 8451
bgneal@442 8452 if (settings.actOnFocus) {
bgneal@442 8453 settings.onAction(newFocus.id);
bgneal@442 8454 }
bgneal@442 8455
bgneal@442 8456 if (evt)
bgneal@442 8457 Event.cancel(evt);
bgneal@442 8458 };
bgneal@442 8459
bgneal@442 8460 rootKeydown = function(evt) {
bgneal@442 8461 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
bgneal@442 8462
bgneal@442 8463 switch (evt.keyCode) {
bgneal@442 8464 case DOM_VK_LEFT:
bgneal@442 8465 if (enableLeftRight) t.moveFocus(-1);
bgneal@442 8466 break;
bgneal@442 8467
bgneal@442 8468 case DOM_VK_RIGHT:
bgneal@442 8469 if (enableLeftRight) t.moveFocus(1);
bgneal@442 8470 break;
bgneal@442 8471
bgneal@442 8472 case DOM_VK_UP:
bgneal@442 8473 if (enableUpDown) t.moveFocus(-1);
bgneal@442 8474 break;
bgneal@442 8475
bgneal@442 8476 case DOM_VK_DOWN:
bgneal@442 8477 if (enableUpDown) t.moveFocus(1);
bgneal@442 8478 break;
bgneal@442 8479
bgneal@442 8480 case DOM_VK_ESCAPE:
bgneal@442 8481 if (settings.onCancel) {
bgneal@442 8482 settings.onCancel();
bgneal@442 8483 Event.cancel(evt);
bgneal@442 8484 }
bgneal@442 8485 break;
bgneal@442 8486
bgneal@442 8487 case DOM_VK_ENTER:
bgneal@442 8488 case DOM_VK_RETURN:
bgneal@442 8489 case DOM_VK_SPACE:
bgneal@442 8490 if (settings.onAction) {
bgneal@442 8491 settings.onAction(focussedId);
bgneal@442 8492 Event.cancel(evt);
bgneal@442 8493 }
bgneal@442 8494 break;
bgneal@442 8495 }
bgneal@442 8496 };
bgneal@442 8497
bgneal@442 8498 // Set up state and listeners for each item.
bgneal@442 8499 each(items, function(item, idx) {
bgneal@442 8500 var tabindex;
bgneal@442 8501
bgneal@442 8502 if (!item.id) {
bgneal@442 8503 item.id = dom.uniqueId('_mce_item_');
bgneal@442 8504 }
bgneal@442 8505
bgneal@442 8506 if (excludeFromTabOrder) {
bgneal@442 8507 dom.bind(item.id, 'blur', itemBlurred);
bgneal@442 8508 tabindex = '-1';
bgneal@442 8509 } else {
bgneal@442 8510 tabindex = (idx === 0 ? '0' : '-1');
bgneal@442 8511 }
bgneal@442 8512
bgneal@442 8513 dom.setAttrib(item.id, 'tabindex', tabindex);
bgneal@442 8514 dom.bind(dom.get(item.id), 'focus', itemFocussed);
bgneal@442 8515 });
bgneal@442 8516
bgneal@442 8517 // Setup initial state for root element.
bgneal@442 8518 if (items[0]){
bgneal@442 8519 focussedId = items[0].id;
bgneal@442 8520 }
bgneal@442 8521
bgneal@442 8522 dom.setAttrib(root, 'tabindex', '-1');
bgneal@442 8523
bgneal@442 8524 // Setup listeners for root element.
bgneal@442 8525 dom.bind(dom.get(root), 'focus', rootFocussed);
bgneal@442 8526 dom.bind(dom.get(root), 'keydown', rootKeydown);
bgneal@442 8527 }
bgneal@442 8528 });
bgneal@442 8529 })(tinymce);
bgneal@442 8530 (function(tinymce) {
bgneal@312 8531 // Shorten class names
bgneal@312 8532 var DOM = tinymce.DOM, is = tinymce.is;
bgneal@312 8533
bgneal@312 8534 tinymce.create('tinymce.ui.Control', {
bgneal@442 8535 Control : function(id, s, editor) {
bgneal@312 8536 this.id = id;
bgneal@312 8537 this.settings = s = s || {};
bgneal@312 8538 this.rendered = false;
bgneal@312 8539 this.onRender = new tinymce.util.Dispatcher(this);
bgneal@312 8540 this.classPrefix = '';
bgneal@312 8541 this.scope = s.scope || this;
bgneal@312 8542 this.disabled = 0;
bgneal@312 8543 this.active = 0;
bgneal@442 8544 this.editor = editor;
bgneal@442 8545 },
bgneal@442 8546
bgneal@442 8547 setAriaProperty : function(property, value) {
bgneal@442 8548 var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
bgneal@442 8549 if (element) {
bgneal@442 8550 DOM.setAttrib(element, 'aria-' + property, !!value);
bgneal@442 8551 }
bgneal@442 8552 },
bgneal@442 8553
bgneal@442 8554 focus : function() {
bgneal@442 8555 DOM.get(this.id).focus();
bgneal@312 8556 },
bgneal@312 8557
bgneal@312 8558 setDisabled : function(s) {
bgneal@312 8559 if (s != this.disabled) {
bgneal@442 8560 this.setAriaProperty('disabled', s);
bgneal@312 8561
bgneal@312 8562 this.setState('Disabled', s);
bgneal@312 8563 this.setState('Enabled', !s);
bgneal@312 8564 this.disabled = s;
bgneal@312 8565 }
bgneal@312 8566 },
bgneal@312 8567
bgneal@312 8568 isDisabled : function() {
bgneal@312 8569 return this.disabled;
bgneal@312 8570 },
bgneal@312 8571
bgneal@312 8572 setActive : function(s) {
bgneal@312 8573 if (s != this.active) {
bgneal@312 8574 this.setState('Active', s);
bgneal@312 8575 this.active = s;
bgneal@442 8576 this.setAriaProperty('pressed', s);
bgneal@312 8577 }
bgneal@312 8578 },
bgneal@312 8579
bgneal@312 8580 isActive : function() {
bgneal@312 8581 return this.active;
bgneal@312 8582 },
bgneal@312 8583
bgneal@312 8584 setState : function(c, s) {
bgneal@312 8585 var n = DOM.get(this.id);
bgneal@312 8586
bgneal@312 8587 c = this.classPrefix + c;
bgneal@312 8588
bgneal@312 8589 if (s)
bgneal@312 8590 DOM.addClass(n, c);
bgneal@312 8591 else
bgneal@312 8592 DOM.removeClass(n, c);
bgneal@312 8593 },
bgneal@312 8594
bgneal@312 8595 isRendered : function() {
bgneal@312 8596 return this.rendered;
bgneal@312 8597 },
bgneal@312 8598
bgneal@312 8599 renderHTML : function() {
bgneal@312 8600 },
bgneal@312 8601
bgneal@312 8602 renderTo : function(n) {
bgneal@312 8603 DOM.setHTML(n, this.renderHTML());
bgneal@312 8604 },
bgneal@312 8605
bgneal@312 8606 postRender : function() {
bgneal@312 8607 var t = this, b;
bgneal@312 8608
bgneal@312 8609 // Set pending states
bgneal@312 8610 if (is(t.disabled)) {
bgneal@312 8611 b = t.disabled;
bgneal@312 8612 t.disabled = -1;
bgneal@312 8613 t.setDisabled(b);
bgneal@312 8614 }
bgneal@312 8615
bgneal@312 8616 if (is(t.active)) {
bgneal@312 8617 b = t.active;
bgneal@312 8618 t.active = -1;
bgneal@312 8619 t.setActive(b);
bgneal@312 8620 }
bgneal@312 8621 },
bgneal@312 8622
bgneal@312 8623 remove : function() {
bgneal@312 8624 DOM.remove(this.id);
bgneal@312 8625 this.destroy();
bgneal@312 8626 },
bgneal@312 8627
bgneal@312 8628 destroy : function() {
bgneal@312 8629 tinymce.dom.Event.clear(this.id);
bgneal@312 8630 }
bgneal@312 8631 });
bgneal@312 8632 })(tinymce);
bgneal@312 8633 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
bgneal@442 8634 Container : function(id, s, editor) {
bgneal@442 8635 this.parent(id, s, editor);
bgneal@312 8636
bgneal@312 8637 this.controls = [];
bgneal@312 8638
bgneal@312 8639 this.lookup = {};
bgneal@312 8640 },
bgneal@312 8641
bgneal@312 8642 add : function(c) {
bgneal@312 8643 this.lookup[c.id] = c;
bgneal@312 8644 this.controls.push(c);
bgneal@312 8645
bgneal@312 8646 return c;
bgneal@312 8647 },
bgneal@312 8648
bgneal@312 8649 get : function(n) {
bgneal@312 8650 return this.lookup[n];
bgneal@312 8651 }
bgneal@312 8652 });
bgneal@312 8653
bgneal@312 8654
bgneal@312 8655 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
bgneal@312 8656 Separator : function(id, s) {
bgneal@312 8657 this.parent(id, s);
bgneal@312 8658 this.classPrefix = 'mceSeparator';
bgneal@442 8659 this.setDisabled(true);
bgneal@312 8660 },
bgneal@312 8661
bgneal@312 8662 renderHTML : function() {
bgneal@442 8663 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
bgneal@312 8664 }
bgneal@312 8665 });
bgneal@312 8666
bgneal@312 8667 (function(tinymce) {
bgneal@312 8668 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@312 8669
bgneal@312 8670 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
bgneal@312 8671 MenuItem : function(id, s) {
bgneal@312 8672 this.parent(id, s);
bgneal@312 8673 this.classPrefix = 'mceMenuItem';
bgneal@312 8674 },
bgneal@312 8675
bgneal@312 8676 setSelected : function(s) {
bgneal@312 8677 this.setState('Selected', s);
bgneal@442 8678 this.setAriaProperty('checked', !!s);
bgneal@312 8679 this.selected = s;
bgneal@312 8680 },
bgneal@312 8681
bgneal@312 8682 isSelected : function() {
bgneal@312 8683 return this.selected;
bgneal@312 8684 },
bgneal@312 8685
bgneal@312 8686 postRender : function() {
bgneal@312 8687 var t = this;
bgneal@312 8688
bgneal@312 8689 t.parent();
bgneal@312 8690
bgneal@312 8691 // Set pending state
bgneal@312 8692 if (is(t.selected))
bgneal@312 8693 t.setSelected(t.selected);
bgneal@312 8694 }
bgneal@312 8695 });
bgneal@312 8696 })(tinymce);
bgneal@312 8697
bgneal@312 8698 (function(tinymce) {
bgneal@312 8699 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
bgneal@312 8700
bgneal@312 8701 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
bgneal@312 8702 Menu : function(id, s) {
bgneal@312 8703 var t = this;
bgneal@312 8704
bgneal@312 8705 t.parent(id, s);
bgneal@312 8706 t.items = {};
bgneal@312 8707 t.collapsed = false;
bgneal@312 8708 t.menuCount = 0;
bgneal@312 8709 t.onAddItem = new tinymce.util.Dispatcher(this);
bgneal@312 8710 },
bgneal@312 8711
bgneal@312 8712 expand : function(d) {
bgneal@312 8713 var t = this;
bgneal@312 8714
bgneal@312 8715 if (d) {
bgneal@312 8716 walk(t, function(o) {
bgneal@312 8717 if (o.expand)
bgneal@312 8718 o.expand();
bgneal@312 8719 }, 'items', t);
bgneal@312 8720 }
bgneal@312 8721
bgneal@312 8722 t.collapsed = false;
bgneal@312 8723 },
bgneal@312 8724
bgneal@312 8725 collapse : function(d) {
bgneal@312 8726 var t = this;
bgneal@312 8727
bgneal@312 8728 if (d) {
bgneal@312 8729 walk(t, function(o) {
bgneal@312 8730 if (o.collapse)
bgneal@312 8731 o.collapse();
bgneal@312 8732 }, 'items', t);
bgneal@312 8733 }
bgneal@312 8734
bgneal@312 8735 t.collapsed = true;
bgneal@312 8736 },
bgneal@312 8737
bgneal@312 8738 isCollapsed : function() {
bgneal@312 8739 return this.collapsed;
bgneal@312 8740 },
bgneal@312 8741
bgneal@312 8742 add : function(o) {
bgneal@312 8743 if (!o.settings)
bgneal@312 8744 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
bgneal@312 8745
bgneal@312 8746 this.onAddItem.dispatch(this, o);
bgneal@312 8747
bgneal@312 8748 return this.items[o.id] = o;
bgneal@312 8749 },
bgneal@312 8750
bgneal@312 8751 addSeparator : function() {
bgneal@312 8752 return this.add({separator : true});
bgneal@312 8753 },
bgneal@312 8754
bgneal@312 8755 addMenu : function(o) {
bgneal@312 8756 if (!o.collapse)
bgneal@312 8757 o = this.createMenu(o);
bgneal@312 8758
bgneal@312 8759 this.menuCount++;
bgneal@312 8760
bgneal@312 8761 return this.add(o);
bgneal@312 8762 },
bgneal@312 8763
bgneal@312 8764 hasMenus : function() {
bgneal@312 8765 return this.menuCount !== 0;
bgneal@312 8766 },
bgneal@312 8767
bgneal@312 8768 remove : function(o) {
bgneal@312 8769 delete this.items[o.id];
bgneal@312 8770 },
bgneal@312 8771
bgneal@312 8772 removeAll : function() {
bgneal@312 8773 var t = this;
bgneal@312 8774
bgneal@312 8775 walk(t, function(o) {
bgneal@312 8776 if (o.removeAll)
bgneal@312 8777 o.removeAll();
bgneal@312 8778 else
bgneal@312 8779 o.remove();
bgneal@312 8780
bgneal@312 8781 o.destroy();
bgneal@312 8782 }, 'items', t);
bgneal@312 8783
bgneal@312 8784 t.items = {};
bgneal@312 8785 },
bgneal@312 8786
bgneal@312 8787 createMenu : function(o) {
bgneal@312 8788 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
bgneal@312 8789
bgneal@312 8790 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
bgneal@312 8791
bgneal@312 8792 return m;
bgneal@312 8793 }
bgneal@312 8794 });
bgneal@312 8795 })(tinymce);
bgneal@312 8796 (function(tinymce) {
bgneal@312 8797 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
bgneal@312 8798
bgneal@312 8799 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
bgneal@312 8800 DropMenu : function(id, s) {
bgneal@312 8801 s = s || {};
bgneal@312 8802 s.container = s.container || DOM.doc.body;
bgneal@312 8803 s.offset_x = s.offset_x || 0;
bgneal@312 8804 s.offset_y = s.offset_y || 0;
bgneal@312 8805 s.vp_offset_x = s.vp_offset_x || 0;
bgneal@312 8806 s.vp_offset_y = s.vp_offset_y || 0;
bgneal@312 8807
bgneal@312 8808 if (is(s.icons) && !s.icons)
bgneal@312 8809 s['class'] += ' mceNoIcons';
bgneal@312 8810
bgneal@312 8811 this.parent(id, s);
bgneal@312 8812 this.onShowMenu = new tinymce.util.Dispatcher(this);
bgneal@312 8813 this.onHideMenu = new tinymce.util.Dispatcher(this);
bgneal@312 8814 this.classPrefix = 'mceMenu';
bgneal@312 8815 },
bgneal@312 8816
bgneal@312 8817 createMenu : function(s) {
bgneal@312 8818 var t = this, cs = t.settings, m;
bgneal@312 8819
bgneal@312 8820 s.container = s.container || cs.container;
bgneal@312 8821 s.parent = t;
bgneal@312 8822 s.constrain = s.constrain || cs.constrain;
bgneal@312 8823 s['class'] = s['class'] || cs['class'];
bgneal@312 8824 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
bgneal@312 8825 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
bgneal@442 8826 s.keyboard_focus = cs.keyboard_focus;
bgneal@312 8827 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
bgneal@312 8828
bgneal@312 8829 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
bgneal@312 8830
bgneal@312 8831 return m;
bgneal@312 8832 },
bgneal@442 8833
bgneal@442 8834 focus : function() {
bgneal@442 8835 var t = this;
bgneal@442 8836 if (t.keyboardNav) {
bgneal@442 8837 t.keyboardNav.focus();
bgneal@442 8838 }
bgneal@442 8839 },
bgneal@312 8840
bgneal@312 8841 update : function() {
bgneal@312 8842 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
bgneal@312 8843
bgneal@312 8844 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
bgneal@312 8845 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
bgneal@312 8846
bgneal@312 8847 if (!DOM.boxModel)
bgneal@312 8848 t.element.setStyles({width : tw + 2, height : th + 2});
bgneal@312 8849 else
bgneal@312 8850 t.element.setStyles({width : tw, height : th});
bgneal@312 8851
bgneal@312 8852 if (s.max_width)
bgneal@312 8853 DOM.setStyle(co, 'width', tw);
bgneal@312 8854
bgneal@312 8855 if (s.max_height) {
bgneal@312 8856 DOM.setStyle(co, 'height', th);
bgneal@312 8857
bgneal@312 8858 if (tb.clientHeight < s.max_height)
bgneal@312 8859 DOM.setStyle(co, 'overflow', 'hidden');
bgneal@312 8860 }
bgneal@312 8861 },
bgneal@312 8862
bgneal@312 8863 showMenu : function(x, y, px) {
bgneal@312 8864 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
bgneal@312 8865
bgneal@312 8866 t.collapse(1);
bgneal@312 8867
bgneal@312 8868 if (t.isMenuVisible)
bgneal@312 8869 return;
bgneal@312 8870
bgneal@312 8871 if (!t.rendered) {
bgneal@312 8872 co = DOM.add(t.settings.container, t.renderNode());
bgneal@312 8873
bgneal@312 8874 each(t.items, function(o) {
bgneal@312 8875 o.postRender();
bgneal@312 8876 });
bgneal@312 8877
bgneal@312 8878 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@312 8879 } else
bgneal@312 8880 co = DOM.get('menu_' + t.id);
bgneal@312 8881
bgneal@312 8882 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
bgneal@312 8883 if (!tinymce.isOpera)
bgneal@312 8884 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
bgneal@312 8885
bgneal@312 8886 DOM.show(co);
bgneal@312 8887 t.update();
bgneal@312 8888
bgneal@312 8889 x += s.offset_x || 0;
bgneal@312 8890 y += s.offset_y || 0;
bgneal@312 8891 vp.w -= 4;
bgneal@312 8892 vp.h -= 4;
bgneal@312 8893
bgneal@312 8894 // Move inside viewport if not submenu
bgneal@312 8895 if (s.constrain) {
bgneal@312 8896 w = co.clientWidth - ot;
bgneal@312 8897 h = co.clientHeight - ot;
bgneal@312 8898 mx = vp.x + vp.w;
bgneal@312 8899 my = vp.y + vp.h;
bgneal@312 8900
bgneal@312 8901 if ((x + s.vp_offset_x + w) > mx)
bgneal@312 8902 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
bgneal@312 8903
bgneal@312 8904 if ((y + s.vp_offset_y + h) > my)
bgneal@312 8905 y = Math.max(0, (my - s.vp_offset_y) - h);
bgneal@312 8906 }
bgneal@312 8907
bgneal@312 8908 DOM.setStyles(co, {left : x , top : y});
bgneal@312 8909 t.element.update();
bgneal@312 8910
bgneal@312 8911 t.isMenuVisible = 1;
bgneal@312 8912 t.mouseClickFunc = Event.add(co, 'click', function(e) {
bgneal@312 8913 var m;
bgneal@312 8914
bgneal@312 8915 e = e.target;
bgneal@312 8916
bgneal@312 8917 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@312 8918 m = t.items[e.id];
bgneal@312 8919
bgneal@312 8920 if (m.isDisabled())
bgneal@312 8921 return;
bgneal@312 8922
bgneal@312 8923 dm = t;
bgneal@312 8924
bgneal@312 8925 while (dm) {
bgneal@312 8926 if (dm.hideMenu)
bgneal@312 8927 dm.hideMenu();
bgneal@312 8928
bgneal@312 8929 dm = dm.settings.parent;
bgneal@312 8930 }
bgneal@312 8931
bgneal@312 8932 if (m.settings.onclick)
bgneal@312 8933 m.settings.onclick(e);
bgneal@312 8934
bgneal@312 8935 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@312 8936 }
bgneal@312 8937 });
bgneal@312 8938
bgneal@312 8939 if (t.hasMenus()) {
bgneal@312 8940 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
bgneal@312 8941 var m, r, mi;
bgneal@312 8942
bgneal@312 8943 e = e.target;
bgneal@312 8944 if (e && (e = DOM.getParent(e, 'tr'))) {
bgneal@312 8945 m = t.items[e.id];
bgneal@312 8946
bgneal@312 8947 if (t.lastMenu)
bgneal@312 8948 t.lastMenu.collapse(1);
bgneal@312 8949
bgneal@312 8950 if (m.isDisabled())
bgneal@312 8951 return;
bgneal@312 8952
bgneal@312 8953 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
bgneal@312 8954 //p = DOM.getPos(s.container);
bgneal@312 8955 r = DOM.getRect(e);
bgneal@312 8956 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
bgneal@312 8957 t.lastMenu = m;
bgneal@312 8958 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
bgneal@312 8959 }
bgneal@312 8960 }
bgneal@312 8961 });
bgneal@312 8962 }
bgneal@442 8963
bgneal@442 8964 Event.add(co, 'keydown', t._keyHandler, t);
bgneal@312 8965
bgneal@312 8966 t.onShowMenu.dispatch(t);
bgneal@312 8967
bgneal@442 8968 if (s.keyboard_focus) {
bgneal@442 8969 t._setupKeyboardNav();
bgneal@312 8970 }
bgneal@312 8971 },
bgneal@312 8972
bgneal@312 8973 hideMenu : function(c) {
bgneal@312 8974 var t = this, co = DOM.get('menu_' + t.id), e;
bgneal@312 8975
bgneal@312 8976 if (!t.isMenuVisible)
bgneal@312 8977 return;
bgneal@312 8978
bgneal@442 8979 if (t.keyboardNav) t.keyboardNav.destroy();
bgneal@312 8980 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@312 8981 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@312 8982 Event.remove(co, 'keydown', t._keyHandler);
bgneal@312 8983 DOM.hide(co);
bgneal@312 8984 t.isMenuVisible = 0;
bgneal@312 8985
bgneal@312 8986 if (!c)
bgneal@312 8987 t.collapse(1);
bgneal@312 8988
bgneal@312 8989 if (t.element)
bgneal@312 8990 t.element.hide();
bgneal@312 8991
bgneal@312 8992 if (e = DOM.get(t.id))
bgneal@312 8993 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
bgneal@312 8994
bgneal@312 8995 t.onHideMenu.dispatch(t);
bgneal@312 8996 },
bgneal@312 8997
bgneal@312 8998 add : function(o) {
bgneal@312 8999 var t = this, co;
bgneal@312 9000
bgneal@312 9001 o = t.parent(o);
bgneal@312 9002
bgneal@312 9003 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
bgneal@312 9004 t._add(DOM.select('tbody', co)[0], o);
bgneal@312 9005
bgneal@312 9006 return o;
bgneal@312 9007 },
bgneal@312 9008
bgneal@312 9009 collapse : function(d) {
bgneal@312 9010 this.parent(d);
bgneal@312 9011 this.hideMenu(1);
bgneal@312 9012 },
bgneal@312 9013
bgneal@312 9014 remove : function(o) {
bgneal@312 9015 DOM.remove(o.id);
bgneal@312 9016 this.destroy();
bgneal@312 9017
bgneal@312 9018 return this.parent(o);
bgneal@312 9019 },
bgneal@312 9020
bgneal@312 9021 destroy : function() {
bgneal@312 9022 var t = this, co = DOM.get('menu_' + t.id);
bgneal@312 9023
bgneal@442 9024 if (t.keyboardNav) t.keyboardNav.destroy();
bgneal@312 9025 Event.remove(co, 'mouseover', t.mouseOverFunc);
bgneal@442 9026 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
bgneal@312 9027 Event.remove(co, 'click', t.mouseClickFunc);
bgneal@442 9028 Event.remove(co, 'keydown', t._keyHandler);
bgneal@312 9029
bgneal@312 9030 if (t.element)
bgneal@312 9031 t.element.remove();
bgneal@312 9032
bgneal@312 9033 DOM.remove(co);
bgneal@312 9034 },
bgneal@312 9035
bgneal@312 9036 renderNode : function() {
bgneal@312 9037 var t = this, s = t.settings, n, tb, co, w;
bgneal@312 9038
bgneal@442 9039 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
bgneal@442 9040 if (t.settings.parent) {
bgneal@442 9041 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
bgneal@442 9042 }
bgneal@442 9043 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
bgneal@312 9044 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
bgneal@312 9045
bgneal@312 9046 if (s.menu_line)
bgneal@312 9047 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
bgneal@312 9048
bgneal@312 9049 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
bgneal@442 9050 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
bgneal@312 9051 tb = DOM.add(n, 'tbody');
bgneal@312 9052
bgneal@312 9053 each(t.items, function(o) {
bgneal@312 9054 t._add(tb, o);
bgneal@312 9055 });
bgneal@312 9056
bgneal@312 9057 t.rendered = true;
bgneal@312 9058
bgneal@312 9059 return w;
bgneal@312 9060 },
bgneal@312 9061
bgneal@312 9062 // Internal functions
bgneal@442 9063 _setupKeyboardNav : function(){
bgneal@442 9064 var contextMenu, menuItems, t=this;
bgneal@442 9065 contextMenu = DOM.select('#menu_' + t.id)[0];
bgneal@442 9066 menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
bgneal@442 9067 menuItems.splice(0,0,contextMenu);
bgneal@442 9068 t.keyboardNav = new tinymce.ui.KeyboardNavigation({
bgneal@442 9069 root: 'menu_' + t.id,
bgneal@442 9070 items: menuItems,
bgneal@442 9071 onCancel: function() {
bgneal@442 9072 t.hideMenu();
bgneal@442 9073 },
bgneal@442 9074 enableUpDown: true
bgneal@442 9075 });
bgneal@442 9076 contextMenu.focus();
bgneal@442 9077 },
bgneal@442 9078
bgneal@442 9079 _keyHandler : function(evt) {
bgneal@442 9080 var t = this, e;
bgneal@442 9081 switch (evt.keyCode) {
bgneal@442 9082 case 37: // Left
bgneal@442 9083 if (t.settings.parent) {
bgneal@442 9084 t.hideMenu();
bgneal@442 9085 t.settings.parent.focus();
bgneal@442 9086 Event.cancel(evt);
bgneal@442 9087 }
bgneal@442 9088 break;
bgneal@442 9089 case 39: // Right
bgneal@442 9090 if (t.mouseOverFunc)
bgneal@442 9091 t.mouseOverFunc(evt);
bgneal@442 9092 break;
bgneal@312 9093 }
bgneal@312 9094 },
bgneal@312 9095
bgneal@312 9096 _add : function(tb, o) {
bgneal@312 9097 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
bgneal@312 9098
bgneal@312 9099 if (s.separator) {
bgneal@312 9100 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
bgneal@312 9101 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
bgneal@312 9102
bgneal@312 9103 if (n = ro.previousSibling)
bgneal@312 9104 DOM.addClass(n, 'mceLast');
bgneal@312 9105
bgneal@312 9106 return;
bgneal@312 9107 }
bgneal@312 9108
bgneal@312 9109 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
bgneal@442 9110 n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
bgneal@442 9111 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
bgneal@442 9112
bgneal@442 9113 if (s.parent) {
bgneal@442 9114 DOM.setAttrib(a, 'aria-haspopup', 'true');
bgneal@442 9115 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
bgneal@442 9116 }
bgneal@312 9117
bgneal@312 9118 DOM.addClass(it, s['class']);
bgneal@312 9119 // n = DOM.add(n, 'span', {'class' : 'item'});
bgneal@312 9120
bgneal@312 9121 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
bgneal@312 9122
bgneal@312 9123 if (s.icon_src)
bgneal@312 9124 DOM.add(ic, 'img', {src : s.icon_src});
bgneal@312 9125
bgneal@312 9126 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
bgneal@312 9127
bgneal@312 9128 if (o.settings.style)
bgneal@312 9129 DOM.setAttrib(n, 'style', o.settings.style);
bgneal@312 9130
bgneal@312 9131 if (tb.childNodes.length == 1)
bgneal@312 9132 DOM.addClass(ro, 'mceFirst');
bgneal@312 9133
bgneal@312 9134 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
bgneal@312 9135 DOM.addClass(ro, 'mceFirst');
bgneal@312 9136
bgneal@312 9137 if (o.collapse)
bgneal@312 9138 DOM.addClass(ro, cp + 'ItemSub');
bgneal@312 9139
bgneal@312 9140 if (n = ro.previousSibling)
bgneal@312 9141 DOM.removeClass(n, 'mceLast');
bgneal@312 9142
bgneal@312 9143 DOM.addClass(ro, 'mceLast');
bgneal@312 9144 }
bgneal@312 9145 });
bgneal@312 9146 })(tinymce);
bgneal@312 9147 (function(tinymce) {
bgneal@312 9148 var DOM = tinymce.DOM;
bgneal@312 9149
bgneal@312 9150 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
bgneal@442 9151 Button : function(id, s, ed) {
bgneal@442 9152 this.parent(id, s, ed);
bgneal@312 9153 this.classPrefix = 'mceButton';
bgneal@312 9154 },
bgneal@312 9155
bgneal@312 9156 renderHTML : function() {
bgneal@312 9157 var cp = this.classPrefix, s = this.settings, h, l;
bgneal@312 9158
bgneal@312 9159 l = DOM.encode(s.label || '');
bgneal@442 9160 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
bgneal@312 9161
bgneal@312 9162 if (s.image)
bgneal@442 9163 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
bgneal@312 9164 else
bgneal@442 9165 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
bgneal@442 9166
bgneal@442 9167 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
bgneal@442 9168 h += '</a>';
bgneal@312 9169 return h;
bgneal@312 9170 },
bgneal@312 9171
bgneal@312 9172 postRender : function() {
bgneal@312 9173 var t = this, s = t.settings;
bgneal@312 9174
bgneal@312 9175 tinymce.dom.Event.add(t.id, 'click', function(e) {
bgneal@312 9176 if (!t.isDisabled())
bgneal@312 9177 return s.onclick.call(s.scope, e);
bgneal@312 9178 });
bgneal@312 9179 }
bgneal@312 9180 });
bgneal@312 9181 })(tinymce);
bgneal@312 9182
bgneal@312 9183 (function(tinymce) {
bgneal@312 9184 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@312 9185
bgneal@312 9186 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
bgneal@442 9187 ListBox : function(id, s, ed) {
bgneal@312 9188 var t = this;
bgneal@312 9189
bgneal@442 9190 t.parent(id, s, ed);
bgneal@312 9191
bgneal@312 9192 t.items = [];
bgneal@312 9193
bgneal@312 9194 t.onChange = new Dispatcher(t);
bgneal@312 9195
bgneal@312 9196 t.onPostRender = new Dispatcher(t);
bgneal@312 9197
bgneal@312 9198 t.onAdd = new Dispatcher(t);
bgneal@312 9199
bgneal@312 9200 t.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@312 9201
bgneal@312 9202 t.classPrefix = 'mceListBox';
bgneal@312 9203 },
bgneal@312 9204
bgneal@312 9205 select : function(va) {
bgneal@312 9206 var t = this, fv, f;
bgneal@312 9207
bgneal@312 9208 if (va == undefined)
bgneal@312 9209 return t.selectByIndex(-1);
bgneal@312 9210
bgneal@312 9211 // Is string or number make function selector
bgneal@312 9212 if (va && va.call)
bgneal@312 9213 f = va;
bgneal@312 9214 else {
bgneal@312 9215 f = function(v) {
bgneal@312 9216 return v == va;
bgneal@312 9217 };
bgneal@312 9218 }
bgneal@312 9219
bgneal@312 9220 // Do we need to do something?
bgneal@312 9221 if (va != t.selectedValue) {
bgneal@312 9222 // Find item
bgneal@312 9223 each(t.items, function(o, i) {
bgneal@312 9224 if (f(o.value)) {
bgneal@312 9225 fv = 1;
bgneal@312 9226 t.selectByIndex(i);
bgneal@312 9227 return false;
bgneal@312 9228 }
bgneal@312 9229 });
bgneal@312 9230
bgneal@312 9231 if (!fv)
bgneal@312 9232 t.selectByIndex(-1);
bgneal@312 9233 }
bgneal@312 9234 },
bgneal@312 9235
bgneal@312 9236 selectByIndex : function(idx) {
bgneal@312 9237 var t = this, e, o;
bgneal@312 9238
bgneal@312 9239 if (idx != t.selectedIndex) {
bgneal@312 9240 e = DOM.get(t.id + '_text');
bgneal@312 9241 o = t.items[idx];
bgneal@312 9242
bgneal@312 9243 if (o) {
bgneal@312 9244 t.selectedValue = o.value;
bgneal@312 9245 t.selectedIndex = idx;
bgneal@312 9246 DOM.setHTML(e, DOM.encode(o.title));
bgneal@312 9247 DOM.removeClass(e, 'mceTitle');
bgneal@442 9248 DOM.setAttrib(t.id, 'aria-valuenow', o.title);
bgneal@312 9249 } else {
bgneal@312 9250 DOM.setHTML(e, DOM.encode(t.settings.title));
bgneal@312 9251 DOM.addClass(e, 'mceTitle');
bgneal@312 9252 t.selectedValue = t.selectedIndex = null;
bgneal@442 9253 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
bgneal@442 9254 }
bgneal@312 9255 e = 0;
bgneal@312 9256 }
bgneal@312 9257 },
bgneal@312 9258
bgneal@312 9259 add : function(n, v, o) {
bgneal@312 9260 var t = this;
bgneal@312 9261
bgneal@312 9262 o = o || {};
bgneal@312 9263 o = tinymce.extend(o, {
bgneal@312 9264 title : n,
bgneal@312 9265 value : v
bgneal@312 9266 });
bgneal@312 9267
bgneal@312 9268 t.items.push(o);
bgneal@312 9269 t.onAdd.dispatch(t, o);
bgneal@312 9270 },
bgneal@312 9271
bgneal@312 9272 getLength : function() {
bgneal@312 9273 return this.items.length;
bgneal@312 9274 },
bgneal@312 9275
bgneal@312 9276 renderHTML : function() {
bgneal@312 9277 var h = '', t = this, s = t.settings, cp = t.classPrefix;
bgneal@312 9278
bgneal@442 9279 h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
bgneal@442 9280 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
bgneal@442 9281 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
bgneal@442 9282 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
bgneal@442 9283 h += '</tr></tbody></table></span>';
bgneal@312 9284
bgneal@312 9285 return h;
bgneal@312 9286 },
bgneal@312 9287
bgneal@312 9288 showMenu : function() {
bgneal@312 9289 var t = this, p1, p2, e = DOM.get(this.id), m;
bgneal@312 9290
bgneal@312 9291 if (t.isDisabled() || t.items.length == 0)
bgneal@312 9292 return;
bgneal@312 9293
bgneal@312 9294 if (t.menu && t.menu.isMenuVisible)
bgneal@312 9295 return t.hideMenu();
bgneal@312 9296
bgneal@312 9297 if (!t.isMenuRendered) {
bgneal@312 9298 t.renderMenu();
bgneal@312 9299 t.isMenuRendered = true;
bgneal@312 9300 }
bgneal@312 9301
bgneal@312 9302 p1 = DOM.getPos(this.settings.menu_container);
bgneal@312 9303 p2 = DOM.getPos(e);
bgneal@312 9304
bgneal@312 9305 m = t.menu;
bgneal@312 9306 m.settings.offset_x = p2.x;
bgneal@312 9307 m.settings.offset_y = p2.y;
bgneal@312 9308 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
bgneal@312 9309
bgneal@312 9310 // Select in menu
bgneal@312 9311 if (t.oldID)
bgneal@312 9312 m.items[t.oldID].setSelected(0);
bgneal@312 9313
bgneal@312 9314 each(t.items, function(o) {
bgneal@312 9315 if (o.value === t.selectedValue) {
bgneal@312 9316 m.items[o.id].setSelected(1);
bgneal@312 9317 t.oldID = o.id;
bgneal@312 9318 }
bgneal@312 9319 });
bgneal@312 9320
bgneal@312 9321 m.showMenu(0, e.clientHeight);
bgneal@312 9322
bgneal@312 9323 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 9324 DOM.addClass(t.id, t.classPrefix + 'Selected');
bgneal@312 9325
bgneal@312 9326 //DOM.get(t.id + '_text').focus();
bgneal@312 9327 },
bgneal@312 9328
bgneal@312 9329 hideMenu : function(e) {
bgneal@312 9330 var t = this;
bgneal@312 9331
bgneal@312 9332 if (t.menu && t.menu.isMenuVisible) {
bgneal@442 9333 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@442 9334
bgneal@312 9335 // Prevent double toogles by canceling the mouse click event to the button
bgneal@312 9336 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
bgneal@312 9337 return;
bgneal@312 9338
bgneal@312 9339 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@312 9340 DOM.removeClass(t.id, t.classPrefix + 'Selected');
bgneal@312 9341 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 9342 t.menu.hideMenu();
bgneal@312 9343 }
bgneal@312 9344 }
bgneal@312 9345 },
bgneal@312 9346
bgneal@312 9347 renderMenu : function() {
bgneal@312 9348 var t = this, m;
bgneal@312 9349
bgneal@312 9350 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@312 9351 menu_line : 1,
bgneal@312 9352 'class' : t.classPrefix + 'Menu mceNoIcons',
bgneal@312 9353 max_width : 150,
bgneal@312 9354 max_height : 150
bgneal@312 9355 });
bgneal@312 9356
bgneal@442 9357 m.onHideMenu.add(function() {
bgneal@442 9358 t.hideMenu();
bgneal@442 9359 t.focus();
bgneal@442 9360 });
bgneal@312 9361
bgneal@312 9362 m.add({
bgneal@312 9363 title : t.settings.title,
bgneal@312 9364 'class' : 'mceMenuItemTitle',
bgneal@312 9365 onclick : function() {
bgneal@312 9366 if (t.settings.onselect('') !== false)
bgneal@312 9367 t.select(''); // Must be runned after
bgneal@312 9368 }
bgneal@312 9369 });
bgneal@312 9370
bgneal@312 9371 each(t.items, function(o) {
bgneal@312 9372 // No value then treat it as a title
bgneal@312 9373 if (o.value === undefined) {
bgneal@312 9374 m.add({
bgneal@312 9375 title : o.title,
bgneal@312 9376 'class' : 'mceMenuItemTitle',
bgneal@312 9377 onclick : function() {
bgneal@312 9378 if (t.settings.onselect('') !== false)
bgneal@312 9379 t.select(''); // Must be runned after
bgneal@312 9380 }
bgneal@312 9381 });
bgneal@312 9382 } else {
bgneal@312 9383 o.id = DOM.uniqueId();
bgneal@312 9384 o.onclick = function() {
bgneal@312 9385 if (t.settings.onselect(o.value) !== false)
bgneal@312 9386 t.select(o.value); // Must be runned after
bgneal@312 9387 };
bgneal@312 9388
bgneal@312 9389 m.add(o);
bgneal@312 9390 }
bgneal@312 9391 });
bgneal@312 9392
bgneal@312 9393 t.onRenderMenu.dispatch(t, m);
bgneal@312 9394 t.menu = m;
bgneal@312 9395 },
bgneal@312 9396
bgneal@312 9397 postRender : function() {
bgneal@312 9398 var t = this, cp = t.classPrefix;
bgneal@312 9399
bgneal@312 9400 Event.add(t.id, 'click', t.showMenu, t);
bgneal@442 9401 Event.add(t.id, 'keydown', function(evt) {
bgneal@442 9402 if (evt.keyCode == 32) { // Space
bgneal@442 9403 t.showMenu(evt);
bgneal@442 9404 Event.cancel(evt);
bgneal@442 9405 }
bgneal@442 9406 });
bgneal@442 9407 Event.add(t.id, 'focus', function() {
bgneal@312 9408 if (!t._focused) {
bgneal@442 9409 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
bgneal@442 9410 if (e.keyCode == 40) {
bgneal@442 9411 t.showMenu();
bgneal@442 9412 Event.cancel(e);
bgneal@442 9413 }
bgneal@442 9414 });
bgneal@442 9415 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
bgneal@442 9416 var v;
bgneal@442 9417 if (e.keyCode == 13) {
bgneal@312 9418 // Fake select on enter
bgneal@312 9419 v = t.selectedValue;
bgneal@312 9420 t.selectedValue = null; // Needs to be null to fake change
bgneal@442 9421 Event.cancel(e);
bgneal@312 9422 t.settings.onselect(v);
bgneal@312 9423 }
bgneal@312 9424 });
bgneal@312 9425 }
bgneal@312 9426
bgneal@312 9427 t._focused = 1;
bgneal@312 9428 });
bgneal@442 9429 Event.add(t.id, 'blur', function() {
bgneal@442 9430 Event.remove(t.id, 'keydown', t.keyDownHandler);
bgneal@442 9431 Event.remove(t.id, 'keypress', t.keyPressHandler);
bgneal@442 9432 t._focused = 0;
bgneal@442 9433 });
bgneal@312 9434
bgneal@312 9435 // Old IE doesn't have hover on all elements
bgneal@312 9436 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@312 9437 Event.add(t.id, 'mouseover', function() {
bgneal@312 9438 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@312 9439 DOM.addClass(t.id, cp + 'Hover');
bgneal@312 9440 });
bgneal@312 9441
bgneal@312 9442 Event.add(t.id, 'mouseout', function() {
bgneal@312 9443 if (!DOM.hasClass(t.id, cp + 'Disabled'))
bgneal@312 9444 DOM.removeClass(t.id, cp + 'Hover');
bgneal@312 9445 });
bgneal@312 9446 }
bgneal@312 9447
bgneal@312 9448 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@312 9449 },
bgneal@312 9450
bgneal@312 9451 destroy : function() {
bgneal@312 9452 this.parent();
bgneal@312 9453
bgneal@312 9454 Event.clear(this.id + '_text');
bgneal@312 9455 Event.clear(this.id + '_open');
bgneal@312 9456 }
bgneal@312 9457 });
bgneal@312 9458 })(tinymce);
bgneal@312 9459 (function(tinymce) {
bgneal@312 9460 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
bgneal@312 9461
bgneal@312 9462 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
bgneal@312 9463 NativeListBox : function(id, s) {
bgneal@312 9464 this.parent(id, s);
bgneal@312 9465 this.classPrefix = 'mceNativeListBox';
bgneal@312 9466 },
bgneal@312 9467
bgneal@312 9468 setDisabled : function(s) {
bgneal@312 9469 DOM.get(this.id).disabled = s;
bgneal@442 9470 this.setAriaProperty('disabled', s);
bgneal@312 9471 },
bgneal@312 9472
bgneal@312 9473 isDisabled : function() {
bgneal@312 9474 return DOM.get(this.id).disabled;
bgneal@312 9475 },
bgneal@312 9476
bgneal@312 9477 select : function(va) {
bgneal@312 9478 var t = this, fv, f;
bgneal@312 9479
bgneal@312 9480 if (va == undefined)
bgneal@312 9481 return t.selectByIndex(-1);
bgneal@312 9482
bgneal@312 9483 // Is string or number make function selector
bgneal@312 9484 if (va && va.call)
bgneal@312 9485 f = va;
bgneal@312 9486 else {
bgneal@312 9487 f = function(v) {
bgneal@312 9488 return v == va;
bgneal@312 9489 };
bgneal@312 9490 }
bgneal@312 9491
bgneal@312 9492 // Do we need to do something?
bgneal@312 9493 if (va != t.selectedValue) {
bgneal@312 9494 // Find item
bgneal@312 9495 each(t.items, function(o, i) {
bgneal@312 9496 if (f(o.value)) {
bgneal@312 9497 fv = 1;
bgneal@312 9498 t.selectByIndex(i);
bgneal@312 9499 return false;
bgneal@312 9500 }
bgneal@312 9501 });
bgneal@312 9502
bgneal@312 9503 if (!fv)
bgneal@312 9504 t.selectByIndex(-1);
bgneal@312 9505 }
bgneal@312 9506 },
bgneal@312 9507
bgneal@312 9508 selectByIndex : function(idx) {
bgneal@312 9509 DOM.get(this.id).selectedIndex = idx + 1;
bgneal@312 9510 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
bgneal@312 9511 },
bgneal@312 9512
bgneal@312 9513 add : function(n, v, a) {
bgneal@312 9514 var o, t = this;
bgneal@312 9515
bgneal@312 9516 a = a || {};
bgneal@312 9517 a.value = v;
bgneal@312 9518
bgneal@312 9519 if (t.isRendered())
bgneal@312 9520 DOM.add(DOM.get(this.id), 'option', a, n);
bgneal@312 9521
bgneal@312 9522 o = {
bgneal@312 9523 title : n,
bgneal@312 9524 value : v,
bgneal@312 9525 attribs : a
bgneal@312 9526 };
bgneal@312 9527
bgneal@312 9528 t.items.push(o);
bgneal@312 9529 t.onAdd.dispatch(t, o);
bgneal@312 9530 },
bgneal@312 9531
bgneal@312 9532 getLength : function() {
bgneal@312 9533 return this.items.length;
bgneal@312 9534 },
bgneal@312 9535
bgneal@312 9536 renderHTML : function() {
bgneal@312 9537 var h, t = this;
bgneal@312 9538
bgneal@312 9539 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
bgneal@312 9540
bgneal@312 9541 each(t.items, function(it) {
bgneal@312 9542 h += DOM.createHTML('option', {value : it.value}, it.title);
bgneal@312 9543 });
bgneal@312 9544
bgneal@442 9545 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
bgneal@442 9546 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
bgneal@312 9547 return h;
bgneal@312 9548 },
bgneal@312 9549
bgneal@312 9550 postRender : function() {
bgneal@442 9551 var t = this, ch, changeListenerAdded = true;
bgneal@312 9552
bgneal@312 9553 t.rendered = true;
bgneal@312 9554
bgneal@312 9555 function onChange(e) {
bgneal@312 9556 var v = t.items[e.target.selectedIndex - 1];
bgneal@312 9557
bgneal@312 9558 if (v && (v = v.value)) {
bgneal@312 9559 t.onChange.dispatch(t, v);
bgneal@312 9560
bgneal@312 9561 if (t.settings.onselect)
bgneal@312 9562 t.settings.onselect(v);
bgneal@312 9563 }
bgneal@312 9564 };
bgneal@312 9565
bgneal@312 9566 Event.add(t.id, 'change', onChange);
bgneal@312 9567
bgneal@312 9568 // Accessibility keyhandler
bgneal@312 9569 Event.add(t.id, 'keydown', function(e) {
bgneal@312 9570 var bf;
bgneal@312 9571
bgneal@312 9572 Event.remove(t.id, 'change', ch);
bgneal@442 9573 changeListenerAdded = false;
bgneal@312 9574
bgneal@312 9575 bf = Event.add(t.id, 'blur', function() {
bgneal@442 9576 if (changeListenerAdded) return;
bgneal@442 9577 changeListenerAdded = true;
bgneal@312 9578 Event.add(t.id, 'change', onChange);
bgneal@312 9579 Event.remove(t.id, 'blur', bf);
bgneal@312 9580 });
bgneal@312 9581
bgneal@312 9582 if (e.keyCode == 13 || e.keyCode == 32) {
bgneal@312 9583 onChange(e);
bgneal@312 9584 return Event.cancel(e);
bgneal@312 9585 }
bgneal@312 9586 });
bgneal@312 9587
bgneal@312 9588 t.onPostRender.dispatch(t, DOM.get(t.id));
bgneal@312 9589 }
bgneal@312 9590 });
bgneal@312 9591 })(tinymce);
bgneal@312 9592 (function(tinymce) {
bgneal@312 9593 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@312 9594
bgneal@312 9595 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
bgneal@442 9596 MenuButton : function(id, s, ed) {
bgneal@442 9597 this.parent(id, s, ed);
bgneal@312 9598
bgneal@312 9599 this.onRenderMenu = new tinymce.util.Dispatcher(this);
bgneal@312 9600
bgneal@312 9601 s.menu_container = s.menu_container || DOM.doc.body;
bgneal@312 9602 },
bgneal@312 9603
bgneal@312 9604 showMenu : function() {
bgneal@312 9605 var t = this, p1, p2, e = DOM.get(t.id), m;
bgneal@312 9606
bgneal@312 9607 if (t.isDisabled())
bgneal@312 9608 return;
bgneal@312 9609
bgneal@312 9610 if (!t.isMenuRendered) {
bgneal@312 9611 t.renderMenu();
bgneal@312 9612 t.isMenuRendered = true;
bgneal@312 9613 }
bgneal@312 9614
bgneal@312 9615 if (t.isMenuVisible)
bgneal@312 9616 return t.hideMenu();
bgneal@312 9617
bgneal@312 9618 p1 = DOM.getPos(t.settings.menu_container);
bgneal@312 9619 p2 = DOM.getPos(e);
bgneal@312 9620
bgneal@312 9621 m = t.menu;
bgneal@312 9622 m.settings.offset_x = p2.x;
bgneal@312 9623 m.settings.offset_y = p2.y;
bgneal@312 9624 m.settings.vp_offset_x = p2.x;
bgneal@312 9625 m.settings.vp_offset_y = p2.y;
bgneal@312 9626 m.settings.keyboard_focus = t._focused;
bgneal@312 9627 m.showMenu(0, e.clientHeight);
bgneal@312 9628
bgneal@312 9629 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 9630 t.setState('Selected', 1);
bgneal@312 9631
bgneal@312 9632 t.isMenuVisible = 1;
bgneal@312 9633 },
bgneal@312 9634
bgneal@312 9635 renderMenu : function() {
bgneal@312 9636 var t = this, m;
bgneal@312 9637
bgneal@312 9638 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
bgneal@312 9639 menu_line : 1,
bgneal@312 9640 'class' : this.classPrefix + 'Menu',
bgneal@312 9641 icons : t.settings.icons
bgneal@312 9642 });
bgneal@312 9643
bgneal@442 9644 m.onHideMenu.add(function() {
bgneal@442 9645 t.hideMenu();
bgneal@442 9646 t.focus();
bgneal@442 9647 });
bgneal@312 9648
bgneal@312 9649 t.onRenderMenu.dispatch(t, m);
bgneal@312 9650 t.menu = m;
bgneal@312 9651 },
bgneal@312 9652
bgneal@312 9653 hideMenu : function(e) {
bgneal@312 9654 var t = this;
bgneal@312 9655
bgneal@312 9656 // Prevent double toogles by canceling the mouse click event to the button
bgneal@312 9657 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
bgneal@312 9658 return;
bgneal@312 9659
bgneal@312 9660 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
bgneal@312 9661 t.setState('Selected', 0);
bgneal@312 9662 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 9663 if (t.menu)
bgneal@312 9664 t.menu.hideMenu();
bgneal@312 9665 }
bgneal@312 9666
bgneal@312 9667 t.isMenuVisible = 0;
bgneal@312 9668 },
bgneal@312 9669
bgneal@312 9670 postRender : function() {
bgneal@312 9671 var t = this, s = t.settings;
bgneal@312 9672
bgneal@312 9673 Event.add(t.id, 'click', function() {
bgneal@312 9674 if (!t.isDisabled()) {
bgneal@312 9675 if (s.onclick)
bgneal@312 9676 s.onclick(t.value);
bgneal@312 9677
bgneal@312 9678 t.showMenu();
bgneal@312 9679 }
bgneal@312 9680 });
bgneal@312 9681 }
bgneal@312 9682 });
bgneal@312 9683 })(tinymce);
bgneal@312 9684
bgneal@312 9685 (function(tinymce) {
bgneal@312 9686 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
bgneal@312 9687
bgneal@312 9688 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
bgneal@442 9689 SplitButton : function(id, s, ed) {
bgneal@442 9690 this.parent(id, s, ed);
bgneal@312 9691 this.classPrefix = 'mceSplitButton';
bgneal@312 9692 },
bgneal@312 9693
bgneal@312 9694 renderHTML : function() {
bgneal@312 9695 var h, t = this, s = t.settings, h1;
bgneal@312 9696
bgneal@312 9697 h = '<tbody><tr>';
bgneal@312 9698
bgneal@312 9699 if (s.image)
bgneal@442 9700 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
bgneal@312 9701 else
bgneal@312 9702 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
bgneal@312 9703
bgneal@442 9704 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
bgneal@442 9705 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@312 9706
bgneal@442 9707 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
bgneal@442 9708 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
bgneal@312 9709
bgneal@312 9710 h += '</tr></tbody>';
bgneal@442 9711 h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
bgneal@442 9712 return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
bgneal@312 9713 },
bgneal@312 9714
bgneal@312 9715 postRender : function() {
bgneal@442 9716 var t = this, s = t.settings, activate;
bgneal@312 9717
bgneal@312 9718 if (s.onclick) {
bgneal@442 9719 activate = function(evt) {
bgneal@442 9720 if (!t.isDisabled()) {
bgneal@312 9721 s.onclick(t.value);
bgneal@442 9722 Event.cancel(evt);
bgneal@442 9723 }
bgneal@442 9724 };
bgneal@442 9725 Event.add(t.id + '_action', 'click', activate);
bgneal@442 9726 Event.add(t.id, ['click', 'keydown'], function(evt) {
bgneal@442 9727 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
bgneal@442 9728 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
bgneal@442 9729 activate();
bgneal@442 9730 Event.cancel(evt);
bgneal@442 9731 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
bgneal@442 9732 t.showMenu();
bgneal@442 9733 Event.cancel(evt);
bgneal@442 9734 }
bgneal@442 9735 });
bgneal@442 9736 }
bgneal@442 9737
bgneal@442 9738 Event.add(t.id + '_open', 'click', function (evt) {
bgneal@442 9739 t.showMenu();
bgneal@442 9740 Event.cancel(evt);
bgneal@442 9741 });
bgneal@442 9742 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
bgneal@442 9743 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
bgneal@312 9744
bgneal@312 9745 // Old IE doesn't have hover on all elements
bgneal@312 9746 if (tinymce.isIE6 || !DOM.boxModel) {
bgneal@312 9747 Event.add(t.id, 'mouseover', function() {
bgneal@312 9748 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@312 9749 DOM.addClass(t.id, 'mceSplitButtonHover');
bgneal@312 9750 });
bgneal@312 9751
bgneal@312 9752 Event.add(t.id, 'mouseout', function() {
bgneal@312 9753 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
bgneal@312 9754 DOM.removeClass(t.id, 'mceSplitButtonHover');
bgneal@312 9755 });
bgneal@312 9756 }
bgneal@312 9757 },
bgneal@312 9758
bgneal@312 9759 destroy : function() {
bgneal@312 9760 this.parent();
bgneal@312 9761
bgneal@312 9762 Event.clear(this.id + '_action');
bgneal@312 9763 Event.clear(this.id + '_open');
bgneal@442 9764 Event.clear(this.id);
bgneal@312 9765 }
bgneal@312 9766 });
bgneal@312 9767 })(tinymce);
bgneal@312 9768
bgneal@312 9769 (function(tinymce) {
bgneal@312 9770 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
bgneal@312 9771
bgneal@312 9772 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
bgneal@442 9773 ColorSplitButton : function(id, s, ed) {
bgneal@312 9774 var t = this;
bgneal@312 9775
bgneal@442 9776 t.parent(id, s, ed);
bgneal@312 9777
bgneal@312 9778 t.settings = s = tinymce.extend({
bgneal@312 9779 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
bgneal@312 9780 grid_width : 8,
bgneal@312 9781 default_color : '#888888'
bgneal@312 9782 }, t.settings);
bgneal@312 9783
bgneal@312 9784 t.onShowMenu = new tinymce.util.Dispatcher(t);
bgneal@312 9785
bgneal@312 9786 t.onHideMenu = new tinymce.util.Dispatcher(t);
bgneal@312 9787
bgneal@312 9788 t.value = s.default_color;
bgneal@312 9789 },
bgneal@312 9790
bgneal@312 9791 showMenu : function() {
bgneal@312 9792 var t = this, r, p, e, p2;
bgneal@312 9793
bgneal@312 9794 if (t.isDisabled())
bgneal@312 9795 return;
bgneal@312 9796
bgneal@312 9797 if (!t.isMenuRendered) {
bgneal@312 9798 t.renderMenu();
bgneal@312 9799 t.isMenuRendered = true;
bgneal@312 9800 }
bgneal@312 9801
bgneal@312 9802 if (t.isMenuVisible)
bgneal@312 9803 return t.hideMenu();
bgneal@312 9804
bgneal@312 9805 e = DOM.get(t.id);
bgneal@312 9806 DOM.show(t.id + '_menu');
bgneal@312 9807 DOM.addClass(e, 'mceSplitButtonSelected');
bgneal@312 9808 p2 = DOM.getPos(e);
bgneal@312 9809 DOM.setStyles(t.id + '_menu', {
bgneal@312 9810 left : p2.x,
bgneal@312 9811 top : p2.y + e.clientHeight,
bgneal@312 9812 zIndex : 200000
bgneal@312 9813 });
bgneal@312 9814 e = 0;
bgneal@312 9815
bgneal@312 9816 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@312 9817 t.onShowMenu.dispatch(t);
bgneal@312 9818
bgneal@312 9819 if (t._focused) {
bgneal@312 9820 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
bgneal@312 9821 if (e.keyCode == 27)
bgneal@312 9822 t.hideMenu();
bgneal@312 9823 });
bgneal@312 9824
bgneal@312 9825 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
bgneal@312 9826 }
bgneal@312 9827
bgneal@312 9828 t.isMenuVisible = 1;
bgneal@312 9829 },
bgneal@312 9830
bgneal@312 9831 hideMenu : function(e) {
bgneal@312 9832 var t = this;
bgneal@312 9833
bgneal@442 9834 if (t.isMenuVisible) {
bgneal@442 9835 // Prevent double toogles by canceling the mouse click event to the button
bgneal@442 9836 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
bgneal@442 9837 return;
bgneal@442 9838
bgneal@442 9839 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
bgneal@442 9840 DOM.removeClass(t.id, 'mceSplitButtonSelected');
bgneal@442 9841 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
bgneal@442 9842 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
bgneal@442 9843 DOM.hide(t.id + '_menu');
bgneal@442 9844 }
bgneal@442 9845
bgneal@442 9846 t.isMenuVisible = 0;
bgneal@442 9847 }
bgneal@312 9848 },
bgneal@312 9849
bgneal@312 9850 renderMenu : function() {
bgneal@442 9851 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
bgneal@442 9852
bgneal@442 9853 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
bgneal@312 9854 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
bgneal@312 9855 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
bgneal@312 9856
bgneal@442 9857 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
bgneal@312 9858 tb = DOM.add(n, 'tbody');
bgneal@312 9859
bgneal@312 9860 // Generate color grid
bgneal@312 9861 i = 0;
bgneal@312 9862 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
bgneal@312 9863 c = c.replace(/^#/, '');
bgneal@312 9864
bgneal@312 9865 if (!i--) {
bgneal@312 9866 tr = DOM.add(tb, 'tr');
bgneal@312 9867 i = s.grid_width - 1;
bgneal@312 9868 }
bgneal@312 9869
bgneal@312 9870 n = DOM.add(tr, 'td');
bgneal@312 9871 n = DOM.add(n, 'a', {
bgneal@442 9872 role : 'option',
bgneal@312 9873 href : 'javascript:;',
bgneal@312 9874 style : {
bgneal@312 9875 backgroundColor : '#' + c
bgneal@312 9876 },
bgneal@442 9877 'title': t.editor.getLang('colors.' + c, c),
bgneal@442 9878 'data-mce-color' : '#' + c
bgneal@442 9879 });
bgneal@442 9880
bgneal@442 9881 if (t.editor.forcedHighContrastMode) {
bgneal@442 9882 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
bgneal@442 9883 if (n.getContext && (context = n.getContext("2d"))) {
bgneal@442 9884 context.fillStyle = '#' + c;
bgneal@442 9885 context.fillRect(0, 0, 16, 16);
bgneal@442 9886 } else {
bgneal@442 9887 // No point leaving a canvas element around if it's not supported for drawing on anyway.
bgneal@442 9888 DOM.remove(n);
bgneal@442 9889 }
bgneal@442 9890 }
bgneal@312 9891 });
bgneal@312 9892
bgneal@312 9893 if (s.more_colors_func) {
bgneal@312 9894 n = DOM.add(tb, 'tr');
bgneal@312 9895 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
bgneal@442 9896 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
bgneal@312 9897
bgneal@312 9898 Event.add(n, 'click', function(e) {
bgneal@312 9899 s.more_colors_func.call(s.more_colors_scope || this);
bgneal@312 9900 return Event.cancel(e); // Cancel to fix onbeforeunload problem
bgneal@312 9901 });
bgneal@312 9902 }
bgneal@312 9903
bgneal@312 9904 DOM.addClass(m, 'mceColorSplitMenu');
bgneal@442 9905
bgneal@442 9906 new tinymce.ui.KeyboardNavigation({
bgneal@442 9907 root: t.id + '_menu',
bgneal@442 9908 items: DOM.select('a', t.id + '_menu'),
bgneal@442 9909 onCancel: function() {
bgneal@442 9910 t.hideMenu();
bgneal@442 9911 t.focus();
bgneal@442 9912 }
bgneal@442 9913 });
bgneal@442 9914
bgneal@442 9915 // Prevent IE from scrolling and hindering click to occur #4019
bgneal@442 9916 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
bgneal@312 9917
bgneal@312 9918 Event.add(t.id + '_menu', 'click', function(e) {
bgneal@312 9919 var c;
bgneal@312 9920
bgneal@442 9921 e = DOM.getParent(e.target, 'a', tb);
bgneal@442 9922
bgneal@442 9923 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
bgneal@312 9924 t.setColor(c);
bgneal@312 9925
bgneal@312 9926 return Event.cancel(e); // Prevent IE auto save warning
bgneal@312 9927 });
bgneal@312 9928
bgneal@312 9929 return w;
bgneal@312 9930 },
bgneal@312 9931
bgneal@312 9932 setColor : function(c) {
bgneal@442 9933 this.displayColor(c);
bgneal@442 9934 this.hideMenu();
bgneal@442 9935 this.settings.onselect(c);
bgneal@442 9936 },
bgneal@442 9937
bgneal@442 9938 displayColor : function(c) {
bgneal@312 9939 var t = this;
bgneal@312 9940
bgneal@312 9941 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
bgneal@312 9942
bgneal@312 9943 t.value = c;
bgneal@312 9944 },
bgneal@312 9945
bgneal@312 9946 postRender : function() {
bgneal@312 9947 var t = this, id = t.id;
bgneal@312 9948
bgneal@312 9949 t.parent();
bgneal@312 9950 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
bgneal@312 9951 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
bgneal@312 9952 },
bgneal@312 9953
bgneal@312 9954 destroy : function() {
bgneal@312 9955 this.parent();
bgneal@312 9956
bgneal@312 9957 Event.clear(this.id + '_menu');
bgneal@312 9958 Event.clear(this.id + '_more');
bgneal@312 9959 DOM.remove(this.id + '_menu');
bgneal@312 9960 }
bgneal@312 9961 });
bgneal@312 9962 })(tinymce);
bgneal@312 9963
bgneal@442 9964 (function(tinymce) {
bgneal@442 9965 // Shorten class names
bgneal@442 9966 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
bgneal@442 9967 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
bgneal@442 9968 renderHTML : function() {
bgneal@442 9969 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
bgneal@442 9970
bgneal@442 9971 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
bgneal@442 9972 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
bgneal@442 9973 h.push("<span role='application'>");
bgneal@442 9974 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
bgneal@442 9975 each(controls, function(toolbar) {
bgneal@442 9976 h.push(toolbar.renderHTML());
bgneal@442 9977 });
bgneal@442 9978 h.push("</span>");
bgneal@442 9979 h.push('</div>');
bgneal@442 9980
bgneal@442 9981 return h.join('');
bgneal@442 9982 },
bgneal@442 9983
bgneal@442 9984 focus : function() {
bgneal@442 9985 this.keyNav.focus();
bgneal@442 9986 },
bgneal@442 9987
bgneal@442 9988 postRender : function() {
bgneal@442 9989 var t = this, items = [];
bgneal@442 9990
bgneal@442 9991 each(t.controls, function(toolbar) {
bgneal@442 9992 each (toolbar.controls, function(control) {
bgneal@442 9993 if (control.id) {
bgneal@442 9994 items.push(control);
bgneal@442 9995 }
bgneal@442 9996 });
bgneal@442 9997 });
bgneal@442 9998
bgneal@442 9999 t.keyNav = new tinymce.ui.KeyboardNavigation({
bgneal@442 10000 root: t.id,
bgneal@442 10001 items: items,
bgneal@442 10002 onCancel: function() {
bgneal@442 10003 t.editor.focus();
bgneal@442 10004 },
bgneal@442 10005 excludeFromTabOrder: !t.settings.tab_focus_toolbar
bgneal@442 10006 });
bgneal@442 10007 },
bgneal@442 10008
bgneal@442 10009 destroy : function() {
bgneal@442 10010 var self = this;
bgneal@442 10011
bgneal@442 10012 self.parent();
bgneal@442 10013 self.keyNav.destroy();
bgneal@442 10014 Event.clear(self.id);
bgneal@442 10015 }
bgneal@442 10016 });
bgneal@442 10017 })(tinymce);
bgneal@442 10018
bgneal@442 10019 (function(tinymce) {
bgneal@442 10020 // Shorten class names
bgneal@442 10021 var dom = tinymce.DOM, each = tinymce.each;
bgneal@312 10022 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
bgneal@312 10023 renderHTML : function() {
bgneal@442 10024 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
bgneal@312 10025
bgneal@312 10026 cl = t.controls;
bgneal@312 10027 for (i=0; i<cl.length; i++) {
bgneal@312 10028 // Get current control, prev control, next control and if the control is a list box or not
bgneal@312 10029 co = cl[i];
bgneal@312 10030 pr = cl[i - 1];
bgneal@312 10031 nx = cl[i + 1];
bgneal@312 10032
bgneal@312 10033 // Add toolbar start
bgneal@312 10034 if (i === 0) {
bgneal@312 10035 c = 'mceToolbarStart';
bgneal@312 10036
bgneal@312 10037 if (co.Button)
bgneal@312 10038 c += ' mceToolbarStartButton';
bgneal@312 10039 else if (co.SplitButton)
bgneal@312 10040 c += ' mceToolbarStartSplitButton';
bgneal@312 10041 else if (co.ListBox)
bgneal@312 10042 c += ' mceToolbarStartListBox';
bgneal@312 10043
bgneal@312 10044 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 10045 }
bgneal@312 10046
bgneal@312 10047 // Add toolbar end before list box and after the previous button
bgneal@312 10048 // This is to fix the o2k7 editor skins
bgneal@312 10049 if (pr && co.ListBox) {
bgneal@312 10050 if (pr.Button || pr.SplitButton)
bgneal@312 10051 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 10052 }
bgneal@312 10053
bgneal@312 10054 // Render control HTML
bgneal@312 10055
bgneal@312 10056 // IE 8 quick fix, needed to propertly generate a hit area for anchors
bgneal@312 10057 if (dom.stdMode)
bgneal@312 10058 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
bgneal@312 10059 else
bgneal@312 10060 h += '<td>' + co.renderHTML() + '</td>';
bgneal@312 10061
bgneal@312 10062 // Add toolbar start after list box and before the next button
bgneal@312 10063 // This is to fix the o2k7 editor skins
bgneal@312 10064 if (nx && co.ListBox) {
bgneal@312 10065 if (nx.Button || nx.SplitButton)
bgneal@312 10066 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 10067 }
bgneal@312 10068 }
bgneal@312 10069
bgneal@312 10070 c = 'mceToolbarEnd';
bgneal@312 10071
bgneal@312 10072 if (co.Button)
bgneal@312 10073 c += ' mceToolbarEndButton';
bgneal@312 10074 else if (co.SplitButton)
bgneal@312 10075 c += ' mceToolbarEndSplitButton';
bgneal@312 10076 else if (co.ListBox)
bgneal@312 10077 c += ' mceToolbarEndListBox';
bgneal@312 10078
bgneal@312 10079 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
bgneal@312 10080
bgneal@442 10081 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
bgneal@312 10082 }
bgneal@312 10083 });
bgneal@442 10084 })(tinymce);
bgneal@312 10085
bgneal@312 10086 (function(tinymce) {
bgneal@312 10087 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
bgneal@312 10088
bgneal@312 10089 tinymce.create('tinymce.AddOnManager', {
bgneal@312 10090 AddOnManager : function() {
bgneal@312 10091 var self = this;
bgneal@312 10092
bgneal@312 10093 self.items = [];
bgneal@312 10094 self.urls = {};
bgneal@312 10095 self.lookup = {};
bgneal@312 10096 self.onAdd = new Dispatcher(self);
bgneal@312 10097 },
bgneal@312 10098
bgneal@312 10099 get : function(n) {
bgneal@312 10100 return this.lookup[n];
bgneal@312 10101 },
bgneal@312 10102
bgneal@312 10103 requireLangPack : function(n) {
bgneal@312 10104 var s = tinymce.settings;
bgneal@312 10105
bgneal@442 10106 if (s && s.language && s.language_load !== false)
bgneal@312 10107 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
bgneal@312 10108 },
bgneal@312 10109
bgneal@312 10110 add : function(id, o) {
bgneal@312 10111 this.items.push(o);
bgneal@312 10112 this.lookup[id] = o;
bgneal@312 10113 this.onAdd.dispatch(this, id, o);
bgneal@312 10114
bgneal@312 10115 return o;
bgneal@312 10116 },
bgneal@312 10117
bgneal@312 10118 load : function(n, u, cb, s) {
bgneal@312 10119 var t = this;
bgneal@312 10120
bgneal@312 10121 if (t.urls[n])
bgneal@312 10122 return;
bgneal@312 10123
bgneal@312 10124 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
bgneal@442 10125 u = tinymce.baseURL + '/' + u;
bgneal@312 10126
bgneal@312 10127 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
bgneal@312 10128
bgneal@312 10129 if (!t.lookup[n])
bgneal@312 10130 tinymce.ScriptLoader.add(u, cb, s);
bgneal@312 10131 }
bgneal@312 10132 });
bgneal@312 10133
bgneal@312 10134 // Create plugin and theme managers
bgneal@312 10135 tinymce.PluginManager = new tinymce.AddOnManager();
bgneal@312 10136 tinymce.ThemeManager = new tinymce.AddOnManager();
bgneal@312 10137 }(tinymce));
bgneal@312 10138
bgneal@312 10139 (function(tinymce) {
bgneal@312 10140 // Shorten names
bgneal@312 10141 var each = tinymce.each, extend = tinymce.extend,
bgneal@312 10142 DOM = tinymce.DOM, Event = tinymce.dom.Event,
bgneal@312 10143 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@312 10144 explode = tinymce.explode,
bgneal@312 10145 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
bgneal@312 10146
bgneal@312 10147 // Setup some URLs where the editor API is located and where the document is
bgneal@312 10148 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
bgneal@312 10149 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
bgneal@312 10150 tinymce.documentBaseURL += '/';
bgneal@312 10151
bgneal@312 10152 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
bgneal@312 10153
bgneal@312 10154 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
bgneal@312 10155
bgneal@312 10156 // Add before unload listener
bgneal@312 10157 // This was required since IE was leaking memory if you added and removed beforeunload listeners
bgneal@312 10158 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
bgneal@312 10159 tinymce.onBeforeUnload = new Dispatcher(tinymce);
bgneal@312 10160
bgneal@312 10161 // Must be on window or IE will leak if the editor is placed in frame or iframe
bgneal@312 10162 Event.add(window, 'beforeunload', function(e) {
bgneal@312 10163 tinymce.onBeforeUnload.dispatch(tinymce, e);
bgneal@312 10164 });
bgneal@312 10165
bgneal@312 10166 tinymce.onAddEditor = new Dispatcher(tinymce);
bgneal@312 10167
bgneal@312 10168 tinymce.onRemoveEditor = new Dispatcher(tinymce);
bgneal@312 10169
bgneal@312 10170 tinymce.EditorManager = extend(tinymce, {
bgneal@312 10171 editors : [],
bgneal@312 10172
bgneal@312 10173 i18n : {},
bgneal@312 10174
bgneal@312 10175 activeEditor : null,
bgneal@312 10176
bgneal@312 10177 init : function(s) {
bgneal@312 10178 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
bgneal@312 10179
bgneal@312 10180 function execCallback(se, n, s) {
bgneal@312 10181 var f = se[n];
bgneal@312 10182
bgneal@312 10183 if (!f)
bgneal@312 10184 return;
bgneal@312 10185
bgneal@312 10186 if (tinymce.is(f, 'string')) {
bgneal@312 10187 s = f.replace(/\.\w+$/, '');
bgneal@312 10188 s = s ? tinymce.resolve(s) : 0;
bgneal@312 10189 f = tinymce.resolve(f);
bgneal@312 10190 }
bgneal@312 10191
bgneal@312 10192 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
bgneal@312 10193 };
bgneal@312 10194
bgneal@312 10195 s = extend({
bgneal@312 10196 theme : "simple",
bgneal@312 10197 language : "en"
bgneal@312 10198 }, s);
bgneal@312 10199
bgneal@312 10200 t.settings = s;
bgneal@312 10201
bgneal@312 10202 // Legacy call
bgneal@312 10203 Event.add(document, 'init', function() {
bgneal@312 10204 var l, co;
bgneal@312 10205
bgneal@312 10206 execCallback(s, 'onpageload');
bgneal@312 10207
bgneal@312 10208 switch (s.mode) {
bgneal@312 10209 case "exact":
bgneal@312 10210 l = s.elements || '';
bgneal@312 10211
bgneal@312 10212 if(l.length > 0) {
bgneal@312 10213 each(explode(l), function(v) {
bgneal@312 10214 if (DOM.get(v)) {
bgneal@312 10215 ed = new tinymce.Editor(v, s);
bgneal@312 10216 el.push(ed);
bgneal@312 10217 ed.render(1);
bgneal@312 10218 } else {
bgneal@312 10219 each(document.forms, function(f) {
bgneal@312 10220 each(f.elements, function(e) {
bgneal@312 10221 if (e.name === v) {
bgneal@312 10222 v = 'mce_editor_' + instanceCounter++;
bgneal@312 10223 DOM.setAttrib(e, 'id', v);
bgneal@312 10224
bgneal@312 10225 ed = new tinymce.Editor(v, s);
bgneal@312 10226 el.push(ed);
bgneal@312 10227 ed.render(1);
bgneal@312 10228 }
bgneal@312 10229 });
bgneal@312 10230 });
bgneal@312 10231 }
bgneal@312 10232 });
bgneal@312 10233 }
bgneal@312 10234 break;
bgneal@312 10235
bgneal@312 10236 case "textareas":
bgneal@312 10237 case "specific_textareas":
bgneal@312 10238 function hasClass(n, c) {
bgneal@312 10239 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
bgneal@312 10240 };
bgneal@312 10241
bgneal@312 10242 each(DOM.select('textarea'), function(v) {
bgneal@312 10243 if (s.editor_deselector && hasClass(v, s.editor_deselector))
bgneal@312 10244 return;
bgneal@312 10245
bgneal@312 10246 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
bgneal@312 10247 // Can we use the name
bgneal@312 10248 e = DOM.get(v.name);
bgneal@312 10249 if (!v.id && !e)
bgneal@312 10250 v.id = v.name;
bgneal@312 10251
bgneal@312 10252 // Generate unique name if missing or already exists
bgneal@312 10253 if (!v.id || t.get(v.id))
bgneal@312 10254 v.id = DOM.uniqueId();
bgneal@312 10255
bgneal@312 10256 ed = new tinymce.Editor(v.id, s);
bgneal@312 10257 el.push(ed);
bgneal@312 10258 ed.render(1);
bgneal@312 10259 }
bgneal@312 10260 });
bgneal@312 10261 break;
bgneal@312 10262 }
bgneal@312 10263
bgneal@312 10264 // Call onInit when all editors are initialized
bgneal@312 10265 if (s.oninit) {
bgneal@312 10266 l = co = 0;
bgneal@312 10267
bgneal@312 10268 each(el, function(ed) {
bgneal@312 10269 co++;
bgneal@312 10270
bgneal@312 10271 if (!ed.initialized) {
bgneal@312 10272 // Wait for it
bgneal@312 10273 ed.onInit.add(function() {
bgneal@312 10274 l++;
bgneal@312 10275
bgneal@312 10276 // All done
bgneal@312 10277 if (l == co)
bgneal@312 10278 execCallback(s, 'oninit');
bgneal@312 10279 });
bgneal@312 10280 } else
bgneal@312 10281 l++;
bgneal@312 10282
bgneal@312 10283 // All done
bgneal@312 10284 if (l == co)
bgneal@312 10285 execCallback(s, 'oninit');
bgneal@312 10286 });
bgneal@312 10287 }
bgneal@312 10288 });
bgneal@312 10289 },
bgneal@312 10290
bgneal@312 10291 get : function(id) {
bgneal@312 10292 if (id === undefined)
bgneal@312 10293 return this.editors;
bgneal@312 10294
bgneal@312 10295 return this.editors[id];
bgneal@312 10296 },
bgneal@312 10297
bgneal@312 10298 getInstanceById : function(id) {
bgneal@312 10299 return this.get(id);
bgneal@312 10300 },
bgneal@312 10301
bgneal@312 10302 add : function(editor) {
bgneal@312 10303 var self = this, editors = self.editors;
bgneal@312 10304
bgneal@312 10305 // Add named and index editor instance
bgneal@312 10306 editors[editor.id] = editor;
bgneal@312 10307 editors.push(editor);
bgneal@312 10308
bgneal@312 10309 self._setActive(editor);
bgneal@312 10310 self.onAddEditor.dispatch(self, editor);
bgneal@312 10311
bgneal@312 10312
bgneal@312 10313 return editor;
bgneal@312 10314 },
bgneal@312 10315
bgneal@312 10316 remove : function(editor) {
bgneal@312 10317 var t = this, i, editors = t.editors;
bgneal@312 10318
bgneal@312 10319 // Not in the collection
bgneal@312 10320 if (!editors[editor.id])
bgneal@312 10321 return null;
bgneal@312 10322
bgneal@312 10323 delete editors[editor.id];
bgneal@312 10324
bgneal@312 10325 for (i = 0; i < editors.length; i++) {
bgneal@312 10326 if (editors[i] == editor) {
bgneal@312 10327 editors.splice(i, 1);
bgneal@312 10328 break;
bgneal@312 10329 }
bgneal@312 10330 }
bgneal@312 10331
bgneal@312 10332 // Select another editor since the active one was removed
bgneal@312 10333 if (t.activeEditor == editor)
bgneal@312 10334 t._setActive(editors[0]);
bgneal@312 10335
bgneal@312 10336 editor.destroy();
bgneal@312 10337 t.onRemoveEditor.dispatch(t, editor);
bgneal@312 10338
bgneal@312 10339 return editor;
bgneal@312 10340 },
bgneal@312 10341
bgneal@312 10342 execCommand : function(c, u, v) {
bgneal@312 10343 var t = this, ed = t.get(v), w;
bgneal@312 10344
bgneal@312 10345 // Manager commands
bgneal@312 10346 switch (c) {
bgneal@312 10347 case "mceFocus":
bgneal@312 10348 ed.focus();
bgneal@312 10349 return true;
bgneal@312 10350
bgneal@312 10351 case "mceAddEditor":
bgneal@312 10352 case "mceAddControl":
bgneal@312 10353 if (!t.get(v))
bgneal@312 10354 new tinymce.Editor(v, t.settings).render();
bgneal@312 10355
bgneal@312 10356 return true;
bgneal@312 10357
bgneal@312 10358 case "mceAddFrameControl":
bgneal@312 10359 w = v.window;
bgneal@312 10360
bgneal@312 10361 // Add tinyMCE global instance and tinymce namespace to specified window
bgneal@312 10362 w.tinyMCE = tinyMCE;
bgneal@312 10363 w.tinymce = tinymce;
bgneal@312 10364
bgneal@312 10365 tinymce.DOM.doc = w.document;
bgneal@312 10366 tinymce.DOM.win = w;
bgneal@312 10367
bgneal@312 10368 ed = new tinymce.Editor(v.element_id, v);
bgneal@312 10369 ed.render();
bgneal@312 10370
bgneal@312 10371 // Fix IE memory leaks
bgneal@312 10372 if (tinymce.isIE) {
bgneal@312 10373 function clr() {
bgneal@312 10374 ed.destroy();
bgneal@312 10375 w.detachEvent('onunload', clr);
bgneal@312 10376 w = w.tinyMCE = w.tinymce = null; // IE leak
bgneal@312 10377 };
bgneal@312 10378
bgneal@312 10379 w.attachEvent('onunload', clr);
bgneal@312 10380 }
bgneal@312 10381
bgneal@312 10382 v.page_window = null;
bgneal@312 10383
bgneal@312 10384 return true;
bgneal@312 10385
bgneal@312 10386 case "mceRemoveEditor":
bgneal@312 10387 case "mceRemoveControl":
bgneal@312 10388 if (ed)
bgneal@312 10389 ed.remove();
bgneal@312 10390
bgneal@312 10391 return true;
bgneal@312 10392
bgneal@312 10393 case 'mceToggleEditor':
bgneal@312 10394 if (!ed) {
bgneal@312 10395 t.execCommand('mceAddControl', 0, v);
bgneal@312 10396 return true;
bgneal@312 10397 }
bgneal@312 10398
bgneal@312 10399 if (ed.isHidden())
bgneal@312 10400 ed.show();
bgneal@312 10401 else
bgneal@312 10402 ed.hide();
bgneal@312 10403
bgneal@312 10404 return true;
bgneal@312 10405 }
bgneal@312 10406
bgneal@312 10407 // Run command on active editor
bgneal@312 10408 if (t.activeEditor)
bgneal@312 10409 return t.activeEditor.execCommand(c, u, v);
bgneal@312 10410
bgneal@312 10411 return false;
bgneal@312 10412 },
bgneal@312 10413
bgneal@312 10414 execInstanceCommand : function(id, c, u, v) {
bgneal@312 10415 var ed = this.get(id);
bgneal@312 10416
bgneal@312 10417 if (ed)
bgneal@312 10418 return ed.execCommand(c, u, v);
bgneal@312 10419
bgneal@312 10420 return false;
bgneal@312 10421 },
bgneal@312 10422
bgneal@312 10423 triggerSave : function() {
bgneal@312 10424 each(this.editors, function(e) {
bgneal@312 10425 e.save();
bgneal@312 10426 });
bgneal@312 10427 },
bgneal@312 10428
bgneal@312 10429 addI18n : function(p, o) {
bgneal@312 10430 var lo, i18n = this.i18n;
bgneal@312 10431
bgneal@312 10432 if (!tinymce.is(p, 'string')) {
bgneal@312 10433 each(p, function(o, lc) {
bgneal@312 10434 each(o, function(o, g) {
bgneal@312 10435 each(o, function(o, k) {
bgneal@312 10436 if (g === 'common')
bgneal@312 10437 i18n[lc + '.' + k] = o;
bgneal@312 10438 else
bgneal@312 10439 i18n[lc + '.' + g + '.' + k] = o;
bgneal@312 10440 });
bgneal@312 10441 });
bgneal@312 10442 });
bgneal@312 10443 } else {
bgneal@312 10444 each(o, function(o, k) {
bgneal@312 10445 i18n[p + '.' + k] = o;
bgneal@312 10446 });
bgneal@312 10447 }
bgneal@312 10448 },
bgneal@312 10449
bgneal@312 10450 // Private methods
bgneal@312 10451
bgneal@312 10452 _setActive : function(editor) {
bgneal@312 10453 this.selectedInstance = this.activeEditor = editor;
bgneal@312 10454 }
bgneal@312 10455 });
bgneal@312 10456 })(tinymce);
bgneal@312 10457
bgneal@312 10458 (function(tinymce) {
bgneal@312 10459 // Shorten these names
bgneal@312 10460 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
bgneal@312 10461 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
bgneal@312 10462 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
bgneal@312 10463 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
bgneal@312 10464 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
bgneal@312 10465
bgneal@312 10466 tinymce.create('tinymce.Editor', {
bgneal@312 10467 Editor : function(id, s) {
bgneal@312 10468 var t = this;
bgneal@312 10469
bgneal@312 10470 t.id = t.editorId = id;
bgneal@312 10471
bgneal@312 10472 t.execCommands = {};
bgneal@312 10473 t.queryStateCommands = {};
bgneal@312 10474 t.queryValueCommands = {};
bgneal@312 10475
bgneal@312 10476 t.isNotDirty = false;
bgneal@312 10477
bgneal@312 10478 t.plugins = {};
bgneal@312 10479
bgneal@312 10480 // Add events to the editor
bgneal@312 10481 each([
bgneal@312 10482 'onPreInit',
bgneal@312 10483
bgneal@312 10484 'onBeforeRenderUI',
bgneal@312 10485
bgneal@312 10486 'onPostRender',
bgneal@312 10487
bgneal@312 10488 'onInit',
bgneal@312 10489
bgneal@312 10490 'onRemove',
bgneal@312 10491
bgneal@312 10492 'onActivate',
bgneal@312 10493
bgneal@312 10494 'onDeactivate',
bgneal@312 10495
bgneal@312 10496 'onClick',
bgneal@312 10497
bgneal@312 10498 'onEvent',
bgneal@312 10499
bgneal@312 10500 'onMouseUp',
bgneal@312 10501
bgneal@312 10502 'onMouseDown',
bgneal@312 10503
bgneal@312 10504 'onDblClick',
bgneal@312 10505
bgneal@312 10506 'onKeyDown',
bgneal@312 10507
bgneal@312 10508 'onKeyUp',
bgneal@312 10509
bgneal@312 10510 'onKeyPress',
bgneal@312 10511
bgneal@312 10512 'onContextMenu',
bgneal@312 10513
bgneal@312 10514 'onSubmit',
bgneal@312 10515
bgneal@312 10516 'onReset',
bgneal@312 10517
bgneal@312 10518 'onPaste',
bgneal@312 10519
bgneal@312 10520 'onPreProcess',
bgneal@312 10521
bgneal@312 10522 'onPostProcess',
bgneal@312 10523
bgneal@312 10524 'onBeforeSetContent',
bgneal@312 10525
bgneal@312 10526 'onBeforeGetContent',
bgneal@312 10527
bgneal@312 10528 'onSetContent',
bgneal@312 10529
bgneal@312 10530 'onGetContent',
bgneal@312 10531
bgneal@312 10532 'onLoadContent',
bgneal@312 10533
bgneal@312 10534 'onSaveContent',
bgneal@312 10535
bgneal@312 10536 'onNodeChange',
bgneal@312 10537
bgneal@312 10538 'onChange',
bgneal@312 10539
bgneal@312 10540 'onBeforeExecCommand',
bgneal@312 10541
bgneal@312 10542 'onExecCommand',
bgneal@312 10543
bgneal@312 10544 'onUndo',
bgneal@312 10545
bgneal@312 10546 'onRedo',
bgneal@312 10547
bgneal@312 10548 'onVisualAid',
bgneal@312 10549
bgneal@312 10550 'onSetProgressState'
bgneal@312 10551 ], function(e) {
bgneal@312 10552 t[e] = new Dispatcher(t);
bgneal@312 10553 });
bgneal@312 10554
bgneal@312 10555 t.settings = s = extend({
bgneal@312 10556 id : id,
bgneal@312 10557 language : 'en',
bgneal@312 10558 docs_language : 'en',
bgneal@312 10559 theme : 'simple',
bgneal@312 10560 skin : 'default',
bgneal@312 10561 delta_width : 0,
bgneal@312 10562 delta_height : 0,
bgneal@312 10563 popup_css : '',
bgneal@312 10564 plugins : '',
bgneal@312 10565 document_base_url : tinymce.documentBaseURL,
bgneal@312 10566 add_form_submit_trigger : 1,
bgneal@312 10567 submit_patch : 1,
bgneal@312 10568 add_unload_trigger : 1,
bgneal@312 10569 convert_urls : 1,
bgneal@312 10570 relative_urls : 1,
bgneal@312 10571 remove_script_host : 1,
bgneal@312 10572 table_inline_editing : 0,
bgneal@312 10573 object_resizing : 1,
bgneal@312 10574 cleanup : 1,
bgneal@312 10575 accessibility_focus : 1,
bgneal@312 10576 custom_shortcuts : 1,
bgneal@312 10577 custom_undo_redo_keyboard_shortcuts : 1,
bgneal@312 10578 custom_undo_redo_restore_selection : 1,
bgneal@312 10579 custom_undo_redo : 1,
bgneal@312 10580 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
bgneal@312 10581 visual_table_class : 'mceItemTable',
bgneal@312 10582 visual : 1,
bgneal@312 10583 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
bgneal@312 10584 apply_source_formatting : 1,
bgneal@312 10585 directionality : 'ltr',
bgneal@312 10586 forced_root_block : 'p',
bgneal@312 10587 hidden_input : 1,
bgneal@312 10588 padd_empty_editor : 1,
bgneal@312 10589 render_ui : 1,
bgneal@312 10590 init_theme : 1,
bgneal@312 10591 force_p_newlines : 1,
bgneal@312 10592 indentation : '30px',
bgneal@312 10593 keep_styles : 1,
bgneal@312 10594 fix_table_elements : 1,
bgneal@312 10595 inline_styles : 1,
bgneal@442 10596 convert_fonts_to_spans : true,
bgneal@442 10597 indent : 'simple',
bgneal@442 10598 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
bgneal@442 10599 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
bgneal@442 10600 validate : true,
bgneal@442 10601 entity_encoding : 'named',
bgneal@442 10602 url_converter : t.convertURL,
bgneal@442 10603 url_converter_scope : t,
bgneal@442 10604 ie7_compat : true
bgneal@312 10605 }, s);
bgneal@312 10606
bgneal@312 10607 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
bgneal@312 10608 base_uri : tinyMCE.baseURI
bgneal@312 10609 });
bgneal@312 10610
bgneal@312 10611 t.baseURI = tinymce.baseURI;
bgneal@312 10612
bgneal@442 10613 t.contentCSS = [];
bgneal@442 10614
bgneal@312 10615 // Call setup
bgneal@312 10616 t.execCallback('setup', t);
bgneal@312 10617 },
bgneal@312 10618
bgneal@312 10619 render : function(nst) {
bgneal@312 10620 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
bgneal@312 10621
bgneal@312 10622 // Page is not loaded yet, wait for it
bgneal@312 10623 if (!Event.domLoaded) {
bgneal@312 10624 Event.add(document, 'init', function() {
bgneal@312 10625 t.render();
bgneal@312 10626 });
bgneal@312 10627 return;
bgneal@312 10628 }
bgneal@312 10629
bgneal@312 10630 tinyMCE.settings = s;
bgneal@312 10631
bgneal@312 10632 // Element not found, then skip initialization
bgneal@312 10633 if (!t.getElement())
bgneal@312 10634 return;
bgneal@312 10635
bgneal@312 10636 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
bgneal@312 10637 // browser says it has contentEditable support but there is no visible caret
bgneal@312 10638 // We will remove this check ones Apple implements full contentEditable support
bgneal@312 10639 if (tinymce.isIDevice)
bgneal@312 10640 return;
bgneal@312 10641
bgneal@312 10642 // Add hidden input for non input elements inside form elements
bgneal@312 10643 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
bgneal@312 10644 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
bgneal@312 10645
bgneal@312 10646 if (tinymce.WindowManager)
bgneal@312 10647 t.windowManager = new tinymce.WindowManager(t);
bgneal@312 10648
bgneal@312 10649 if (s.encoding == 'xml') {
bgneal@312 10650 t.onGetContent.add(function(ed, o) {
bgneal@312 10651 if (o.save)
bgneal@312 10652 o.content = DOM.encode(o.content);
bgneal@312 10653 });
bgneal@312 10654 }
bgneal@312 10655
bgneal@312 10656 if (s.add_form_submit_trigger) {
bgneal@312 10657 t.onSubmit.addToTop(function() {
bgneal@312 10658 if (t.initialized) {
bgneal@312 10659 t.save();
bgneal@312 10660 t.isNotDirty = 1;
bgneal@312 10661 }
bgneal@312 10662 });
bgneal@312 10663 }
bgneal@312 10664
bgneal@312 10665 if (s.add_unload_trigger) {
bgneal@312 10666 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
bgneal@312 10667 if (t.initialized && !t.destroyed && !t.isHidden())
bgneal@312 10668 t.save({format : 'raw', no_events : true});
bgneal@312 10669 });
bgneal@312 10670 }
bgneal@312 10671
bgneal@312 10672 tinymce.addUnload(t.destroy, t);
bgneal@312 10673
bgneal@312 10674 if (s.submit_patch) {
bgneal@312 10675 t.onBeforeRenderUI.add(function() {
bgneal@312 10676 var n = t.getElement().form;
bgneal@312 10677
bgneal@312 10678 if (!n)
bgneal@312 10679 return;
bgneal@312 10680
bgneal@312 10681 // Already patched
bgneal@312 10682 if (n._mceOldSubmit)
bgneal@312 10683 return;
bgneal@312 10684
bgneal@312 10685 // Check page uses id="submit" or name="submit" for it's submit button
bgneal@312 10686 if (!n.submit.nodeType && !n.submit.length) {
bgneal@312 10687 t.formElement = n;
bgneal@312 10688 n._mceOldSubmit = n.submit;
bgneal@312 10689 n.submit = function() {
bgneal@312 10690 // Save all instances
bgneal@312 10691 tinymce.triggerSave();
bgneal@312 10692 t.isNotDirty = 1;
bgneal@312 10693
bgneal@312 10694 return t.formElement._mceOldSubmit(t.formElement);
bgneal@312 10695 };
bgneal@312 10696 }
bgneal@312 10697
bgneal@312 10698 n = null;
bgneal@312 10699 });
bgneal@312 10700 }
bgneal@312 10701
bgneal@312 10702 // Load scripts
bgneal@312 10703 function loadScripts() {
bgneal@442 10704 if (s.language && s.language_load !== false)
bgneal@312 10705 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
bgneal@312 10706
bgneal@312 10707 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
bgneal@312 10708 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
bgneal@312 10709
bgneal@312 10710 each(explode(s.plugins), function(p) {
bgneal@312 10711 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
bgneal@312 10712 // Skip safari plugin, since it is removed as of 3.3b1
bgneal@312 10713 if (p == 'safari')
bgneal@312 10714 return;
bgneal@312 10715
bgneal@312 10716 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
bgneal@312 10717 }
bgneal@312 10718 });
bgneal@312 10719
bgneal@312 10720 // Init when que is loaded
bgneal@312 10721 sl.loadQueue(function() {
bgneal@312 10722 if (!t.removed)
bgneal@312 10723 t.init();
bgneal@312 10724 });
bgneal@312 10725 };
bgneal@312 10726
bgneal@312 10727 loadScripts();
bgneal@312 10728 },
bgneal@312 10729
bgneal@312 10730 init : function() {
bgneal@442 10731 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
bgneal@312 10732
bgneal@312 10733 tinymce.add(t);
bgneal@312 10734
bgneal@442 10735 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
bgneal@442 10736
bgneal@312 10737 if (s.theme) {
bgneal@312 10738 s.theme = s.theme.replace(/-/, '');
bgneal@312 10739 o = ThemeManager.get(s.theme);
bgneal@312 10740 t.theme = new o();
bgneal@312 10741
bgneal@312 10742 if (t.theme.init && s.init_theme)
bgneal@312 10743 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
bgneal@312 10744 }
bgneal@312 10745
bgneal@312 10746 // Create all plugins
bgneal@312 10747 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
bgneal@312 10748 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
bgneal@312 10749
bgneal@312 10750 if (c) {
bgneal@312 10751 po = new c(t, u);
bgneal@312 10752
bgneal@312 10753 t.plugins[p] = po;
bgneal@312 10754
bgneal@312 10755 if (po.init)
bgneal@312 10756 po.init(t, u);
bgneal@312 10757 }
bgneal@312 10758 });
bgneal@312 10759
bgneal@312 10760 // Setup popup CSS path(s)
bgneal@312 10761 if (s.popup_css !== false) {
bgneal@312 10762 if (s.popup_css)
bgneal@312 10763 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
bgneal@312 10764 else
bgneal@312 10765 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
bgneal@312 10766 }
bgneal@312 10767
bgneal@312 10768 if (s.popup_css_add)
bgneal@312 10769 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
bgneal@312 10770
bgneal@312 10771 t.controlManager = new tinymce.ControlManager(t);
bgneal@312 10772
bgneal@312 10773 if (s.custom_undo_redo) {
bgneal@312 10774 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@442 10775 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@442 10776 t.undoManager.beforeChange();
bgneal@312 10777 });
bgneal@312 10778
bgneal@312 10779 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
bgneal@312 10780 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
bgneal@312 10781 t.undoManager.add();
bgneal@312 10782 });
bgneal@312 10783 }
bgneal@312 10784
bgneal@312 10785 t.onExecCommand.add(function(ed, c) {
bgneal@312 10786 // Don't refresh the select lists until caret move
bgneal@312 10787 if (!/^(FontName|FontSize)$/.test(c))
bgneal@312 10788 t.nodeChanged();
bgneal@312 10789 });
bgneal@312 10790
bgneal@312 10791 // Remove ghost selections on images and tables in Gecko
bgneal@312 10792 if (isGecko) {
bgneal@312 10793 function repaint(a, o) {
bgneal@312 10794 if (!o || !o.initial)
bgneal@312 10795 t.execCommand('mceRepaint');
bgneal@312 10796 };
bgneal@312 10797
bgneal@312 10798 t.onUndo.add(repaint);
bgneal@312 10799 t.onRedo.add(repaint);
bgneal@312 10800 t.onSetContent.add(repaint);
bgneal@312 10801 }
bgneal@312 10802
bgneal@312 10803 // Enables users to override the control factory
bgneal@312 10804 t.onBeforeRenderUI.dispatch(t, t.controlManager);
bgneal@312 10805
bgneal@312 10806 // Measure box
bgneal@312 10807 if (s.render_ui) {
bgneal@312 10808 w = s.width || e.style.width || e.offsetWidth;
bgneal@312 10809 h = s.height || e.style.height || e.offsetHeight;
bgneal@312 10810 t.orgDisplay = e.style.display;
bgneal@312 10811 re = /^[0-9\.]+(|px)$/i;
bgneal@312 10812
bgneal@312 10813 if (re.test('' + w))
bgneal@312 10814 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
bgneal@312 10815
bgneal@312 10816 if (re.test('' + h))
bgneal@312 10817 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
bgneal@312 10818
bgneal@312 10819 // Render UI
bgneal@312 10820 o = t.theme.renderUI({
bgneal@312 10821 targetNode : e,
bgneal@312 10822 width : w,
bgneal@312 10823 height : h,
bgneal@312 10824 deltaWidth : s.delta_width,
bgneal@312 10825 deltaHeight : s.delta_height
bgneal@312 10826 });
bgneal@312 10827
bgneal@312 10828 t.editorContainer = o.editorContainer;
bgneal@312 10829 }
bgneal@312 10830
bgneal@312 10831
bgneal@312 10832 // User specified a document.domain value
bgneal@312 10833 if (document.domain && location.hostname != document.domain)
bgneal@312 10834 tinymce.relaxedDomain = document.domain;
bgneal@312 10835
bgneal@312 10836 // Resize editor
bgneal@312 10837 DOM.setStyles(o.sizeContainer || o.editorContainer, {
bgneal@312 10838 width : w,
bgneal@312 10839 height : h
bgneal@312 10840 });
bgneal@312 10841
bgneal@442 10842 // Load specified content CSS last
bgneal@442 10843 if (s.content_css) {
bgneal@442 10844 tinymce.each(explode(s.content_css), function(u) {
bgneal@442 10845 t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
bgneal@442 10846 });
bgneal@442 10847 }
bgneal@442 10848
bgneal@312 10849 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
bgneal@312 10850 if (h < 100)
bgneal@312 10851 h = 100;
bgneal@312 10852
bgneal@312 10853 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
bgneal@312 10854
bgneal@312 10855 // We only need to override paths if we have to
bgneal@312 10856 // IE has a bug where it remove site absolute urls to relative ones if this is specified
bgneal@312 10857 if (s.document_base_url != tinymce.documentBaseURL)
bgneal@312 10858 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
bgneal@312 10859
bgneal@442 10860 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
bgneal@442 10861 if (s.ie7_compat)
bgneal@442 10862 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
bgneal@442 10863 else
bgneal@442 10864 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
bgneal@442 10865
bgneal@442 10866 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
bgneal@442 10867
bgneal@442 10868 // Firefox 2 doesn't load stylesheets correctly this way
bgneal@442 10869 if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
bgneal@442 10870 for (i = 0; i < t.contentCSS.length; i++)
bgneal@442 10871 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
bgneal@442 10872
bgneal@442 10873 t.contentCSS = [];
bgneal@442 10874 }
bgneal@312 10875
bgneal@312 10876 bi = s.body_id || 'tinymce';
bgneal@312 10877 if (bi.indexOf('=') != -1) {
bgneal@312 10878 bi = t.getParam('body_id', '', 'hash');
bgneal@312 10879 bi = bi[t.id] || bi;
bgneal@312 10880 }
bgneal@312 10881
bgneal@312 10882 bc = s.body_class || '';
bgneal@312 10883 if (bc.indexOf('=') != -1) {
bgneal@312 10884 bc = t.getParam('body_class', '', 'hash');
bgneal@312 10885 bc = bc[t.id] || '';
bgneal@312 10886 }
bgneal@312 10887
bgneal@312 10888 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
bgneal@312 10889
bgneal@312 10890 // Domain relaxing enabled, then set document domain
bgneal@442 10891 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
bgneal@312 10892 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
bgneal@442 10893 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
bgneal@312 10894 }
bgneal@312 10895
bgneal@312 10896 // Create iframe
bgneal@442 10897 // TODO: ACC add the appropriate description on this.
bgneal@442 10898 n = DOM.add(o.iframeContainer, 'iframe', {
bgneal@312 10899 id : t.id + "_ifr",
bgneal@312 10900 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
bgneal@442 10901 frameBorder : '0',
bgneal@442 10902 title : s.aria_label,
bgneal@312 10903 style : {
bgneal@312 10904 width : '100%',
bgneal@312 10905 height : h
bgneal@312 10906 }
bgneal@312 10907 });
bgneal@312 10908
bgneal@312 10909 t.contentAreaContainer = o.iframeContainer;
bgneal@312 10910 DOM.get(o.editorContainer).style.display = t.orgDisplay;
bgneal@312 10911 DOM.get(t.id).style.display = 'none';
bgneal@442 10912 DOM.setAttrib(t.id, 'aria-hidden', true);
bgneal@442 10913
bgneal@442 10914 if (!tinymce.relaxedDomain || !u)
bgneal@312 10915 t.setupIframe();
bgneal@312 10916
bgneal@312 10917 e = n = o = null; // Cleanup
bgneal@312 10918 },
bgneal@312 10919
bgneal@312 10920 setupIframe : function() {
bgneal@312 10921 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
bgneal@312 10922
bgneal@312 10923 // Setup iframe body
bgneal@312 10924 if (!isIE || !tinymce.relaxedDomain) {
bgneal@312 10925 d.open();
bgneal@312 10926 d.write(t.iframeHTML);
bgneal@312 10927 d.close();
bgneal@442 10928
bgneal@442 10929 if (tinymce.relaxedDomain)
bgneal@442 10930 d.domain = tinymce.relaxedDomain;
bgneal@312 10931 }
bgneal@312 10932
bgneal@312 10933 // Design mode needs to be added here Ctrl+A will fail otherwise
bgneal@312 10934 if (!isIE) {
bgneal@312 10935 try {
bgneal@312 10936 if (!s.readonly)
bgneal@312 10937 d.designMode = 'On';
bgneal@312 10938 } catch (ex) {
bgneal@312 10939 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@312 10940 // The design mode will be set ones the editor is focused
bgneal@312 10941 }
bgneal@312 10942 }
bgneal@312 10943
bgneal@312 10944 // IE needs to use contentEditable or it will display non secure items for HTTPS
bgneal@312 10945 if (isIE) {
bgneal@312 10946 // It will not steal focus if we hide it while setting contentEditable
bgneal@312 10947 b = t.getBody();
bgneal@312 10948 DOM.hide(b);
bgneal@312 10949
bgneal@312 10950 if (!s.readonly)
bgneal@312 10951 b.contentEditable = true;
bgneal@312 10952
bgneal@312 10953 DOM.show(b);
bgneal@312 10954 }
bgneal@312 10955
bgneal@442 10956 t.schema = new tinymce.html.Schema(s);
bgneal@442 10957
bgneal@312 10958 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
bgneal@312 10959 keep_values : true,
bgneal@312 10960 url_converter : t.convertURL,
bgneal@312 10961 url_converter_scope : t,
bgneal@312 10962 hex_colors : s.force_hex_style_colors,
bgneal@312 10963 class_filter : s.class_filter,
bgneal@312 10964 update_styles : 1,
bgneal@312 10965 fix_ie_paragraphs : 1,
bgneal@312 10966 schema : t.schema
bgneal@442 10967 });
bgneal@442 10968
bgneal@442 10969 t.parser = new tinymce.html.DomParser(s, t.schema);
bgneal@442 10970
bgneal@442 10971 // Force anchor names closed
bgneal@442 10972 t.parser.addAttributeFilter('name', function(nodes, name) {
bgneal@442 10973 var i = nodes.length, sibling, prevSibling, parent, node;
bgneal@442 10974
bgneal@442 10975 while (i--) {
bgneal@442 10976 node = nodes[i];
bgneal@442 10977 if (node.name === 'a' && node.firstChild) {
bgneal@442 10978 parent = node.parent;
bgneal@442 10979
bgneal@442 10980 // Move children after current node
bgneal@442 10981 sibling = node.lastChild;
bgneal@442 10982 do {
bgneal@442 10983 prevSibling = sibling.prev;
bgneal@442 10984 parent.insert(sibling, node);
bgneal@442 10985 sibling = prevSibling;
bgneal@442 10986 } while (sibling);
bgneal@442 10987 }
bgneal@442 10988 }
bgneal@442 10989 });
bgneal@442 10990
bgneal@442 10991 // Convert src and href into data-mce-src, data-mce-href and data-mce-style
bgneal@442 10992 t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
bgneal@442 10993 var i = nodes.length, node, dom = t.dom, value;
bgneal@442 10994
bgneal@442 10995 while (i--) {
bgneal@442 10996 node = nodes[i];
bgneal@442 10997 value = node.attr(name);
bgneal@442 10998
bgneal@442 10999 if (name === "style")
bgneal@442 11000 node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
bgneal@442 11001 else
bgneal@442 11002 node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
bgneal@442 11003 }
bgneal@442 11004 });
bgneal@442 11005
bgneal@442 11006 // Keep scripts from executing
bgneal@442 11007 t.parser.addNodeFilter('script', function(nodes, name) {
bgneal@442 11008 var i = nodes.length;
bgneal@442 11009
bgneal@442 11010 while (i--)
bgneal@442 11011 nodes[i].attr('type', 'mce-text/javascript');
bgneal@442 11012 });
bgneal@442 11013
bgneal@442 11014 t.parser.addNodeFilter('#cdata', function(nodes, name) {
bgneal@442 11015 var i = nodes.length, node;
bgneal@442 11016
bgneal@442 11017 while (i--) {
bgneal@442 11018 node = nodes[i];
bgneal@442 11019 node.type = 8;
bgneal@442 11020 node.name = '#comment';
bgneal@442 11021 node.value = '[CDATA[' + node.value + ']]';
bgneal@442 11022 }
bgneal@442 11023 });
bgneal@442 11024
bgneal@442 11025 t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
bgneal@442 11026 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
bgneal@442 11027
bgneal@442 11028 while (i--) {
bgneal@442 11029 node = nodes[i];
bgneal@442 11030
bgneal@442 11031 if (node.isEmpty(nonEmptyElements))
bgneal@442 11032 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
bgneal@442 11033 }
bgneal@442 11034 });
bgneal@442 11035
bgneal@442 11036 t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
bgneal@312 11037
bgneal@312 11038 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
bgneal@312 11039
bgneal@312 11040 t.formatter = new tinymce.Formatter(this);
bgneal@312 11041
bgneal@312 11042 // Register default formats
bgneal@312 11043 t.formatter.register({
bgneal@312 11044 alignleft : [
bgneal@312 11045 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
bgneal@442 11046 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
bgneal@312 11047 ],
bgneal@312 11048
bgneal@312 11049 aligncenter : [
bgneal@312 11050 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
bgneal@442 11051 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
bgneal@442 11052 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
bgneal@312 11053 ],
bgneal@312 11054
bgneal@312 11055 alignright : [
bgneal@312 11056 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
bgneal@442 11057 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
bgneal@312 11058 ],
bgneal@312 11059
bgneal@312 11060 alignfull : [
bgneal@312 11061 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
bgneal@312 11062 ],
bgneal@312 11063
bgneal@312 11064 bold : [
bgneal@442 11065 {inline : 'strong', remove : 'all'},
bgneal@312 11066 {inline : 'span', styles : {fontWeight : 'bold'}},
bgneal@442 11067 {inline : 'b', remove : 'all'}
bgneal@312 11068 ],
bgneal@312 11069
bgneal@312 11070 italic : [
bgneal@442 11071 {inline : 'em', remove : 'all'},
bgneal@312 11072 {inline : 'span', styles : {fontStyle : 'italic'}},
bgneal@442 11073 {inline : 'i', remove : 'all'}
bgneal@312 11074 ],
bgneal@312 11075
bgneal@312 11076 underline : [
bgneal@312 11077 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
bgneal@442 11078 {inline : 'u', remove : 'all'}
bgneal@312 11079 ],
bgneal@312 11080
bgneal@312 11081 strikethrough : [
bgneal@312 11082 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
bgneal@442 11083 {inline : 'strike', remove : 'all'}
bgneal@312 11084 ],
bgneal@312 11085
bgneal@442 11086 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
bgneal@442 11087 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
bgneal@312 11088 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
bgneal@312 11089 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
bgneal@312 11090 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
bgneal@312 11091 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
bgneal@442 11092 subscript : {inline : 'sub'},
bgneal@442 11093 superscript : {inline : 'sup'},
bgneal@312 11094
bgneal@312 11095 removeformat : [
bgneal@312 11096 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
bgneal@312 11097 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
bgneal@312 11098 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
bgneal@312 11099 ]
bgneal@312 11100 });
bgneal@312 11101
bgneal@312 11102 // Register default block formats
bgneal@312 11103 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
bgneal@312 11104 t.formatter.register(name, {block : name, remove : 'all'});
bgneal@312 11105 });
bgneal@312 11106
bgneal@312 11107 // Register user defined formats
bgneal@312 11108 t.formatter.register(t.settings.formats);
bgneal@312 11109
bgneal@312 11110 t.undoManager = new tinymce.UndoManager(t);
bgneal@312 11111
bgneal@312 11112 // Pass through
bgneal@312 11113 t.undoManager.onAdd.add(function(um, l) {
bgneal@442 11114 if (um.hasUndo())
bgneal@312 11115 return t.onChange.dispatch(t, l, um);
bgneal@312 11116 });
bgneal@312 11117
bgneal@312 11118 t.undoManager.onUndo.add(function(um, l) {
bgneal@312 11119 return t.onUndo.dispatch(t, l, um);
bgneal@312 11120 });
bgneal@312 11121
bgneal@312 11122 t.undoManager.onRedo.add(function(um, l) {
bgneal@312 11123 return t.onRedo.dispatch(t, l, um);
bgneal@312 11124 });
bgneal@312 11125
bgneal@312 11126 t.forceBlocks = new tinymce.ForceBlocks(t, {
bgneal@312 11127 forced_root_block : s.forced_root_block
bgneal@312 11128 });
bgneal@312 11129
bgneal@312 11130 t.editorCommands = new tinymce.EditorCommands(t);
bgneal@312 11131
bgneal@312 11132 // Pass through
bgneal@312 11133 t.serializer.onPreProcess.add(function(se, o) {
bgneal@312 11134 return t.onPreProcess.dispatch(t, o, se);
bgneal@312 11135 });
bgneal@312 11136
bgneal@312 11137 t.serializer.onPostProcess.add(function(se, o) {
bgneal@312 11138 return t.onPostProcess.dispatch(t, o, se);
bgneal@312 11139 });
bgneal@312 11140
bgneal@312 11141 t.onPreInit.dispatch(t);
bgneal@312 11142
bgneal@312 11143 if (!s.gecko_spellcheck)
bgneal@312 11144 t.getBody().spellcheck = 0;
bgneal@312 11145
bgneal@312 11146 if (!s.readonly)
bgneal@312 11147 t._addEvents();
bgneal@312 11148
bgneal@312 11149 t.controlManager.onPostRender.dispatch(t, t.controlManager);
bgneal@312 11150 t.onPostRender.dispatch(t);
bgneal@312 11151
bgneal@312 11152 if (s.directionality)
bgneal@312 11153 t.getBody().dir = s.directionality;
bgneal@312 11154
bgneal@312 11155 if (s.nowrap)
bgneal@312 11156 t.getBody().style.whiteSpace = "nowrap";
bgneal@312 11157
bgneal@312 11158 if (s.handle_node_change_callback) {
bgneal@312 11159 t.onNodeChange.add(function(ed, cm, n) {
bgneal@312 11160 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
bgneal@312 11161 });
bgneal@312 11162 }
bgneal@312 11163
bgneal@312 11164 if (s.save_callback) {
bgneal@312 11165 t.onSaveContent.add(function(ed, o) {
bgneal@312 11166 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@312 11167
bgneal@312 11168 if (h)
bgneal@312 11169 o.content = h;
bgneal@312 11170 });
bgneal@312 11171 }
bgneal@312 11172
bgneal@312 11173 if (s.onchange_callback) {
bgneal@312 11174 t.onChange.add(function(ed, l) {
bgneal@312 11175 t.execCallback('onchange_callback', t, l);
bgneal@312 11176 });
bgneal@312 11177 }
bgneal@312 11178
bgneal@442 11179 if (s.protect) {
bgneal@442 11180 t.onBeforeSetContent.add(function(ed, o) {
bgneal@442 11181 if (s.protect) {
bgneal@442 11182 each(s.protect, function(pattern) {
bgneal@442 11183 o.content = o.content.replace(pattern, function(str) {
bgneal@442 11184 return '<!--mce:protected ' + escape(str) + '-->';
bgneal@442 11185 });
bgneal@442 11186 });
bgneal@442 11187 }
bgneal@442 11188 });
bgneal@442 11189 }
bgneal@442 11190
bgneal@312 11191 if (s.convert_newlines_to_brs) {
bgneal@312 11192 t.onBeforeSetContent.add(function(ed, o) {
bgneal@312 11193 if (o.initial)
bgneal@312 11194 o.content = o.content.replace(/\r?\n/g, '<br />');
bgneal@312 11195 });
bgneal@312 11196 }
bgneal@312 11197
bgneal@312 11198 if (s.preformatted) {
bgneal@312 11199 t.onPostProcess.add(function(ed, o) {
bgneal@312 11200 o.content = o.content.replace(/^\s*<pre.*?>/, '');
bgneal@312 11201 o.content = o.content.replace(/<\/pre>\s*$/, '');
bgneal@312 11202
bgneal@312 11203 if (o.set)
bgneal@312 11204 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
bgneal@312 11205 });
bgneal@312 11206 }
bgneal@312 11207
bgneal@312 11208 if (s.verify_css_classes) {
bgneal@312 11209 t.serializer.attribValueFilter = function(n, v) {
bgneal@312 11210 var s, cl;
bgneal@312 11211
bgneal@312 11212 if (n == 'class') {
bgneal@312 11213 // Build regexp for classes
bgneal@312 11214 if (!t.classesRE) {
bgneal@312 11215 cl = t.dom.getClasses();
bgneal@312 11216
bgneal@312 11217 if (cl.length > 0) {
bgneal@312 11218 s = '';
bgneal@312 11219
bgneal@312 11220 each (cl, function(o) {
bgneal@312 11221 s += (s ? '|' : '') + o['class'];
bgneal@312 11222 });
bgneal@312 11223
bgneal@312 11224 t.classesRE = new RegExp('(' + s + ')', 'gi');
bgneal@312 11225 }
bgneal@312 11226 }
bgneal@312 11227
bgneal@312 11228 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
bgneal@312 11229 }
bgneal@312 11230
bgneal@312 11231 return v;
bgneal@312 11232 };
bgneal@312 11233 }
bgneal@312 11234
bgneal@312 11235 if (s.cleanup_callback) {
bgneal@312 11236 t.onBeforeSetContent.add(function(ed, o) {
bgneal@312 11237 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@312 11238 });
bgneal@312 11239
bgneal@312 11240 t.onPreProcess.add(function(ed, o) {
bgneal@312 11241 if (o.set)
bgneal@312 11242 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
bgneal@312 11243
bgneal@312 11244 if (o.get)
bgneal@312 11245 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
bgneal@312 11246 });
bgneal@312 11247
bgneal@312 11248 t.onPostProcess.add(function(ed, o) {
bgneal@312 11249 if (o.set)
bgneal@312 11250 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
bgneal@312 11251
bgneal@312 11252 if (o.get)
bgneal@312 11253 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
bgneal@312 11254 });
bgneal@312 11255 }
bgneal@312 11256
bgneal@312 11257 if (s.save_callback) {
bgneal@312 11258 t.onGetContent.add(function(ed, o) {
bgneal@312 11259 if (o.save)
bgneal@312 11260 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
bgneal@312 11261 });
bgneal@312 11262 }
bgneal@312 11263
bgneal@312 11264 if (s.handle_event_callback) {
bgneal@312 11265 t.onEvent.add(function(ed, e, o) {
bgneal@312 11266 if (t.execCallback('handle_event_callback', e, ed, o) === false)
bgneal@312 11267 Event.cancel(e);
bgneal@312 11268 });
bgneal@312 11269 }
bgneal@312 11270
bgneal@312 11271 // Add visual aids when new contents is added
bgneal@312 11272 t.onSetContent.add(function() {
bgneal@312 11273 t.addVisual(t.getBody());
bgneal@312 11274 });
bgneal@312 11275
bgneal@312 11276 // Remove empty contents
bgneal@312 11277 if (s.padd_empty_editor) {
bgneal@312 11278 t.onPostProcess.add(function(ed, o) {
bgneal@312 11279 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
bgneal@312 11280 });
bgneal@312 11281 }
bgneal@312 11282
bgneal@312 11283 if (isGecko) {
bgneal@312 11284 // Fix gecko link bug, when a link is placed at the end of block elements there is
bgneal@312 11285 // no way to move the caret behind the link. This fix adds a bogus br element after the link
bgneal@312 11286 function fixLinks(ed, o) {
bgneal@312 11287 each(ed.dom.select('a'), function(n) {
bgneal@312 11288 var pn = n.parentNode;
bgneal@312 11289
bgneal@312 11290 if (ed.dom.isBlock(pn) && pn.lastChild === n)
bgneal@442 11291 ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
bgneal@312 11292 });
bgneal@312 11293 };
bgneal@312 11294
bgneal@312 11295 t.onExecCommand.add(function(ed, cmd) {
bgneal@312 11296 if (cmd === 'CreateLink')
bgneal@312 11297 fixLinks(ed);
bgneal@312 11298 });
bgneal@312 11299
bgneal@312 11300 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
bgneal@312 11301
bgneal@312 11302 if (!s.readonly) {
bgneal@312 11303 try {
bgneal@312 11304 // Design mode must be set here once again to fix a bug where
bgneal@312 11305 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
bgneal@312 11306 d.designMode = 'Off';
bgneal@312 11307 d.designMode = 'On';
bgneal@312 11308 } catch (ex) {
bgneal@312 11309 // Will fail on Gecko if the editor is placed in an hidden container element
bgneal@312 11310 // The design mode will be set ones the editor is focused
bgneal@312 11311 }
bgneal@312 11312 }
bgneal@312 11313 }
bgneal@312 11314
bgneal@312 11315 // A small timeout was needed since firefox will remove. Bug: #1838304
bgneal@312 11316 setTimeout(function () {
bgneal@312 11317 if (t.removed)
bgneal@312 11318 return;
bgneal@312 11319
bgneal@442 11320 t.load({initial : true, format : 'html'});
bgneal@312 11321 t.startContent = t.getContent({format : 'raw'});
bgneal@442 11322 t.undoManager.add();
bgneal@312 11323 t.initialized = true;
bgneal@312 11324
bgneal@312 11325 t.onInit.dispatch(t);
bgneal@312 11326 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
bgneal@312 11327 t.execCallback('init_instance_callback', t);
bgneal@312 11328 t.focus(true);
bgneal@312 11329 t.nodeChanged({initial : 1});
bgneal@312 11330
bgneal@312 11331 // Load specified content CSS last
bgneal@442 11332 each(t.contentCSS, function(u) {
bgneal@442 11333 t.dom.loadCSS(u);
bgneal@442 11334 });
bgneal@312 11335
bgneal@312 11336 // Handle auto focus
bgneal@312 11337 if (s.auto_focus) {
bgneal@312 11338 setTimeout(function () {
bgneal@312 11339 var ed = tinymce.get(s.auto_focus);
bgneal@312 11340
bgneal@312 11341 ed.selection.select(ed.getBody(), 1);
bgneal@312 11342 ed.selection.collapse(1);
bgneal@312 11343 ed.getWin().focus();
bgneal@312 11344 }, 100);
bgneal@312 11345 }
bgneal@312 11346 }, 1);
bgneal@312 11347
bgneal@312 11348 e = null;
bgneal@312 11349 },
bgneal@312 11350
bgneal@312 11351
bgneal@312 11352 focus : function(sf) {
bgneal@312 11353 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
bgneal@312 11354
bgneal@312 11355 if (!sf) {
bgneal@312 11356 // Get selected control element
bgneal@312 11357 ieRng = t.selection.getRng();
bgneal@312 11358 if (ieRng.item) {
bgneal@312 11359 controlElm = ieRng.item(0);
bgneal@312 11360 }
bgneal@312 11361
bgneal@312 11362 // Is not content editable
bgneal@312 11363 if (!ce)
bgneal@312 11364 t.getWin().focus();
bgneal@312 11365
bgneal@312 11366 // Restore selected control element
bgneal@312 11367 // This is needed when for example an image is selected within a
bgneal@312 11368 // layer a call to focus will then remove the control selection
bgneal@312 11369 if (controlElm && controlElm.ownerDocument == doc) {
bgneal@312 11370 ieRng = doc.body.createControlRange();
bgneal@312 11371 ieRng.addElement(controlElm);
bgneal@312 11372 ieRng.select();
bgneal@312 11373 }
bgneal@312 11374
bgneal@312 11375 }
bgneal@312 11376
bgneal@312 11377 if (tinymce.activeEditor != t) {
bgneal@312 11378 if ((oed = tinymce.activeEditor) != null)
bgneal@312 11379 oed.onDeactivate.dispatch(oed, t);
bgneal@312 11380
bgneal@312 11381 t.onActivate.dispatch(t, oed);
bgneal@312 11382 }
bgneal@312 11383
bgneal@312 11384 tinymce._setActive(t);
bgneal@312 11385 },
bgneal@312 11386
bgneal@312 11387 execCallback : function(n) {
bgneal@312 11388 var t = this, f = t.settings[n], s;
bgneal@312 11389
bgneal@312 11390 if (!f)
bgneal@312 11391 return;
bgneal@312 11392
bgneal@312 11393 // Look through lookup
bgneal@312 11394 if (t.callbackLookup && (s = t.callbackLookup[n])) {
bgneal@312 11395 f = s.func;
bgneal@312 11396 s = s.scope;
bgneal@312 11397 }
bgneal@312 11398
bgneal@312 11399 if (is(f, 'string')) {
bgneal@312 11400 s = f.replace(/\.\w+$/, '');
bgneal@312 11401 s = s ? tinymce.resolve(s) : 0;
bgneal@312 11402 f = tinymce.resolve(f);
bgneal@312 11403 t.callbackLookup = t.callbackLookup || {};
bgneal@312 11404 t.callbackLookup[n] = {func : f, scope : s};
bgneal@312 11405 }
bgneal@312 11406
bgneal@312 11407 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
bgneal@312 11408 },
bgneal@312 11409
bgneal@312 11410 translate : function(s) {
bgneal@312 11411 var c = this.settings.language || 'en', i18n = tinymce.i18n;
bgneal@312 11412
bgneal@312 11413 if (!s)
bgneal@312 11414 return '';
bgneal@312 11415
bgneal@312 11416 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
bgneal@312 11417 return i18n[c + '.' + b] || '{#' + b + '}';
bgneal@312 11418 });
bgneal@312 11419 },
bgneal@312 11420
bgneal@312 11421 getLang : function(n, dv) {
bgneal@312 11422 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
bgneal@312 11423 },
bgneal@312 11424
bgneal@312 11425 getParam : function(n, dv, ty) {
bgneal@312 11426 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
bgneal@312 11427
bgneal@312 11428 if (ty === 'hash') {
bgneal@312 11429 o = {};
bgneal@312 11430
bgneal@312 11431 if (is(v, 'string')) {
bgneal@312 11432 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
bgneal@312 11433 v = v.split('=');
bgneal@312 11434
bgneal@312 11435 if (v.length > 1)
bgneal@312 11436 o[tr(v[0])] = tr(v[1]);
bgneal@312 11437 else
bgneal@312 11438 o[tr(v[0])] = tr(v);
bgneal@312 11439 });
bgneal@312 11440 } else
bgneal@312 11441 o = v;
bgneal@312 11442
bgneal@312 11443 return o;
bgneal@312 11444 }
bgneal@312 11445
bgneal@312 11446 return v;
bgneal@312 11447 },
bgneal@312 11448
bgneal@312 11449 nodeChanged : function(o) {
bgneal@442 11450 var t = this, s = t.selection, n = s.getStart() || t.getBody();
bgneal@312 11451
bgneal@312 11452 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
bgneal@312 11453 if (t.initialized) {
bgneal@312 11454 o = o || {};
bgneal@312 11455 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
bgneal@312 11456
bgneal@312 11457 // Get parents and add them to object
bgneal@312 11458 o.parents = [];
bgneal@312 11459 t.dom.getParent(n, function(node) {
bgneal@312 11460 if (node.nodeName == 'BODY')
bgneal@312 11461 return true;
bgneal@312 11462
bgneal@312 11463 o.parents.push(node);
bgneal@312 11464 });
bgneal@312 11465
bgneal@312 11466 t.onNodeChange.dispatch(
bgneal@312 11467 t,
bgneal@312 11468 o ? o.controlManager || t.controlManager : t.controlManager,
bgneal@312 11469 n,
bgneal@312 11470 s.isCollapsed(),
bgneal@312 11471 o
bgneal@312 11472 );
bgneal@312 11473 }
bgneal@312 11474 },
bgneal@312 11475
bgneal@312 11476 addButton : function(n, s) {
bgneal@312 11477 var t = this;
bgneal@312 11478
bgneal@312 11479 t.buttons = t.buttons || {};
bgneal@312 11480 t.buttons[n] = s;
bgneal@312 11481 },
bgneal@312 11482
bgneal@442 11483 addCommand : function(name, callback, scope) {
bgneal@442 11484 this.execCommands[name] = {func : callback, scope : scope || this};
bgneal@442 11485 },
bgneal@442 11486
bgneal@442 11487 addQueryStateHandler : function(name, callback, scope) {
bgneal@442 11488 this.queryStateCommands[name] = {func : callback, scope : scope || this};
bgneal@442 11489 },
bgneal@442 11490
bgneal@442 11491 addQueryValueHandler : function(name, callback, scope) {
bgneal@442 11492 this.queryValueCommands[name] = {func : callback, scope : scope || this};
bgneal@312 11493 },
bgneal@312 11494
bgneal@312 11495 addShortcut : function(pa, desc, cmd_func, sc) {
bgneal@312 11496 var t = this, c;
bgneal@312 11497
bgneal@312 11498 if (!t.settings.custom_shortcuts)
bgneal@312 11499 return false;
bgneal@312 11500
bgneal@312 11501 t.shortcuts = t.shortcuts || {};
bgneal@312 11502
bgneal@312 11503 if (is(cmd_func, 'string')) {
bgneal@312 11504 c = cmd_func;
bgneal@312 11505
bgneal@312 11506 cmd_func = function() {
bgneal@312 11507 t.execCommand(c, false, null);
bgneal@312 11508 };
bgneal@312 11509 }
bgneal@312 11510
bgneal@312 11511 if (is(cmd_func, 'object')) {
bgneal@312 11512 c = cmd_func;
bgneal@312 11513
bgneal@312 11514 cmd_func = function() {
bgneal@312 11515 t.execCommand(c[0], c[1], c[2]);
bgneal@312 11516 };
bgneal@312 11517 }
bgneal@312 11518
bgneal@312 11519 each(explode(pa), function(pa) {
bgneal@312 11520 var o = {
bgneal@312 11521 func : cmd_func,
bgneal@312 11522 scope : sc || this,
bgneal@312 11523 desc : desc,
bgneal@312 11524 alt : false,
bgneal@312 11525 ctrl : false,
bgneal@312 11526 shift : false
bgneal@312 11527 };
bgneal@312 11528
bgneal@312 11529 each(explode(pa, '+'), function(v) {
bgneal@312 11530 switch (v) {
bgneal@312 11531 case 'alt':
bgneal@312 11532 case 'ctrl':
bgneal@312 11533 case 'shift':
bgneal@312 11534 o[v] = true;
bgneal@312 11535 break;
bgneal@312 11536
bgneal@312 11537 default:
bgneal@312 11538 o.charCode = v.charCodeAt(0);
bgneal@312 11539 o.keyCode = v.toUpperCase().charCodeAt(0);
bgneal@312 11540 }
bgneal@312 11541 });
bgneal@312 11542
bgneal@312 11543 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
bgneal@312 11544 });
bgneal@312 11545
bgneal@312 11546 return true;
bgneal@312 11547 },
bgneal@312 11548
bgneal@312 11549 execCommand : function(cmd, ui, val, a) {
bgneal@312 11550 var t = this, s = 0, o, st;
bgneal@312 11551
bgneal@312 11552 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
bgneal@312 11553 t.focus();
bgneal@312 11554
bgneal@312 11555 o = {};
bgneal@312 11556 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
bgneal@312 11557 if (o.terminate)
bgneal@312 11558 return false;
bgneal@312 11559
bgneal@312 11560 // Command callback
bgneal@312 11561 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
bgneal@312 11562 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11563 return true;
bgneal@312 11564 }
bgneal@312 11565
bgneal@312 11566 // Registred commands
bgneal@312 11567 if (o = t.execCommands[cmd]) {
bgneal@312 11568 st = o.func.call(o.scope, ui, val);
bgneal@312 11569
bgneal@312 11570 // Fall through on true
bgneal@312 11571 if (st !== true) {
bgneal@312 11572 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11573 return st;
bgneal@312 11574 }
bgneal@312 11575 }
bgneal@312 11576
bgneal@312 11577 // Plugin commands
bgneal@312 11578 each(t.plugins, function(p) {
bgneal@312 11579 if (p.execCommand && p.execCommand(cmd, ui, val)) {
bgneal@312 11580 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11581 s = 1;
bgneal@312 11582 return false;
bgneal@312 11583 }
bgneal@312 11584 });
bgneal@312 11585
bgneal@312 11586 if (s)
bgneal@312 11587 return true;
bgneal@312 11588
bgneal@312 11589 // Theme commands
bgneal@312 11590 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
bgneal@312 11591 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11592 return true;
bgneal@312 11593 }
bgneal@312 11594
bgneal@312 11595 // Editor commands
bgneal@312 11596 if (t.editorCommands.execCommand(cmd, ui, val)) {
bgneal@312 11597 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11598 return true;
bgneal@312 11599 }
bgneal@312 11600
bgneal@312 11601 // Browser commands
bgneal@312 11602 t.getDoc().execCommand(cmd, ui, val);
bgneal@312 11603 t.onExecCommand.dispatch(t, cmd, ui, val, a);
bgneal@312 11604 },
bgneal@312 11605
bgneal@312 11606 queryCommandState : function(cmd) {
bgneal@312 11607 var t = this, o, s;
bgneal@312 11608
bgneal@312 11609 // Is hidden then return undefined
bgneal@312 11610 if (t._isHidden())
bgneal@312 11611 return;
bgneal@312 11612
bgneal@312 11613 // Registred commands
bgneal@312 11614 if (o = t.queryStateCommands[cmd]) {
bgneal@312 11615 s = o.func.call(o.scope);
bgneal@312 11616
bgneal@312 11617 // Fall though on true
bgneal@312 11618 if (s !== true)
bgneal@312 11619 return s;
bgneal@312 11620 }
bgneal@312 11621
bgneal@312 11622 // Registred commands
bgneal@312 11623 o = t.editorCommands.queryCommandState(cmd);
bgneal@312 11624 if (o !== -1)
bgneal@312 11625 return o;
bgneal@312 11626
bgneal@312 11627 // Browser commands
bgneal@312 11628 try {
bgneal@312 11629 return this.getDoc().queryCommandState(cmd);
bgneal@312 11630 } catch (ex) {
bgneal@312 11631 // Fails sometimes see bug: 1896577
bgneal@312 11632 }
bgneal@312 11633 },
bgneal@312 11634
bgneal@312 11635 queryCommandValue : function(c) {
bgneal@312 11636 var t = this, o, s;
bgneal@312 11637
bgneal@312 11638 // Is hidden then return undefined
bgneal@312 11639 if (t._isHidden())
bgneal@312 11640 return;
bgneal@312 11641
bgneal@312 11642 // Registred commands
bgneal@312 11643 if (o = t.queryValueCommands[c]) {
bgneal@312 11644 s = o.func.call(o.scope);
bgneal@312 11645
bgneal@312 11646 // Fall though on true
bgneal@312 11647 if (s !== true)
bgneal@312 11648 return s;
bgneal@312 11649 }
bgneal@312 11650
bgneal@312 11651 // Registred commands
bgneal@312 11652 o = t.editorCommands.queryCommandValue(c);
bgneal@312 11653 if (is(o))
bgneal@312 11654 return o;
bgneal@312 11655
bgneal@312 11656 // Browser commands
bgneal@312 11657 try {
bgneal@312 11658 return this.getDoc().queryCommandValue(c);
bgneal@312 11659 } catch (ex) {
bgneal@312 11660 // Fails sometimes see bug: 1896577
bgneal@312 11661 }
bgneal@312 11662 },
bgneal@312 11663
bgneal@312 11664 show : function() {
bgneal@312 11665 var t = this;
bgneal@312 11666
bgneal@312 11667 DOM.show(t.getContainer());
bgneal@312 11668 DOM.hide(t.id);
bgneal@312 11669 t.load();
bgneal@312 11670 },
bgneal@312 11671
bgneal@312 11672 hide : function() {
bgneal@312 11673 var t = this, d = t.getDoc();
bgneal@312 11674
bgneal@312 11675 // Fixed bug where IE has a blinking cursor left from the editor
bgneal@312 11676 if (isIE && d)
bgneal@312 11677 d.execCommand('SelectAll');
bgneal@312 11678
bgneal@312 11679 // We must save before we hide so Safari doesn't crash
bgneal@312 11680 t.save();
bgneal@312 11681 DOM.hide(t.getContainer());
bgneal@312 11682 DOM.setStyle(t.id, 'display', t.orgDisplay);
bgneal@312 11683 },
bgneal@312 11684
bgneal@312 11685 isHidden : function() {
bgneal@312 11686 return !DOM.isHidden(this.id);
bgneal@312 11687 },
bgneal@312 11688
bgneal@312 11689 setProgressState : function(b, ti, o) {
bgneal@312 11690 this.onSetProgressState.dispatch(this, b, ti, o);
bgneal@312 11691
bgneal@312 11692 return b;
bgneal@312 11693 },
bgneal@312 11694
bgneal@312 11695 load : function(o) {
bgneal@312 11696 var t = this, e = t.getElement(), h;
bgneal@312 11697
bgneal@312 11698 if (e) {
bgneal@312 11699 o = o || {};
bgneal@312 11700 o.load = true;
bgneal@312 11701
bgneal@312 11702 // Double encode existing entities in the value
bgneal@312 11703 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
bgneal@312 11704 o.element = e;
bgneal@312 11705
bgneal@312 11706 if (!o.no_events)
bgneal@312 11707 t.onLoadContent.dispatch(t, o);
bgneal@312 11708
bgneal@312 11709 o.element = e = null;
bgneal@312 11710
bgneal@312 11711 return h;
bgneal@312 11712 }
bgneal@312 11713 },
bgneal@312 11714
bgneal@312 11715 save : function(o) {
bgneal@312 11716 var t = this, e = t.getElement(), h, f;
bgneal@312 11717
bgneal@312 11718 if (!e || !t.initialized)
bgneal@312 11719 return;
bgneal@312 11720
bgneal@312 11721 o = o || {};
bgneal@312 11722 o.save = true;
bgneal@312 11723
bgneal@312 11724 // Add undo level will trigger onchange event
bgneal@312 11725 if (!o.no_events) {
bgneal@442 11726 t.undoManager.typing = false;
bgneal@312 11727 t.undoManager.add();
bgneal@312 11728 }
bgneal@312 11729
bgneal@312 11730 o.element = e;
bgneal@312 11731 h = o.content = t.getContent(o);
bgneal@312 11732
bgneal@312 11733 if (!o.no_events)
bgneal@312 11734 t.onSaveContent.dispatch(t, o);
bgneal@312 11735
bgneal@312 11736 h = o.content;
bgneal@312 11737
bgneal@312 11738 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
bgneal@312 11739 e.innerHTML = h;
bgneal@312 11740
bgneal@312 11741 // Update hidden form element
bgneal@312 11742 if (f = DOM.getParent(t.id, 'form')) {
bgneal@312 11743 each(f.elements, function(e) {
bgneal@312 11744 if (e.name == t.id) {
bgneal@312 11745 e.value = h;
bgneal@312 11746 return false;
bgneal@312 11747 }
bgneal@312 11748 });
bgneal@312 11749 }
bgneal@312 11750 } else
bgneal@312 11751 e.value = h;
bgneal@312 11752
bgneal@312 11753 o.element = e = null;
bgneal@312 11754
bgneal@312 11755 return h;
bgneal@312 11756 },
bgneal@312 11757
bgneal@442 11758 setContent : function(content, args) {
bgneal@442 11759 var self = this, rootNode, body = self.getBody();
bgneal@442 11760
bgneal@442 11761 // Setup args object
bgneal@442 11762 args = args || {};
bgneal@442 11763 args.format = args.format || 'html';
bgneal@442 11764 args.set = true;
bgneal@442 11765 args.content = content;
bgneal@442 11766
bgneal@442 11767 // Do preprocessing
bgneal@442 11768 if (!args.no_events)
bgneal@442 11769 self.onBeforeSetContent.dispatch(self, args);
bgneal@442 11770
bgneal@442 11771 content = args.content;
bgneal@312 11772
bgneal@312 11773 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
bgneal@312 11774 // It will also be impossible to place the caret in the editor unless there is a BR element present
bgneal@442 11775 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
bgneal@442 11776 body.innerHTML = '<br data-mce-bogus="1" />';
bgneal@442 11777 return;
bgneal@442 11778 }
bgneal@442 11779
bgneal@442 11780 // Parse and serialize the html
bgneal@442 11781 if (args.format !== 'raw') {
bgneal@442 11782 content = new tinymce.html.Serializer({}, self.schema).serialize(
bgneal@442 11783 self.parser.parse(content)
bgneal@442 11784 );
bgneal@442 11785 }
bgneal@442 11786
bgneal@442 11787 // Set the new cleaned contents to the editor
bgneal@442 11788 args.content = tinymce.trim(content);
bgneal@442 11789 self.dom.setHTML(body, args.content);
bgneal@442 11790
bgneal@442 11791 // Do post processing
bgneal@442 11792 if (!args.no_events)
bgneal@442 11793 self.onSetContent.dispatch(self, args);
bgneal@442 11794
bgneal@442 11795 return args.content;
bgneal@442 11796 },
bgneal@442 11797
bgneal@442 11798 getContent : function(args) {
bgneal@442 11799 var self = this, content;
bgneal@442 11800
bgneal@442 11801 // Setup args object
bgneal@442 11802 args = args || {};
bgneal@442 11803 args.format = args.format || 'html';
bgneal@442 11804 args.get = true;
bgneal@442 11805
bgneal@442 11806 // Do preprocessing
bgneal@442 11807 if (!args.no_events)
bgneal@442 11808 self.onBeforeGetContent.dispatch(self, args);
bgneal@442 11809
bgneal@442 11810 // Get raw contents or by default the cleaned contents
bgneal@442 11811 if (args.format == 'raw')
bgneal@442 11812 content = self.getBody().innerHTML;
bgneal@442 11813 else
bgneal@442 11814 content = self.serializer.serialize(self.getBody(), args);
bgneal@442 11815
bgneal@442 11816 args.content = tinymce.trim(content);
bgneal@442 11817
bgneal@442 11818 // Do post processing
bgneal@442 11819 if (!args.no_events)
bgneal@442 11820 self.onGetContent.dispatch(self, args);
bgneal@442 11821
bgneal@442 11822 return args.content;
bgneal@312 11823 },
bgneal@312 11824
bgneal@312 11825 isDirty : function() {
bgneal@442 11826 var self = this;
bgneal@442 11827
bgneal@442 11828 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
bgneal@312 11829 },
bgneal@312 11830
bgneal@312 11831 getContainer : function() {
bgneal@312 11832 var t = this;
bgneal@312 11833
bgneal@312 11834 if (!t.container)
bgneal@312 11835 t.container = DOM.get(t.editorContainer || t.id + '_parent');
bgneal@312 11836
bgneal@312 11837 return t.container;
bgneal@312 11838 },
bgneal@312 11839
bgneal@312 11840 getContentAreaContainer : function() {
bgneal@312 11841 return this.contentAreaContainer;
bgneal@312 11842 },
bgneal@312 11843
bgneal@312 11844 getElement : function() {
bgneal@312 11845 return DOM.get(this.settings.content_element || this.id);
bgneal@312 11846 },
bgneal@312 11847
bgneal@312 11848 getWin : function() {
bgneal@312 11849 var t = this, e;
bgneal@312 11850
bgneal@312 11851 if (!t.contentWindow) {
bgneal@312 11852 e = DOM.get(t.id + "_ifr");
bgneal@312 11853
bgneal@312 11854 if (e)
bgneal@312 11855 t.contentWindow = e.contentWindow;
bgneal@312 11856 }
bgneal@312 11857
bgneal@312 11858 return t.contentWindow;
bgneal@312 11859 },
bgneal@312 11860
bgneal@312 11861 getDoc : function() {
bgneal@312 11862 var t = this, w;
bgneal@312 11863
bgneal@312 11864 if (!t.contentDocument) {
bgneal@312 11865 w = t.getWin();
bgneal@312 11866
bgneal@312 11867 if (w)
bgneal@312 11868 t.contentDocument = w.document;
bgneal@312 11869 }
bgneal@312 11870
bgneal@312 11871 return t.contentDocument;
bgneal@312 11872 },
bgneal@312 11873
bgneal@312 11874 getBody : function() {
bgneal@312 11875 return this.bodyElement || this.getDoc().body;
bgneal@312 11876 },
bgneal@312 11877
bgneal@312 11878 convertURL : function(u, n, e) {
bgneal@312 11879 var t = this, s = t.settings;
bgneal@312 11880
bgneal@312 11881 // Use callback instead
bgneal@312 11882 if (s.urlconverter_callback)
bgneal@312 11883 return t.execCallback('urlconverter_callback', u, e, true, n);
bgneal@312 11884
bgneal@312 11885 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
bgneal@312 11886 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
bgneal@312 11887 return u;
bgneal@312 11888
bgneal@312 11889 // Convert to relative
bgneal@312 11890 if (s.relative_urls)
bgneal@312 11891 return t.documentBaseURI.toRelative(u);
bgneal@312 11892
bgneal@312 11893 // Convert to absolute
bgneal@312 11894 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
bgneal@312 11895
bgneal@312 11896 return u;
bgneal@312 11897 },
bgneal@312 11898
bgneal@312 11899 addVisual : function(e) {
bgneal@312 11900 var t = this, s = t.settings;
bgneal@312 11901
bgneal@312 11902 e = e || t.getBody();
bgneal@312 11903
bgneal@312 11904 if (!is(t.hasVisual))
bgneal@312 11905 t.hasVisual = s.visual;
bgneal@312 11906
bgneal@312 11907 each(t.dom.select('table,a', e), function(e) {
bgneal@312 11908 var v;
bgneal@312 11909
bgneal@312 11910 switch (e.nodeName) {
bgneal@312 11911 case 'TABLE':
bgneal@312 11912 v = t.dom.getAttrib(e, 'border');
bgneal@312 11913
bgneal@312 11914 if (!v || v == '0') {
bgneal@312 11915 if (t.hasVisual)
bgneal@312 11916 t.dom.addClass(e, s.visual_table_class);
bgneal@312 11917 else
bgneal@312 11918 t.dom.removeClass(e, s.visual_table_class);
bgneal@312 11919 }
bgneal@312 11920
bgneal@312 11921 return;
bgneal@312 11922
bgneal@312 11923 case 'A':
bgneal@312 11924 v = t.dom.getAttrib(e, 'name');
bgneal@312 11925
bgneal@312 11926 if (v) {
bgneal@312 11927 if (t.hasVisual)
bgneal@312 11928 t.dom.addClass(e, 'mceItemAnchor');
bgneal@312 11929 else
bgneal@312 11930 t.dom.removeClass(e, 'mceItemAnchor');
bgneal@312 11931 }
bgneal@312 11932
bgneal@312 11933 return;
bgneal@312 11934 }
bgneal@312 11935 });
bgneal@312 11936
bgneal@312 11937 t.onVisualAid.dispatch(t, e, t.hasVisual);
bgneal@312 11938 },
bgneal@312 11939
bgneal@312 11940 remove : function() {
bgneal@312 11941 var t = this, e = t.getContainer();
bgneal@312 11942
bgneal@312 11943 t.removed = 1; // Cancels post remove event execution
bgneal@312 11944 t.hide();
bgneal@312 11945
bgneal@312 11946 t.execCallback('remove_instance_callback', t);
bgneal@312 11947 t.onRemove.dispatch(t);
bgneal@312 11948
bgneal@312 11949 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
bgneal@312 11950 t.onExecCommand.listeners = [];
bgneal@312 11951
bgneal@312 11952 tinymce.remove(t);
bgneal@312 11953 DOM.remove(e);
bgneal@312 11954 },
bgneal@312 11955
bgneal@312 11956 destroy : function(s) {
bgneal@312 11957 var t = this;
bgneal@312 11958
bgneal@312 11959 // One time is enough
bgneal@312 11960 if (t.destroyed)
bgneal@312 11961 return;
bgneal@312 11962
bgneal@312 11963 if (!s) {
bgneal@312 11964 tinymce.removeUnload(t.destroy);
bgneal@312 11965 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
bgneal@312 11966
bgneal@312 11967 // Manual destroy
bgneal@312 11968 if (t.theme && t.theme.destroy)
bgneal@312 11969 t.theme.destroy();
bgneal@312 11970
bgneal@312 11971 // Destroy controls, selection and dom
bgneal@312 11972 t.controlManager.destroy();
bgneal@312 11973 t.selection.destroy();
bgneal@312 11974 t.dom.destroy();
bgneal@312 11975
bgneal@312 11976 // Remove all events
bgneal@312 11977
bgneal@312 11978 // Don't clear the window or document if content editable
bgneal@312 11979 // is enabled since other instances might still be present
bgneal@312 11980 if (!t.settings.content_editable) {
bgneal@312 11981 Event.clear(t.getWin());
bgneal@312 11982 Event.clear(t.getDoc());
bgneal@312 11983 }
bgneal@312 11984
bgneal@312 11985 Event.clear(t.getBody());
bgneal@312 11986 Event.clear(t.formElement);
bgneal@312 11987 }
bgneal@312 11988
bgneal@312 11989 if (t.formElement) {
bgneal@312 11990 t.formElement.submit = t.formElement._mceOldSubmit;
bgneal@312 11991 t.formElement._mceOldSubmit = null;
bgneal@312 11992 }
bgneal@312 11993
bgneal@312 11994 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
bgneal@312 11995
bgneal@312 11996 if (t.selection)
bgneal@312 11997 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
bgneal@312 11998
bgneal@312 11999 t.destroyed = 1;
bgneal@312 12000 },
bgneal@312 12001
bgneal@312 12002 // Internal functions
bgneal@312 12003
bgneal@312 12004 _addEvents : function() {
bgneal@312 12005 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
bgneal@442 12006 var t = this, i, s = t.settings, dom = t.dom, lo = {
bgneal@312 12007 mouseup : 'onMouseUp',
bgneal@312 12008 mousedown : 'onMouseDown',
bgneal@312 12009 click : 'onClick',
bgneal@312 12010 keyup : 'onKeyUp',
bgneal@312 12011 keydown : 'onKeyDown',
bgneal@312 12012 keypress : 'onKeyPress',
bgneal@312 12013 submit : 'onSubmit',
bgneal@312 12014 reset : 'onReset',
bgneal@312 12015 contextmenu : 'onContextMenu',
bgneal@312 12016 dblclick : 'onDblClick',
bgneal@312 12017 paste : 'onPaste' // Doesn't work in all browsers yet
bgneal@312 12018 };
bgneal@312 12019
bgneal@312 12020 function eventHandler(e, o) {
bgneal@312 12021 var ty = e.type;
bgneal@312 12022
bgneal@312 12023 // Don't fire events when it's removed
bgneal@312 12024 if (t.removed)
bgneal@312 12025 return;
bgneal@312 12026
bgneal@312 12027 // Generic event handler
bgneal@312 12028 if (t.onEvent.dispatch(t, e, o) !== false) {
bgneal@312 12029 // Specific event handler
bgneal@312 12030 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
bgneal@312 12031 }
bgneal@312 12032 };
bgneal@312 12033
bgneal@312 12034 // Add DOM events
bgneal@312 12035 each(lo, function(v, k) {
bgneal@312 12036 switch (k) {
bgneal@312 12037 case 'contextmenu':
bgneal@442 12038 dom.bind(t.getDoc(), k, eventHandler);
bgneal@312 12039 break;
bgneal@312 12040
bgneal@312 12041 case 'paste':
bgneal@442 12042 dom.bind(t.getBody(), k, function(e) {
bgneal@312 12043 eventHandler(e);
bgneal@312 12044 });
bgneal@312 12045 break;
bgneal@312 12046
bgneal@312 12047 case 'submit':
bgneal@312 12048 case 'reset':
bgneal@442 12049 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
bgneal@312 12050 break;
bgneal@312 12051
bgneal@312 12052 default:
bgneal@442 12053 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
bgneal@442 12054 }
bgneal@442 12055 });
bgneal@442 12056
bgneal@442 12057 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
bgneal@312 12058 t.focus(true);
bgneal@312 12059 });
bgneal@312 12060
bgneal@312 12061
bgneal@312 12062 // Fixes bug where a specified document_base_uri could result in broken images
bgneal@312 12063 // This will also fix drag drop of images in Gecko
bgneal@312 12064 if (tinymce.isGecko) {
bgneal@442 12065 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
bgneal@312 12066 var v;
bgneal@312 12067
bgneal@312 12068 e = e.target;
bgneal@312 12069
bgneal@442 12070 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
bgneal@312 12071 e.src = t.documentBaseURI.toAbsolute(v);
bgneal@312 12072 });
bgneal@312 12073 }
bgneal@312 12074
bgneal@312 12075 // Set various midas options in Gecko
bgneal@312 12076 if (isGecko) {
bgneal@312 12077 function setOpts() {
bgneal@312 12078 var t = this, d = t.getDoc(), s = t.settings;
bgneal@312 12079
bgneal@312 12080 if (isGecko && !s.readonly) {
bgneal@312 12081 if (t._isHidden()) {
bgneal@312 12082 try {
bgneal@312 12083 if (!s.content_editable)
bgneal@312 12084 d.designMode = 'On';
bgneal@312 12085 } catch (ex) {
bgneal@312 12086 // Fails if it's hidden
bgneal@312 12087 }
bgneal@312 12088 }
bgneal@312 12089
bgneal@312 12090 try {
bgneal@312 12091 // Try new Gecko method
bgneal@312 12092 d.execCommand("styleWithCSS", 0, false);
bgneal@312 12093 } catch (ex) {
bgneal@312 12094 // Use old method
bgneal@312 12095 if (!t._isHidden())
bgneal@312 12096 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
bgneal@312 12097 }
bgneal@312 12098
bgneal@312 12099 if (!s.table_inline_editing)
bgneal@312 12100 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
bgneal@312 12101
bgneal@312 12102 if (!s.object_resizing)
bgneal@312 12103 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
bgneal@312 12104 }
bgneal@312 12105 };
bgneal@312 12106
bgneal@312 12107 t.onBeforeExecCommand.add(setOpts);
bgneal@312 12108 t.onMouseDown.add(setOpts);
bgneal@312 12109 }
bgneal@312 12110
bgneal@312 12111 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
bgneal@312 12112 // WebKit can't even do simple things like selecting an image
bgneal@312 12113 // This also fixes so it's possible to select mceItemAnchors
bgneal@312 12114 if (tinymce.isWebKit) {
bgneal@312 12115 t.onClick.add(function(ed, e) {
bgneal@312 12116 e = e.target;
bgneal@312 12117
bgneal@312 12118 // Needs tobe the setBaseAndExtend or it will fail to select floated images
bgneal@442 12119 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
bgneal@312 12120 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
bgneal@442 12121 t.nodeChanged();
bgneal@442 12122 }
bgneal@312 12123 });
bgneal@312 12124 }
bgneal@312 12125
bgneal@312 12126 // Add node change handlers
bgneal@312 12127 t.onMouseUp.add(t.nodeChanged);
bgneal@312 12128 //t.onClick.add(t.nodeChanged);
bgneal@312 12129 t.onKeyUp.add(function(ed, e) {
bgneal@312 12130 var c = e.keyCode;
bgneal@312 12131
bgneal@312 12132 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
bgneal@312 12133 t.nodeChanged();
bgneal@312 12134 });
bgneal@312 12135
bgneal@312 12136 // Add reset handler
bgneal@312 12137 t.onReset.add(function() {
bgneal@312 12138 t.setContent(t.startContent, {format : 'raw'});
bgneal@312 12139 });
bgneal@312 12140
bgneal@312 12141 // Add shortcuts
bgneal@312 12142 if (s.custom_shortcuts) {
bgneal@312 12143 if (s.custom_undo_redo_keyboard_shortcuts) {
bgneal@312 12144 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
bgneal@312 12145 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
bgneal@312 12146 }
bgneal@312 12147
bgneal@312 12148 // Add default shortcuts for gecko
bgneal@312 12149 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
bgneal@312 12150 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
bgneal@312 12151 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
bgneal@312 12152
bgneal@312 12153 // BlockFormat shortcuts keys
bgneal@312 12154 for (i=1; i<=6; i++)
bgneal@312 12155 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
bgneal@312 12156
bgneal@312 12157 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
bgneal@312 12158 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
bgneal@312 12159 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
bgneal@312 12160
bgneal@312 12161 function find(e) {
bgneal@312 12162 var v = null;
bgneal@312 12163
bgneal@312 12164 if (!e.altKey && !e.ctrlKey && !e.metaKey)
bgneal@312 12165 return v;
bgneal@312 12166
bgneal@312 12167 each(t.shortcuts, function(o) {
bgneal@312 12168 if (tinymce.isMac && o.ctrl != e.metaKey)
bgneal@312 12169 return;
bgneal@312 12170 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
bgneal@312 12171 return;
bgneal@312 12172
bgneal@312 12173 if (o.alt != e.altKey)
bgneal@312 12174 return;
bgneal@312 12175
bgneal@312 12176 if (o.shift != e.shiftKey)
bgneal@312 12177 return;
bgneal@312 12178
bgneal@312 12179 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
bgneal@312 12180 v = o;
bgneal@312 12181 return false;
bgneal@312 12182 }
bgneal@312 12183 });
bgneal@312 12184
bgneal@312 12185 return v;
bgneal@312 12186 };
bgneal@312 12187
bgneal@312 12188 t.onKeyUp.add(function(ed, e) {
bgneal@312 12189 var o = find(e);
bgneal@312 12190
bgneal@312 12191 if (o)
bgneal@312 12192 return Event.cancel(e);
bgneal@312 12193 });
bgneal@312 12194
bgneal@312 12195 t.onKeyPress.add(function(ed, e) {
bgneal@312 12196 var o = find(e);
bgneal@312 12197
bgneal@312 12198 if (o)
bgneal@312 12199 return Event.cancel(e);
bgneal@312 12200 });
bgneal@312 12201
bgneal@312 12202 t.onKeyDown.add(function(ed, e) {
bgneal@312 12203 var o = find(e);
bgneal@312 12204
bgneal@312 12205 if (o) {
bgneal@312 12206 o.func.call(o.scope);
bgneal@312 12207 return Event.cancel(e);
bgneal@312 12208 }
bgneal@312 12209 });
bgneal@312 12210 }
bgneal@312 12211
bgneal@312 12212 if (tinymce.isIE) {
bgneal@312 12213 // Fix so resize will only update the width and height attributes not the styles of an image
bgneal@312 12214 // It will also block mceItemNoResize items
bgneal@442 12215 dom.bind(t.getDoc(), 'controlselect', function(e) {
bgneal@312 12216 var re = t.resizeInfo, cb;
bgneal@312 12217
bgneal@312 12218 e = e.target;
bgneal@312 12219
bgneal@312 12220 // Don't do this action for non image elements
bgneal@312 12221 if (e.nodeName !== 'IMG')
bgneal@312 12222 return;
bgneal@312 12223
bgneal@312 12224 if (re)
bgneal@442 12225 dom.unbind(re.node, re.ev, re.cb);
bgneal@442 12226
bgneal@442 12227 if (!dom.hasClass(e, 'mceItemNoResize')) {
bgneal@312 12228 ev = 'resizeend';
bgneal@442 12229 cb = dom.bind(e, ev, function(e) {
bgneal@312 12230 var v;
bgneal@312 12231
bgneal@312 12232 e = e.target;
bgneal@312 12233
bgneal@442 12234 if (v = dom.getStyle(e, 'width')) {
bgneal@442 12235 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
bgneal@442 12236 dom.setStyle(e, 'width', '');
bgneal@312 12237 }
bgneal@312 12238
bgneal@442 12239 if (v = dom.getStyle(e, 'height')) {
bgneal@442 12240 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
bgneal@442 12241 dom.setStyle(e, 'height', '');
bgneal@312 12242 }
bgneal@312 12243 });
bgneal@312 12244 } else {
bgneal@312 12245 ev = 'resizestart';
bgneal@442 12246 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
bgneal@312 12247 }
bgneal@312 12248
bgneal@312 12249 re = t.resizeInfo = {
bgneal@312 12250 node : e,
bgneal@312 12251 ev : ev,
bgneal@312 12252 cb : cb
bgneal@312 12253 };
bgneal@312 12254 });
bgneal@312 12255
bgneal@312 12256 t.onKeyDown.add(function(ed, e) {
bgneal@442 12257 var sel;
bgneal@442 12258
bgneal@312 12259 switch (e.keyCode) {
bgneal@312 12260 case 8:
bgneal@442 12261 sel = t.getDoc().selection;
bgneal@442 12262
bgneal@312 12263 // Fix IE control + backspace browser bug
bgneal@442 12264 if (sel.createRange && sel.createRange().item) {
bgneal@442 12265 ed.dom.remove(sel.createRange().item(0));
bgneal@312 12266 return Event.cancel(e);
bgneal@312 12267 }
bgneal@312 12268 }
bgneal@312 12269 });
bgneal@312 12270 }
bgneal@312 12271
bgneal@312 12272 if (tinymce.isOpera) {
bgneal@312 12273 t.onClick.add(function(ed, e) {
bgneal@312 12274 Event.prevent(e);
bgneal@312 12275 });
bgneal@312 12276 }
bgneal@312 12277
bgneal@312 12278 // Add custom undo/redo handlers
bgneal@312 12279 if (s.custom_undo_redo) {
bgneal@312 12280 function addUndo() {
bgneal@442 12281 t.undoManager.typing = false;
bgneal@312 12282 t.undoManager.add();
bgneal@312 12283 };
bgneal@312 12284
bgneal@442 12285 dom.bind(t.getDoc(), 'focusout', function(e) {
bgneal@312 12286 if (!t.removed && t.undoManager.typing)
bgneal@312 12287 addUndo();
bgneal@312 12288 });
bgneal@312 12289
bgneal@442 12290 // Add undo level when contents is drag/dropped within the editor
bgneal@442 12291 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
bgneal@442 12292 addUndo();
bgneal@442 12293 });
bgneal@442 12294
bgneal@312 12295 t.onKeyUp.add(function(ed, e) {
bgneal@442 12296 var rng, parent, bookmark;
bgneal@442 12297
bgneal@442 12298 // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
bgneal@442 12299 if (isIE && e.keyCode == 8) {
bgneal@442 12300 rng = t.selection.getRng();
bgneal@442 12301 if (rng.parentElement) {
bgneal@442 12302 parent = rng.parentElement();
bgneal@442 12303 bookmark = t.selection.getBookmark();
bgneal@442 12304 parent.innerHTML = parent.innerHTML;
bgneal@442 12305 t.selection.moveToBookmark(bookmark);
bgneal@442 12306 }
bgneal@442 12307 }
bgneal@442 12308
bgneal@312 12309 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
bgneal@312 12310 addUndo();
bgneal@312 12311 });
bgneal@312 12312
bgneal@312 12313 t.onKeyDown.add(function(ed, e) {
bgneal@442 12314 var rng, parent, bookmark, keyCode = e.keyCode;
bgneal@312 12315
bgneal@312 12316 // IE has a really odd bug where the DOM might include an node that doesn't have
bgneal@312 12317 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
bgneal@312 12318 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
bgneal@312 12319 // after you delete contents from it. See: #3008923
bgneal@442 12320 if (isIE && keyCode == 46) {
bgneal@312 12321 rng = t.selection.getRng();
bgneal@312 12322
bgneal@312 12323 if (rng.parentElement) {
bgneal@312 12324 parent = rng.parentElement();
bgneal@312 12325
bgneal@442 12326 if (!t.undoManager.typing) {
bgneal@442 12327 t.undoManager.beforeChange();
bgneal@442 12328 t.undoManager.typing = true;
bgneal@442 12329 t.undoManager.add();
bgneal@442 12330 }
bgneal@442 12331
bgneal@312 12332 // Select next word when ctrl key is used in combo with delete
bgneal@312 12333 if (e.ctrlKey) {
bgneal@312 12334 rng.moveEnd('word', 1);
bgneal@312 12335 rng.select();
bgneal@312 12336 }
bgneal@312 12337
bgneal@312 12338 // Delete contents
bgneal@312 12339 t.selection.getSel().clear();
bgneal@312 12340
bgneal@312 12341 // Check if we are within the same parent
bgneal@312 12342 if (rng.parentElement() == parent) {
bgneal@312 12343 bookmark = t.selection.getBookmark();
bgneal@312 12344
bgneal@312 12345 try {
bgneal@312 12346 // Update the HTML and hopefully it will remove the artifacts
bgneal@312 12347 parent.innerHTML = parent.innerHTML;
bgneal@312 12348 } catch (ex) {
bgneal@312 12349 // And since it's IE it can sometimes produce an unknown runtime error
bgneal@312 12350 }
bgneal@312 12351
bgneal@312 12352 // Restore the caret position
bgneal@312 12353 t.selection.moveToBookmark(bookmark);
bgneal@312 12354 }
bgneal@312 12355
bgneal@312 12356 // Block the default delete behavior since it might be broken
bgneal@312 12357 e.preventDefault();
bgneal@312 12358 return;
bgneal@312 12359 }
bgneal@312 12360 }
bgneal@312 12361
bgneal@442 12362 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
bgneal@442 12363 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
bgneal@442 12364 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
bgneal@442 12365 // Todo: Remove this once we normalize enter behavior on IE
bgneal@442 12366 if (tinymce.isIE && keyCode == 13)
bgneal@442 12367 t.undoManager.beforeChange();
bgneal@442 12368
bgneal@312 12369 if (t.undoManager.typing)
bgneal@312 12370 addUndo();
bgneal@312 12371
bgneal@312 12372 return;
bgneal@312 12373 }
bgneal@312 12374
bgneal@442 12375 // If key isn't shift,ctrl,alt,capslock,metakey
bgneal@442 12376 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
bgneal@442 12377 t.undoManager.beforeChange();
bgneal@312 12378 t.undoManager.add();
bgneal@442 12379 t.undoManager.typing = true;
bgneal@312 12380 }
bgneal@312 12381 });
bgneal@312 12382
bgneal@312 12383 t.onMouseDown.add(function() {
bgneal@312 12384 if (t.undoManager.typing)
bgneal@312 12385 addUndo();
bgneal@312 12386 });
bgneal@312 12387 }
bgneal@442 12388
bgneal@442 12389 // Bug fix for FireFox keeping styles from end of selection instead of start.
bgneal@442 12390 if (tinymce.isGecko) {
bgneal@442 12391 function getAttributeApplyFunction() {
bgneal@442 12392 var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
bgneal@442 12393
bgneal@442 12394 return function() {
bgneal@442 12395 var target = t.selection.getStart();
bgneal@442 12396 t.dom.removeAllAttribs(target);
bgneal@442 12397 each(template, function(attr) {
bgneal@442 12398 target.setAttributeNode(attr.cloneNode(true));
bgneal@442 12399 });
bgneal@442 12400 };
bgneal@442 12401 }
bgneal@442 12402
bgneal@442 12403 function isSelectionAcrossElements() {
bgneal@442 12404 var s = t.selection;
bgneal@442 12405
bgneal@442 12406 return !s.isCollapsed() && s.getStart() != s.getEnd();
bgneal@442 12407 }
bgneal@442 12408
bgneal@442 12409 t.onKeyPress.add(function(ed, e) {
bgneal@442 12410 var applyAttributes;
bgneal@442 12411
bgneal@442 12412 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
bgneal@442 12413 applyAttributes = getAttributeApplyFunction();
bgneal@442 12414 t.getDoc().execCommand('delete', false, null);
bgneal@442 12415 applyAttributes();
bgneal@442 12416
bgneal@442 12417 return Event.cancel(e);
bgneal@442 12418 }
bgneal@442 12419 });
bgneal@442 12420
bgneal@442 12421 t.dom.bind(t.getDoc(), 'cut', function(e) {
bgneal@442 12422 var applyAttributes;
bgneal@442 12423
bgneal@442 12424 if (isSelectionAcrossElements()) {
bgneal@442 12425 applyAttributes = getAttributeApplyFunction();
bgneal@442 12426 t.onKeyUp.addToTop(Event.cancel, Event);
bgneal@442 12427
bgneal@442 12428 setTimeout(function() {
bgneal@442 12429 applyAttributes();
bgneal@442 12430 t.onKeyUp.remove(Event.cancel, Event);
bgneal@442 12431 }, 0);
bgneal@442 12432 }
bgneal@442 12433 });
bgneal@442 12434 }
bgneal@312 12435 },
bgneal@312 12436
bgneal@312 12437 _isHidden : function() {
bgneal@312 12438 var s;
bgneal@312 12439
bgneal@312 12440 if (!isGecko)
bgneal@312 12441 return 0;
bgneal@312 12442
bgneal@312 12443 // Weird, wheres that cursor selection?
bgneal@312 12444 s = this.selection.getSel();
bgneal@312 12445 return (!s || !s.rangeCount || s.rangeCount == 0);
bgneal@312 12446 }
bgneal@312 12447 });
bgneal@312 12448 })(tinymce);
bgneal@312 12449
bgneal@312 12450 (function(tinymce) {
bgneal@312 12451 // Added for compression purposes
bgneal@312 12452 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
bgneal@312 12453
bgneal@312 12454 tinymce.EditorCommands = function(editor) {
bgneal@312 12455 var dom = editor.dom,
bgneal@312 12456 selection = editor.selection,
bgneal@312 12457 commands = {state: {}, exec : {}, value : {}},
bgneal@312 12458 settings = editor.settings,
bgneal@312 12459 bookmark;
bgneal@312 12460
bgneal@312 12461 function execCommand(command, ui, value) {
bgneal@312 12462 var func;
bgneal@312 12463
bgneal@312 12464 command = command.toLowerCase();
bgneal@312 12465 if (func = commands.exec[command]) {
bgneal@312 12466 func(command, ui, value);
bgneal@312 12467 return TRUE;
bgneal@312 12468 }
bgneal@312 12469
bgneal@312 12470 return FALSE;
bgneal@312 12471 };
bgneal@312 12472
bgneal@312 12473 function queryCommandState(command) {
bgneal@312 12474 var func;
bgneal@312 12475
bgneal@312 12476 command = command.toLowerCase();
bgneal@312 12477 if (func = commands.state[command])
bgneal@312 12478 return func(command);
bgneal@312 12479
bgneal@312 12480 return -1;
bgneal@312 12481 };
bgneal@312 12482
bgneal@312 12483 function queryCommandValue(command) {
bgneal@312 12484 var func;
bgneal@312 12485
bgneal@312 12486 command = command.toLowerCase();
bgneal@312 12487 if (func = commands.value[command])
bgneal@312 12488 return func(command);
bgneal@312 12489
bgneal@312 12490 return FALSE;
bgneal@312 12491 };
bgneal@312 12492
bgneal@312 12493 function addCommands(command_list, type) {
bgneal@312 12494 type = type || 'exec';
bgneal@312 12495
bgneal@312 12496 each(command_list, function(callback, command) {
bgneal@312 12497 each(command.toLowerCase().split(','), function(command) {
bgneal@312 12498 commands[type][command] = callback;
bgneal@312 12499 });
bgneal@312 12500 });
bgneal@312 12501 };
bgneal@312 12502
bgneal@312 12503 // Expose public methods
bgneal@312 12504 tinymce.extend(this, {
bgneal@312 12505 execCommand : execCommand,
bgneal@312 12506 queryCommandState : queryCommandState,
bgneal@312 12507 queryCommandValue : queryCommandValue,
bgneal@312 12508 addCommands : addCommands
bgneal@312 12509 });
bgneal@312 12510
bgneal@312 12511 // Private methods
bgneal@312 12512
bgneal@312 12513 function execNativeCommand(command, ui, value) {
bgneal@312 12514 if (ui === undefined)
bgneal@312 12515 ui = FALSE;
bgneal@312 12516
bgneal@312 12517 if (value === undefined)
bgneal@312 12518 value = null;
bgneal@312 12519
bgneal@312 12520 return editor.getDoc().execCommand(command, ui, value);
bgneal@312 12521 };
bgneal@312 12522
bgneal@312 12523 function isFormatMatch(name) {
bgneal@312 12524 return editor.formatter.match(name);
bgneal@312 12525 };
bgneal@312 12526
bgneal@312 12527 function toggleFormat(name, value) {
bgneal@312 12528 editor.formatter.toggle(name, value ? {value : value} : undefined);
bgneal@312 12529 };
bgneal@312 12530
bgneal@312 12531 function storeSelection(type) {
bgneal@312 12532 bookmark = selection.getBookmark(type);
bgneal@312 12533 };
bgneal@312 12534
bgneal@312 12535 function restoreSelection() {
bgneal@312 12536 selection.moveToBookmark(bookmark);
bgneal@312 12537 };
bgneal@312 12538
bgneal@312 12539 // Add execCommand overrides
bgneal@312 12540 addCommands({
bgneal@312 12541 // Ignore these, added for compatibility
bgneal@312 12542 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
bgneal@312 12543
bgneal@312 12544 // Add undo manager logic
bgneal@312 12545 'mceEndUndoLevel,mceAddUndoLevel' : function() {
bgneal@312 12546 editor.undoManager.add();
bgneal@312 12547 },
bgneal@312 12548
bgneal@312 12549 'Cut,Copy,Paste' : function(command) {
bgneal@312 12550 var doc = editor.getDoc(), failed;
bgneal@312 12551
bgneal@312 12552 // Try executing the native command
bgneal@312 12553 try {
bgneal@312 12554 execNativeCommand(command);
bgneal@312 12555 } catch (ex) {
bgneal@312 12556 // Command failed
bgneal@312 12557 failed = TRUE;
bgneal@312 12558 }
bgneal@312 12559
bgneal@312 12560 // Present alert message about clipboard access not being available
bgneal@312 12561 if (failed || !doc.queryCommandSupported(command)) {
bgneal@312 12562 if (tinymce.isGecko) {
bgneal@312 12563 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
bgneal@312 12564 if (state)
bgneal@312 12565 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
bgneal@312 12566 });
bgneal@312 12567 } else
bgneal@312 12568 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
bgneal@312 12569 }
bgneal@312 12570 },
bgneal@312 12571
bgneal@312 12572 // Override unlink command
bgneal@312 12573 unlink : function(command) {
bgneal@312 12574 if (selection.isCollapsed())
bgneal@312 12575 selection.select(selection.getNode());
bgneal@312 12576
bgneal@312 12577 execNativeCommand(command);
bgneal@312 12578 selection.collapse(FALSE);
bgneal@312 12579 },
bgneal@312 12580
bgneal@312 12581 // Override justify commands to use the text formatter engine
bgneal@312 12582 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@312 12583 var align = command.substring(7);
bgneal@312 12584
bgneal@312 12585 // Remove all other alignments first
bgneal@312 12586 each('left,center,right,full'.split(','), function(name) {
bgneal@312 12587 if (align != name)
bgneal@312 12588 editor.formatter.remove('align' + name);
bgneal@312 12589 });
bgneal@312 12590
bgneal@312 12591 toggleFormat('align' + align);
bgneal@442 12592 execCommand('mceRepaint');
bgneal@312 12593 },
bgneal@312 12594
bgneal@312 12595 // Override list commands to fix WebKit bug
bgneal@312 12596 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@312 12597 var listElm, listParent;
bgneal@312 12598
bgneal@312 12599 execNativeCommand(command);
bgneal@312 12600
bgneal@312 12601 // WebKit produces lists within block elements so we need to split them
bgneal@312 12602 // we will replace the native list creation logic to custom logic later on
bgneal@312 12603 // TODO: Remove this when the list creation logic is removed
bgneal@312 12604 listElm = dom.getParent(selection.getNode(), 'ol,ul');
bgneal@312 12605 if (listElm) {
bgneal@312 12606 listParent = listElm.parentNode;
bgneal@312 12607
bgneal@312 12608 // If list is within a text block then split that block
bgneal@312 12609 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
bgneal@312 12610 storeSelection();
bgneal@312 12611 dom.split(listParent, listElm);
bgneal@312 12612 restoreSelection();
bgneal@312 12613 }
bgneal@312 12614 }
bgneal@312 12615 },
bgneal@312 12616
bgneal@312 12617 // Override commands to use the text formatter engine
bgneal@442 12618 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
bgneal@312 12619 toggleFormat(command);
bgneal@312 12620 },
bgneal@312 12621
bgneal@312 12622 // Override commands to use the text formatter engine
bgneal@312 12623 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
bgneal@312 12624 toggleFormat(command, value);
bgneal@312 12625 },
bgneal@312 12626
bgneal@312 12627 FontSize : function(command, ui, value) {
bgneal@312 12628 var fontClasses, fontSizes;
bgneal@312 12629
bgneal@312 12630 // Convert font size 1-7 to styles
bgneal@312 12631 if (value >= 1 && value <= 7) {
bgneal@312 12632 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@312 12633 fontClasses = tinymce.explode(settings.font_size_classes);
bgneal@312 12634
bgneal@312 12635 if (fontClasses)
bgneal@312 12636 value = fontClasses[value - 1] || value;
bgneal@312 12637 else
bgneal@312 12638 value = fontSizes[value - 1] || value;
bgneal@312 12639 }
bgneal@312 12640
bgneal@312 12641 toggleFormat(command, value);
bgneal@312 12642 },
bgneal@312 12643
bgneal@312 12644 RemoveFormat : function(command) {
bgneal@312 12645 editor.formatter.remove(command);
bgneal@312 12646 },
bgneal@312 12647
bgneal@312 12648 mceBlockQuote : function(command) {
bgneal@312 12649 toggleFormat('blockquote');
bgneal@312 12650 },
bgneal@312 12651
bgneal@312 12652 FormatBlock : function(command, ui, value) {
bgneal@312 12653 return toggleFormat(value || 'p');
bgneal@312 12654 },
bgneal@312 12655
bgneal@312 12656 mceCleanup : function() {
bgneal@312 12657 var bookmark = selection.getBookmark();
bgneal@312 12658
bgneal@312 12659 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
bgneal@312 12660
bgneal@312 12661 selection.moveToBookmark(bookmark);
bgneal@312 12662 },
bgneal@312 12663
bgneal@312 12664 mceRemoveNode : function(command, ui, value) {
bgneal@312 12665 var node = value || selection.getNode();
bgneal@312 12666
bgneal@312 12667 // Make sure that the body node isn't removed
bgneal@312 12668 if (node != editor.getBody()) {
bgneal@312 12669 storeSelection();
bgneal@312 12670 editor.dom.remove(node, TRUE);
bgneal@312 12671 restoreSelection();
bgneal@312 12672 }
bgneal@312 12673 },
bgneal@312 12674
bgneal@312 12675 mceSelectNodeDepth : function(command, ui, value) {
bgneal@312 12676 var counter = 0;
bgneal@312 12677
bgneal@312 12678 dom.getParent(selection.getNode(), function(node) {
bgneal@312 12679 if (node.nodeType == 1 && counter++ == value) {
bgneal@312 12680 selection.select(node);
bgneal@312 12681 return FALSE;
bgneal@312 12682 }
bgneal@312 12683 }, editor.getBody());
bgneal@312 12684 },
bgneal@312 12685
bgneal@312 12686 mceSelectNode : function(command, ui, value) {
bgneal@312 12687 selection.select(value);
bgneal@312 12688 },
bgneal@312 12689
bgneal@312 12690 mceInsertContent : function(command, ui, value) {
bgneal@442 12691 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
bgneal@442 12692
bgneal@442 12693 function findSuitableCaretNode(node, root_node, next) {
bgneal@442 12694 var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
bgneal@442 12695
bgneal@442 12696 while ((node = walker.current())) {
bgneal@442 12697 if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
bgneal@442 12698 return node;
bgneal@442 12699
bgneal@442 12700 if (next)
bgneal@442 12701 walker.next();
bgneal@442 12702 else
bgneal@442 12703 walker.prev();
bgneal@442 12704 }
bgneal@442 12705 };
bgneal@442 12706
bgneal@442 12707 args = {content: value, format: 'html'};
bgneal@442 12708 selection.onBeforeSetContent.dispatch(selection, args);
bgneal@442 12709 value = args.content;
bgneal@442 12710
bgneal@442 12711 // Add caret at end of contents if it's missing
bgneal@442 12712 if (value.indexOf('{$caret}') == -1)
bgneal@442 12713 value += '{$caret}';
bgneal@442 12714
bgneal@442 12715 // Set the content at selection to a span and replace it's contents with the value
bgneal@442 12716 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
bgneal@442 12717 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
bgneal@442 12718
bgneal@442 12719 caretNode = dom.select('#__mce')[0];
bgneal@442 12720 rootNode = dom.getRoot();
bgneal@442 12721
bgneal@442 12722 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
bgneal@442 12723 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
bgneal@442 12724 node = findSuitableCaretNode(caretNode, rootNode);
bgneal@442 12725 if (node) {
bgneal@442 12726 if (node.nodeName == 'BR')
bgneal@442 12727 node.parentNode.insertBefore(caretNode, node);
bgneal@442 12728 else
bgneal@442 12729 dom.insertAfter(caretNode, node);
bgneal@442 12730 }
bgneal@442 12731 }
bgneal@442 12732
bgneal@442 12733 // Find caret root parent and clean it up using the serializer to avoid nesting
bgneal@442 12734 while (caretNode) {
bgneal@442 12735 if (caretNode === rootNode) {
bgneal@442 12736 // Clean up the parent element by parsing and serializing it
bgneal@442 12737 // This will remove invalid elements/attributes and fix nesting issues
bgneal@442 12738 dom.setOuterHTML(parent,
bgneal@442 12739 new tinymce.html.Serializer({}, editor.schema).serialize(
bgneal@442 12740 editor.parser.parse(dom.getOuterHTML(parent))
bgneal@442 12741 )
bgneal@442 12742 );
bgneal@442 12743
bgneal@442 12744 break;
bgneal@442 12745 }
bgneal@442 12746
bgneal@442 12747 parent = caretNode;
bgneal@442 12748 caretNode = caretNode.parentNode;
bgneal@442 12749 }
bgneal@442 12750
bgneal@442 12751 // Find caret after cleanup and move selection to that location
bgneal@442 12752 caretNode = dom.select('#__mce')[0];
bgneal@442 12753 if (caretNode) {
bgneal@442 12754 node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
bgneal@442 12755 dom.remove(caretNode);
bgneal@442 12756
bgneal@442 12757 if (node) {
bgneal@442 12758 rng = dom.createRng();
bgneal@442 12759
bgneal@442 12760 if (node.nodeType == 3) {
bgneal@442 12761 rng.setStart(node, node.length);
bgneal@442 12762 rng.setEnd(node, node.length);
bgneal@442 12763 } else {
bgneal@442 12764 if (node.nodeName == 'BR') {
bgneal@442 12765 rng.setStartBefore(node);
bgneal@442 12766 rng.setEndBefore(node);
bgneal@442 12767 } else {
bgneal@442 12768 rng.setStartAfter(node);
bgneal@442 12769 rng.setEndAfter(node);
bgneal@442 12770 }
bgneal@442 12771 }
bgneal@442 12772
bgneal@442 12773 selection.setRng(rng);
bgneal@442 12774
bgneal@442 12775 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
bgneal@442 12776 if (!tinymce.isIE) {
bgneal@442 12777 node = dom.create('span', null, '\u00a0');
bgneal@442 12778 rng.insertNode(node);
bgneal@442 12779 nodeRect = dom.getRect(node);
bgneal@442 12780 viewPortRect = dom.getViewPort(editor.getWin());
bgneal@442 12781
bgneal@442 12782 // Check if node is out side the viewport if it is then scroll to it
bgneal@442 12783 if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
bgneal@442 12784 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
bgneal@442 12785 editor.getBody().scrollLeft = nodeRect.x;
bgneal@442 12786 editor.getBody().scrollTop = nodeRect.y;
bgneal@442 12787 }
bgneal@442 12788
bgneal@442 12789 dom.remove(node);
bgneal@442 12790 }
bgneal@442 12791
bgneal@442 12792 // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
bgneal@442 12793 // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
bgneal@442 12794 selection.collapse(true);
bgneal@442 12795 }
bgneal@442 12796 }
bgneal@442 12797
bgneal@442 12798 selection.onSetContent.dispatch(selection, args);
bgneal@442 12799 editor.addVisual();
bgneal@312 12800 },
bgneal@312 12801
bgneal@312 12802 mceInsertRawHTML : function(command, ui, value) {
bgneal@312 12803 selection.setContent('tiny_mce_marker');
bgneal@312 12804 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
bgneal@312 12805 },
bgneal@312 12806
bgneal@312 12807 mceSetContent : function(command, ui, value) {
bgneal@312 12808 editor.setContent(value);
bgneal@312 12809 },
bgneal@312 12810
bgneal@312 12811 'Indent,Outdent' : function(command) {
bgneal@312 12812 var intentValue, indentUnit, value;
bgneal@312 12813
bgneal@312 12814 // Setup indent level
bgneal@312 12815 intentValue = settings.indentation;
bgneal@312 12816 indentUnit = /[a-z%]+$/i.exec(intentValue);
bgneal@312 12817 intentValue = parseInt(intentValue);
bgneal@312 12818
bgneal@312 12819 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
bgneal@312 12820 each(selection.getSelectedBlocks(), function(element) {
bgneal@312 12821 if (command == 'outdent') {
bgneal@312 12822 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
bgneal@312 12823 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
bgneal@312 12824 } else
bgneal@312 12825 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
bgneal@312 12826 });
bgneal@312 12827 } else
bgneal@312 12828 execNativeCommand(command);
bgneal@312 12829 },
bgneal@312 12830
bgneal@312 12831 mceRepaint : function() {
bgneal@312 12832 var bookmark;
bgneal@312 12833
bgneal@312 12834 if (tinymce.isGecko) {
bgneal@312 12835 try {
bgneal@312 12836 storeSelection(TRUE);
bgneal@312 12837
bgneal@312 12838 if (selection.getSel())
bgneal@312 12839 selection.getSel().selectAllChildren(editor.getBody());
bgneal@312 12840
bgneal@312 12841 selection.collapse(TRUE);
bgneal@312 12842 restoreSelection();
bgneal@312 12843 } catch (ex) {
bgneal@312 12844 // Ignore
bgneal@312 12845 }
bgneal@312 12846 }
bgneal@312 12847 },
bgneal@312 12848
bgneal@312 12849 mceToggleFormat : function(command, ui, value) {
bgneal@312 12850 editor.formatter.toggle(value);
bgneal@312 12851 },
bgneal@312 12852
bgneal@312 12853 InsertHorizontalRule : function() {
bgneal@442 12854 editor.execCommand('mceInsertContent', false, '<hr />');
bgneal@312 12855 },
bgneal@312 12856
bgneal@312 12857 mceToggleVisualAid : function() {
bgneal@312 12858 editor.hasVisual = !editor.hasVisual;
bgneal@312 12859 editor.addVisual();
bgneal@312 12860 },
bgneal@312 12861
bgneal@312 12862 mceReplaceContent : function(command, ui, value) {
bgneal@442 12863 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
bgneal@312 12864 },
bgneal@312 12865
bgneal@312 12866 mceInsertLink : function(command, ui, value) {
bgneal@442 12867 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
bgneal@312 12868
bgneal@312 12869 if (tinymce.is(value, 'string'))
bgneal@312 12870 value = {href : value};
bgneal@312 12871
bgneal@442 12872 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
bgneal@442 12873 value.href = value.href.replace(' ', '%20');
bgneal@442 12874
bgneal@312 12875 if (!link) {
bgneal@442 12876 // WebKit can't create links on float images for some odd reason so just remove it and restore it later
bgneal@442 12877 if (tinymce.isWebKit) {
bgneal@442 12878 img = dom.getParent(selection.getNode(), 'img');
bgneal@442 12879
bgneal@442 12880 if (img) {
bgneal@442 12881 floatVal = img.style.cssFloat;
bgneal@442 12882 img.style.cssFloat = null;
bgneal@442 12883 }
bgneal@442 12884 }
bgneal@442 12885
bgneal@312 12886 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
bgneal@442 12887
bgneal@442 12888 // Restore float value
bgneal@442 12889 if (floatVal)
bgneal@442 12890 img.style.cssFloat = floatVal;
bgneal@442 12891
bgneal@442 12892 each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
bgneal@312 12893 dom.setAttribs(link, value);
bgneal@312 12894 });
bgneal@312 12895 } else {
bgneal@312 12896 if (value.href)
bgneal@312 12897 dom.setAttribs(link, value);
bgneal@312 12898 else
bgneal@312 12899 editor.dom.remove(link, TRUE);
bgneal@312 12900 }
bgneal@312 12901 },
bgneal@312 12902
bgneal@312 12903 selectAll : function() {
bgneal@312 12904 var root = dom.getRoot(), rng = dom.createRng();
bgneal@312 12905
bgneal@312 12906 rng.setStart(root, 0);
bgneal@312 12907 rng.setEnd(root, root.childNodes.length);
bgneal@312 12908
bgneal@312 12909 editor.selection.setRng(rng);
bgneal@312 12910 }
bgneal@312 12911 });
bgneal@312 12912
bgneal@312 12913 // Add queryCommandState overrides
bgneal@312 12914 addCommands({
bgneal@312 12915 // Override justify commands
bgneal@312 12916 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
bgneal@312 12917 return isFormatMatch('align' + command.substring(7));
bgneal@312 12918 },
bgneal@312 12919
bgneal@442 12920 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
bgneal@312 12921 return isFormatMatch(command);
bgneal@312 12922 },
bgneal@312 12923
bgneal@312 12924 mceBlockQuote : function() {
bgneal@312 12925 return isFormatMatch('blockquote');
bgneal@312 12926 },
bgneal@312 12927
bgneal@312 12928 Outdent : function() {
bgneal@312 12929 var node;
bgneal@312 12930
bgneal@312 12931 if (settings.inline_styles) {
bgneal@312 12932 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@312 12933 return TRUE;
bgneal@312 12934
bgneal@312 12935 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
bgneal@312 12936 return TRUE;
bgneal@312 12937 }
bgneal@312 12938
bgneal@312 12939 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
bgneal@312 12940 },
bgneal@312 12941
bgneal@312 12942 'InsertUnorderedList,InsertOrderedList' : function(command) {
bgneal@312 12943 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
bgneal@312 12944 }
bgneal@312 12945 }, 'state');
bgneal@312 12946
bgneal@312 12947 // Add queryCommandValue overrides
bgneal@312 12948 addCommands({
bgneal@312 12949 'FontSize,FontName' : function(command) {
bgneal@312 12950 var value = 0, parent;
bgneal@312 12951
bgneal@312 12952 if (parent = dom.getParent(selection.getNode(), 'span')) {
bgneal@312 12953 if (command == 'fontsize')
bgneal@312 12954 value = parent.style.fontSize;
bgneal@312 12955 else
bgneal@312 12956 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
bgneal@312 12957 }
bgneal@312 12958
bgneal@312 12959 return value;
bgneal@312 12960 }
bgneal@312 12961 }, 'value');
bgneal@312 12962
bgneal@312 12963 // Add undo manager logic
bgneal@312 12964 if (settings.custom_undo_redo) {
bgneal@312 12965 addCommands({
bgneal@312 12966 Undo : function() {
bgneal@312 12967 editor.undoManager.undo();
bgneal@312 12968 },
bgneal@312 12969
bgneal@312 12970 Redo : function() {
bgneal@312 12971 editor.undoManager.redo();
bgneal@312 12972 }
bgneal@312 12973 });
bgneal@312 12974 }
bgneal@312 12975 };
bgneal@312 12976 })(tinymce);
bgneal@442 12977
bgneal@312 12978 (function(tinymce) {
bgneal@312 12979 var Dispatcher = tinymce.util.Dispatcher;
bgneal@312 12980
bgneal@312 12981 tinymce.UndoManager = function(editor) {
bgneal@312 12982 var self, index = 0, data = [];
bgneal@312 12983
bgneal@312 12984 function getContent() {
bgneal@312 12985 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
bgneal@312 12986 };
bgneal@312 12987
bgneal@312 12988 return self = {
bgneal@442 12989 typing : false,
bgneal@312 12990
bgneal@312 12991 onAdd : new Dispatcher(self),
bgneal@442 12992
bgneal@312 12993 onUndo : new Dispatcher(self),
bgneal@442 12994
bgneal@312 12995 onRedo : new Dispatcher(self),
bgneal@312 12996
bgneal@442 12997 beforeChange : function() {
bgneal@442 12998 // Set before bookmark on previous level
bgneal@442 12999 if (data[index])
bgneal@442 13000 data[index].beforeBookmark = editor.selection.getBookmark(2, true);
bgneal@442 13001 },
bgneal@442 13002
bgneal@312 13003 add : function(level) {
bgneal@312 13004 var i, settings = editor.settings, lastLevel;
bgneal@312 13005
bgneal@312 13006 level = level || {};
bgneal@312 13007 level.content = getContent();
bgneal@312 13008
bgneal@312 13009 // Add undo level if needed
bgneal@312 13010 lastLevel = data[index];
bgneal@442 13011 if (lastLevel && lastLevel.content == level.content)
bgneal@442 13012 return null;
bgneal@312 13013
bgneal@312 13014 // Time to compress
bgneal@312 13015 if (settings.custom_undo_redo_levels) {
bgneal@312 13016 if (data.length > settings.custom_undo_redo_levels) {
bgneal@312 13017 for (i = 0; i < data.length - 1; i++)
bgneal@312 13018 data[i] = data[i + 1];
bgneal@312 13019
bgneal@312 13020 data.length--;
bgneal@312 13021 index = data.length;
bgneal@312 13022 }
bgneal@312 13023 }
bgneal@312 13024
bgneal@312 13025 // Get a non intrusive normalized bookmark
bgneal@312 13026 level.bookmark = editor.selection.getBookmark(2, true);
bgneal@312 13027
bgneal@312 13028 // Crop array if needed
bgneal@442 13029 if (index < data.length - 1)
bgneal@442 13030 data.length = index + 1;
bgneal@312 13031
bgneal@312 13032 data.push(level);
bgneal@312 13033 index = data.length - 1;
bgneal@312 13034
bgneal@312 13035 self.onAdd.dispatch(self, level);
bgneal@312 13036 editor.isNotDirty = 0;
bgneal@312 13037
bgneal@312 13038 return level;
bgneal@312 13039 },
bgneal@312 13040
bgneal@312 13041 undo : function() {
bgneal@312 13042 var level, i;
bgneal@312 13043
bgneal@312 13044 if (self.typing) {
bgneal@312 13045 self.add();
bgneal@442 13046 self.typing = false;
bgneal@312 13047 }
bgneal@312 13048
bgneal@312 13049 if (index > 0) {
bgneal@312 13050 level = data[--index];
bgneal@312 13051
bgneal@312 13052 editor.setContent(level.content, {format : 'raw'});
bgneal@442 13053 editor.selection.moveToBookmark(level.beforeBookmark);
bgneal@312 13054
bgneal@312 13055 self.onUndo.dispatch(self, level);
bgneal@312 13056 }
bgneal@312 13057
bgneal@312 13058 return level;
bgneal@312 13059 },
bgneal@312 13060
bgneal@312 13061 redo : function() {
bgneal@312 13062 var level;
bgneal@312 13063
bgneal@312 13064 if (index < data.length - 1) {
bgneal@312 13065 level = data[++index];
bgneal@312 13066
bgneal@312 13067 editor.setContent(level.content, {format : 'raw'});
bgneal@312 13068 editor.selection.moveToBookmark(level.bookmark);
bgneal@312 13069
bgneal@312 13070 self.onRedo.dispatch(self, level);
bgneal@312 13071 }
bgneal@312 13072
bgneal@312 13073 return level;
bgneal@312 13074 },
bgneal@312 13075
bgneal@312 13076 clear : function() {
bgneal@312 13077 data = [];
bgneal@442 13078 index = 0;
bgneal@442 13079 self.typing = false;
bgneal@312 13080 },
bgneal@312 13081
bgneal@312 13082 hasUndo : function() {
bgneal@442 13083 return index > 0 || this.typing;
bgneal@312 13084 },
bgneal@312 13085
bgneal@312 13086 hasRedo : function() {
bgneal@442 13087 return index < data.length - 1 && !this.typing;
bgneal@312 13088 }
bgneal@312 13089 };
bgneal@312 13090 };
bgneal@312 13091 })(tinymce);
bgneal@312 13092
bgneal@312 13093 (function(tinymce) {
bgneal@312 13094 // Shorten names
bgneal@312 13095 var Event = tinymce.dom.Event,
bgneal@312 13096 isIE = tinymce.isIE,
bgneal@312 13097 isGecko = tinymce.isGecko,
bgneal@312 13098 isOpera = tinymce.isOpera,
bgneal@312 13099 each = tinymce.each,
bgneal@312 13100 extend = tinymce.extend,
bgneal@312 13101 TRUE = true,
bgneal@312 13102 FALSE = false;
bgneal@312 13103
bgneal@312 13104 function cloneFormats(node) {
bgneal@312 13105 var clone, temp, inner;
bgneal@312 13106
bgneal@312 13107 do {
bgneal@312 13108 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
bgneal@312 13109 if (clone) {
bgneal@312 13110 temp = node.cloneNode(false);
bgneal@312 13111 temp.appendChild(clone);
bgneal@312 13112 clone = temp;
bgneal@312 13113 } else {
bgneal@312 13114 clone = inner = node.cloneNode(false);
bgneal@312 13115 }
bgneal@312 13116
bgneal@312 13117 clone.removeAttribute('id');
bgneal@312 13118 }
bgneal@312 13119 } while (node = node.parentNode);
bgneal@312 13120
bgneal@312 13121 if (clone)
bgneal@312 13122 return {wrapper : clone, inner : inner};
bgneal@312 13123 };
bgneal@312 13124
bgneal@312 13125 // Checks if the selection/caret is at the end of the specified block element
bgneal@312 13126 function isAtEnd(rng, par) {
bgneal@312 13127 var rng2 = par.ownerDocument.createRange();
bgneal@312 13128
bgneal@312 13129 rng2.setStart(rng.endContainer, rng.endOffset);
bgneal@312 13130 rng2.setEndAfter(par);
bgneal@312 13131
bgneal@312 13132 // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
bgneal@312 13133 return rng2.cloneContents().textContent.length == 0;
bgneal@312 13134 };
bgneal@312 13135
bgneal@312 13136 function splitList(selection, dom, li) {
bgneal@312 13137 var listBlock, block;
bgneal@312 13138
bgneal@442 13139 if (dom.isEmpty(li)) {
bgneal@312 13140 listBlock = dom.getParent(li, 'ul,ol');
bgneal@312 13141
bgneal@312 13142 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
bgneal@312 13143 dom.split(listBlock, li);
bgneal@442 13144 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
bgneal@312 13145 dom.replace(block, li);
bgneal@312 13146 selection.select(block, 1);
bgneal@312 13147 }
bgneal@312 13148
bgneal@312 13149 return FALSE;
bgneal@312 13150 }
bgneal@312 13151
bgneal@312 13152 return TRUE;
bgneal@312 13153 };
bgneal@312 13154
bgneal@312 13155 tinymce.create('tinymce.ForceBlocks', {
bgneal@312 13156 ForceBlocks : function(ed) {
bgneal@312 13157 var t = this, s = ed.settings, elm;
bgneal@312 13158
bgneal@312 13159 t.editor = ed;
bgneal@312 13160 t.dom = ed.dom;
bgneal@312 13161 elm = (s.forced_root_block || 'p').toLowerCase();
bgneal@312 13162 s.element = elm.toUpperCase();
bgneal@312 13163
bgneal@312 13164 ed.onPreInit.add(t.setup, t);
bgneal@312 13165
bgneal@312 13166 if (s.forced_root_block) {
bgneal@312 13167 ed.onInit.add(t.forceRoots, t);
bgneal@312 13168 ed.onSetContent.add(t.forceRoots, t);
bgneal@312 13169 ed.onBeforeGetContent.add(t.forceRoots, t);
bgneal@442 13170 ed.onExecCommand.add(function(ed, cmd) {
bgneal@442 13171 if (cmd == 'mceInsertContent') {
bgneal@442 13172 t.forceRoots();
bgneal@442 13173 ed.nodeChanged();
bgneal@442 13174 }
bgneal@442 13175 });
bgneal@312 13176 }
bgneal@312 13177 },
bgneal@312 13178
bgneal@312 13179 setup : function() {
bgneal@312 13180 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
bgneal@312 13181
bgneal@312 13182 // Force root blocks when typing and when getting output
bgneal@312 13183 if (s.forced_root_block) {
bgneal@312 13184 ed.onBeforeExecCommand.add(t.forceRoots, t);
bgneal@312 13185 ed.onKeyUp.add(t.forceRoots, t);
bgneal@312 13186 ed.onPreProcess.add(t.forceRoots, t);
bgneal@312 13187 }
bgneal@312 13188
bgneal@312 13189 if (s.force_br_newlines) {
bgneal@312 13190 // Force IE to produce BRs on enter
bgneal@312 13191 if (isIE) {
bgneal@312 13192 ed.onKeyPress.add(function(ed, e) {
bgneal@312 13193 var n;
bgneal@312 13194
bgneal@312 13195 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
bgneal@312 13196 selection.setContent('<br id="__" /> ', {format : 'raw'});
bgneal@312 13197 n = dom.get('__');
bgneal@312 13198 n.removeAttribute('id');
bgneal@312 13199 selection.select(n);
bgneal@312 13200 selection.collapse();
bgneal@312 13201 return Event.cancel(e);
bgneal@312 13202 }
bgneal@312 13203 });
bgneal@312 13204 }
bgneal@312 13205 }
bgneal@312 13206
bgneal@312 13207 if (s.force_p_newlines) {
bgneal@312 13208 if (!isIE) {
bgneal@312 13209 ed.onKeyPress.add(function(ed, e) {
bgneal@312 13210 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
bgneal@312 13211 Event.cancel(e);
bgneal@312 13212 });
bgneal@312 13213 } else {
bgneal@312 13214 // Ungly hack to for IE to preserve the formatting when you press
bgneal@312 13215 // enter at the end of a block element with formatted contents
bgneal@312 13216 // This logic overrides the browsers default logic with
bgneal@312 13217 // custom logic that enables us to control the output
bgneal@312 13218 tinymce.addUnload(function() {
bgneal@312 13219 t._previousFormats = 0; // Fix IE leak
bgneal@312 13220 });
bgneal@312 13221
bgneal@312 13222 ed.onKeyPress.add(function(ed, e) {
bgneal@312 13223 t._previousFormats = 0;
bgneal@312 13224
bgneal@312 13225 // Clone the current formats, this will later be applied to the new block contents
bgneal@312 13226 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
bgneal@312 13227 t._previousFormats = cloneFormats(ed.selection.getStart());
bgneal@312 13228 });
bgneal@312 13229
bgneal@312 13230 ed.onKeyUp.add(function(ed, e) {
bgneal@312 13231 // Let IE break the element and the wrap the new caret location in the previous formats
bgneal@312 13232 if (e.keyCode == 13 && !e.shiftKey) {
bgneal@312 13233 var parent = ed.selection.getStart(), fmt = t._previousFormats;
bgneal@312 13234
bgneal@312 13235 // Parent is an empty block
bgneal@312 13236 if (!parent.hasChildNodes() && fmt) {
bgneal@312 13237 parent = dom.getParent(parent, dom.isBlock);
bgneal@312 13238
bgneal@312 13239 if (parent && parent.nodeName != 'LI') {
bgneal@312 13240 parent.innerHTML = '';
bgneal@312 13241
bgneal@312 13242 if (t._previousFormats) {
bgneal@312 13243 parent.appendChild(fmt.wrapper);
bgneal@312 13244 fmt.inner.innerHTML = '\uFEFF';
bgneal@312 13245 } else
bgneal@312 13246 parent.innerHTML = '\uFEFF';
bgneal@312 13247
bgneal@312 13248 selection.select(parent, 1);
bgneal@442 13249 selection.collapse(true);
bgneal@312 13250 ed.getDoc().execCommand('Delete', false, null);
bgneal@312 13251 t._previousFormats = 0;
bgneal@312 13252 }
bgneal@312 13253 }
bgneal@312 13254 }
bgneal@312 13255 });
bgneal@312 13256 }
bgneal@312 13257
bgneal@312 13258 if (isGecko) {
bgneal@312 13259 ed.onKeyDown.add(function(ed, e) {
bgneal@312 13260 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
bgneal@312 13261 t.backspaceDelete(e, e.keyCode == 8);
bgneal@312 13262 });
bgneal@312 13263 }
bgneal@312 13264 }
bgneal@312 13265
bgneal@312 13266 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
bgneal@312 13267 if (tinymce.isWebKit) {
bgneal@312 13268 function insertBr(ed) {
bgneal@312 13269 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
bgneal@312 13270
bgneal@312 13271 // Insert BR element
bgneal@312 13272 rng.insertNode(br = dom.create('br'));
bgneal@312 13273
bgneal@312 13274 // Place caret after BR
bgneal@312 13275 rng.setStartAfter(br);
bgneal@312 13276 rng.setEndAfter(br);
bgneal@312 13277 selection.setRng(rng);
bgneal@312 13278
bgneal@312 13279 // Could not place caret after BR then insert an nbsp entity and move the caret
bgneal@312 13280 if (selection.getSel().focusNode == br.previousSibling) {
bgneal@312 13281 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
bgneal@312 13282 selection.collapse(TRUE);
bgneal@312 13283 }
bgneal@312 13284
bgneal@312 13285 // Create a temporary DIV after the BR and get the position as it
bgneal@312 13286 // seems like getPos() returns 0 for text nodes and BR elements.
bgneal@312 13287 dom.insertAfter(div, br);
bgneal@312 13288 divYPos = dom.getPos(div).y;
bgneal@312 13289 dom.remove(div);
bgneal@312 13290
bgneal@312 13291 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
bgneal@312 13292 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
bgneal@312 13293 ed.getWin().scrollTo(0, divYPos);
bgneal@312 13294 };
bgneal@312 13295
bgneal@312 13296 ed.onKeyPress.add(function(ed, e) {
bgneal@312 13297 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
bgneal@312 13298 insertBr(ed);
bgneal@312 13299 Event.cancel(e);
bgneal@312 13300 }
bgneal@312 13301 });
bgneal@312 13302 }
bgneal@312 13303
bgneal@312 13304 // IE specific fixes
bgneal@312 13305 if (isIE) {
bgneal@312 13306 // Replaces IE:s auto generated paragraphs with the specified element name
bgneal@312 13307 if (s.element != 'P') {
bgneal@312 13308 ed.onKeyPress.add(function(ed, e) {
bgneal@312 13309 t.lastElm = selection.getNode().nodeName;
bgneal@312 13310 });
bgneal@312 13311
bgneal@312 13312 ed.onKeyUp.add(function(ed, e) {
bgneal@312 13313 var bl, n = selection.getNode(), b = ed.getBody();
bgneal@312 13314
bgneal@312 13315 if (b.childNodes.length === 1 && n.nodeName == 'P') {
bgneal@312 13316 n = dom.rename(n, s.element);
bgneal@312 13317 selection.select(n);
bgneal@312 13318 selection.collapse();
bgneal@312 13319 ed.nodeChanged();
bgneal@312 13320 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
bgneal@312 13321 bl = dom.getParent(n, 'p');
bgneal@312 13322
bgneal@312 13323 if (bl) {
bgneal@312 13324 dom.rename(bl, s.element);
bgneal@312 13325 ed.nodeChanged();
bgneal@312 13326 }
bgneal@312 13327 }
bgneal@312 13328 });
bgneal@312 13329 }
bgneal@312 13330 }
bgneal@312 13331 },
bgneal@312 13332
bgneal@312 13333 find : function(n, t, s) {
bgneal@312 13334 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
bgneal@312 13335
bgneal@312 13336 while (n = w.nextNode()) {
bgneal@312 13337 c++;
bgneal@312 13338
bgneal@312 13339 // Index by node
bgneal@312 13340 if (t == 0 && n == s)
bgneal@312 13341 return c;
bgneal@312 13342
bgneal@312 13343 // Node by index
bgneal@312 13344 if (t == 1 && c == s)
bgneal@312 13345 return n;
bgneal@312 13346 }
bgneal@312 13347
bgneal@312 13348 return -1;
bgneal@312 13349 },
bgneal@312 13350
bgneal@312 13351 forceRoots : function(ed, e) {
bgneal@312 13352 var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
bgneal@312 13353 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
bgneal@312 13354
bgneal@312 13355 // Fix for bug #1863847
bgneal@312 13356 //if (e && e.keyCode == 13)
bgneal@312 13357 // return TRUE;
bgneal@312 13358
bgneal@312 13359 // Wrap non blocks into blocks
bgneal@312 13360 for (i = nl.length - 1; i >= 0; i--) {
bgneal@312 13361 nx = nl[i];
bgneal@312 13362
bgneal@312 13363 // Ignore internal elements
bgneal@442 13364 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
bgneal@312 13365 bl = null;
bgneal@312 13366 continue;
bgneal@312 13367 }
bgneal@312 13368
bgneal@312 13369 // Is text or non block element
bgneal@312 13370 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
bgneal@312 13371 if (!bl) {
bgneal@312 13372 // Create new block but ignore whitespace
bgneal@312 13373 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
bgneal@312 13374 // Store selection
bgneal@312 13375 if (si == -2 && r) {
bgneal@442 13376 if (!isIE || r.setStart) {
bgneal@312 13377 // If selection is element then mark it
bgneal@312 13378 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
bgneal@312 13379 // Save the id of the selected element
bgneal@312 13380 eid = n.getAttribute("id");
bgneal@312 13381 n.setAttribute("id", "__mce");
bgneal@312 13382 } else {
bgneal@312 13383 // If element is inside body, might not be the case in contentEdiable mode
bgneal@312 13384 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
bgneal@312 13385 so = r.startOffset;
bgneal@312 13386 eo = r.endOffset;
bgneal@312 13387 si = t.find(b, 0, r.startContainer);
bgneal@312 13388 ei = t.find(b, 0, r.endContainer);
bgneal@312 13389 }
bgneal@312 13390 }
bgneal@312 13391 } else {
bgneal@312 13392 // Force control range into text range
bgneal@312 13393 if (r.item) {
bgneal@312 13394 tr = d.body.createTextRange();
bgneal@312 13395 tr.moveToElementText(r.item(0));
bgneal@312 13396 r = tr;
bgneal@312 13397 }
bgneal@312 13398
bgneal@312 13399 tr = d.body.createTextRange();
bgneal@312 13400 tr.moveToElementText(b);
bgneal@312 13401 tr.collapse(1);
bgneal@312 13402 bp = tr.move('character', c) * -1;
bgneal@312 13403
bgneal@312 13404 tr = r.duplicate();
bgneal@312 13405 tr.collapse(1);
bgneal@312 13406 sp = tr.move('character', c) * -1;
bgneal@312 13407
bgneal@312 13408 tr = r.duplicate();
bgneal@312 13409 tr.collapse(0);
bgneal@312 13410 le = (tr.move('character', c) * -1) - sp;
bgneal@312 13411
bgneal@312 13412 si = sp - bp;
bgneal@312 13413 ei = le;
bgneal@312 13414 }
bgneal@312 13415 }
bgneal@312 13416
bgneal@312 13417 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
bgneal@312 13418 // See: http://support.microsoft.com/kb/829907
bgneal@312 13419 bl = ed.dom.create(ed.settings.forced_root_block);
bgneal@312 13420 nx.parentNode.replaceChild(bl, nx);
bgneal@312 13421 bl.appendChild(nx);
bgneal@312 13422 }
bgneal@312 13423 } else {
bgneal@312 13424 if (bl.hasChildNodes())
bgneal@312 13425 bl.insertBefore(nx, bl.firstChild);
bgneal@312 13426 else
bgneal@312 13427 bl.appendChild(nx);
bgneal@312 13428 }
bgneal@312 13429 } else
bgneal@312 13430 bl = null; // Time to create new block
bgneal@312 13431 }
bgneal@312 13432
bgneal@312 13433 // Restore selection
bgneal@312 13434 if (si != -2) {
bgneal@442 13435 if (!isIE || r.setStart) {
bgneal@312 13436 bl = b.getElementsByTagName(ed.settings.element)[0];
bgneal@312 13437 r = d.createRange();
bgneal@312 13438
bgneal@312 13439 // Select last location or generated block
bgneal@312 13440 if (si != -1)
bgneal@312 13441 r.setStart(t.find(b, 1, si), so);
bgneal@312 13442 else
bgneal@312 13443 r.setStart(bl, 0);
bgneal@312 13444
bgneal@312 13445 // Select last location or generated block
bgneal@312 13446 if (ei != -1)
bgneal@312 13447 r.setEnd(t.find(b, 1, ei), eo);
bgneal@312 13448 else
bgneal@312 13449 r.setEnd(bl, 0);
bgneal@312 13450
bgneal@312 13451 if (s) {
bgneal@312 13452 s.removeAllRanges();
bgneal@312 13453 s.addRange(r);
bgneal@312 13454 }
bgneal@312 13455 } else {
bgneal@312 13456 try {
bgneal@312 13457 r = s.createRange();
bgneal@312 13458 r.moveToElementText(b);
bgneal@312 13459 r.collapse(1);
bgneal@312 13460 r.moveStart('character', si);
bgneal@312 13461 r.moveEnd('character', ei);
bgneal@312 13462 r.select();
bgneal@312 13463 } catch (ex) {
bgneal@312 13464 // Ignore
bgneal@312 13465 }
bgneal@312 13466 }
bgneal@442 13467 } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
bgneal@312 13468 // Restore the id of the selected element
bgneal@312 13469 if (eid)
bgneal@312 13470 n.setAttribute('id', eid);
bgneal@312 13471 else
bgneal@312 13472 n.removeAttribute('id');
bgneal@312 13473
bgneal@312 13474 // Move caret before selected element
bgneal@312 13475 r = d.createRange();
bgneal@312 13476 r.setStartBefore(n);
bgneal@312 13477 r.setEndBefore(n);
bgneal@312 13478 se.setRng(r);
bgneal@312 13479 }
bgneal@312 13480 },
bgneal@312 13481
bgneal@312 13482 getParentBlock : function(n) {
bgneal@312 13483 var d = this.dom;
bgneal@312 13484
bgneal@312 13485 return d.getParent(n, d.isBlock);
bgneal@312 13486 },
bgneal@312 13487
bgneal@312 13488 insertPara : function(e) {
bgneal@312 13489 var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
bgneal@312 13490 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
bgneal@312 13491
bgneal@442 13492 ed.undoManager.beforeChange();
bgneal@442 13493
bgneal@312 13494 // If root blocks are forced then use Operas default behavior since it's really good
bgneal@312 13495 // Removed due to bug: #1853816
bgneal@312 13496 // if (se.forced_root_block && isOpera)
bgneal@312 13497 // return TRUE;
bgneal@312 13498
bgneal@312 13499 // Setup before range
bgneal@312 13500 rb = d.createRange();
bgneal@312 13501
bgneal@312 13502 // If is before the first block element and in body, then move it into first block element
bgneal@312 13503 rb.setStart(s.anchorNode, s.anchorOffset);
bgneal@312 13504 rb.collapse(TRUE);
bgneal@312 13505
bgneal@312 13506 // Setup after range
bgneal@312 13507 ra = d.createRange();
bgneal@312 13508
bgneal@312 13509 // If is before the first block element and in body, then move it into first block element
bgneal@312 13510 ra.setStart(s.focusNode, s.focusOffset);
bgneal@312 13511 ra.collapse(TRUE);
bgneal@312 13512
bgneal@312 13513 // Setup start/end points
bgneal@312 13514 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
bgneal@312 13515 sn = dir ? s.anchorNode : s.focusNode;
bgneal@312 13516 so = dir ? s.anchorOffset : s.focusOffset;
bgneal@312 13517 en = dir ? s.focusNode : s.anchorNode;
bgneal@312 13518 eo = dir ? s.focusOffset : s.anchorOffset;
bgneal@312 13519
bgneal@312 13520 // If selection is in empty table cell
bgneal@312 13521 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
bgneal@312 13522 if (sn.firstChild.nodeName == 'BR')
bgneal@312 13523 dom.remove(sn.firstChild); // Remove BR
bgneal@312 13524
bgneal@312 13525 // Create two new block elements
bgneal@312 13526 if (sn.childNodes.length == 0) {
bgneal@312 13527 ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 13528 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 13529 } else {
bgneal@312 13530 n = sn.innerHTML;
bgneal@312 13531 sn.innerHTML = '';
bgneal@312 13532 ed.dom.add(sn, se.element, null, n);
bgneal@312 13533 aft = ed.dom.add(sn, se.element, null, '<br />');
bgneal@312 13534 }
bgneal@312 13535
bgneal@312 13536 // Move caret into the last one
bgneal@312 13537 r = d.createRange();
bgneal@312 13538 r.selectNodeContents(aft);
bgneal@312 13539 r.collapse(1);
bgneal@312 13540 ed.selection.setRng(r);
bgneal@312 13541
bgneal@312 13542 return FALSE;
bgneal@312 13543 }
bgneal@312 13544
bgneal@312 13545 // If the caret is in an invalid location in FF we need to move it into the first block
bgneal@312 13546 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
bgneal@312 13547 sn = en = sn.firstChild;
bgneal@312 13548 so = eo = 0;
bgneal@312 13549 rb = d.createRange();
bgneal@312 13550 rb.setStart(sn, 0);
bgneal@312 13551 ra = d.createRange();
bgneal@312 13552 ra.setStart(en, 0);
bgneal@312 13553 }
bgneal@312 13554
bgneal@312 13555 // Never use body as start or end node
bgneal@312 13556 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@312 13557 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
bgneal@312 13558 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
bgneal@312 13559 en = en.nodeName == "BODY" ? en.firstChild : en;
bgneal@312 13560
bgneal@312 13561 // Get start and end blocks
bgneal@312 13562 sb = t.getParentBlock(sn);
bgneal@312 13563 eb = t.getParentBlock(en);
bgneal@312 13564 bn = sb ? sb.nodeName : se.element; // Get block name to create
bgneal@312 13565
bgneal@312 13566 // Return inside list use default browser behavior
bgneal@312 13567 if (n = t.dom.getParent(sb, 'li,pre')) {
bgneal@312 13568 if (n.nodeName == 'LI')
bgneal@312 13569 return splitList(ed.selection, t.dom, n);
bgneal@312 13570
bgneal@312 13571 return TRUE;
bgneal@312 13572 }
bgneal@312 13573
bgneal@312 13574 // If caption or absolute layers then always generate new blocks within
bgneal@312 13575 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@312 13576 bn = se.element;
bgneal@312 13577 sb = null;
bgneal@312 13578 }
bgneal@312 13579
bgneal@312 13580 // If caption or absolute layers then always generate new blocks within
bgneal@312 13581 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
bgneal@312 13582 bn = se.element;
bgneal@312 13583 eb = null;
bgneal@312 13584 }
bgneal@312 13585
bgneal@312 13586 // Use P instead
bgneal@312 13587 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
bgneal@312 13588 bn = se.element;
bgneal@312 13589 sb = eb = null;
bgneal@312 13590 }
bgneal@312 13591
bgneal@312 13592 // Setup new before and after blocks
bgneal@312 13593 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
bgneal@312 13594 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
bgneal@312 13595
bgneal@312 13596 // Remove id from after clone
bgneal@312 13597 aft.removeAttribute('id');
bgneal@312 13598
bgneal@312 13599 // Is header and cursor is at the end, then force paragraph under
bgneal@312 13600 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
bgneal@312 13601 aft = ed.dom.create(se.element);
bgneal@312 13602
bgneal@312 13603 // Find start chop node
bgneal@312 13604 n = sc = sn;
bgneal@312 13605 do {
bgneal@312 13606 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@312 13607 break;
bgneal@312 13608
bgneal@312 13609 sc = n;
bgneal@312 13610 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
bgneal@312 13611
bgneal@312 13612 // Find end chop node
bgneal@312 13613 n = ec = en;
bgneal@312 13614 do {
bgneal@312 13615 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
bgneal@312 13616 break;
bgneal@312 13617
bgneal@312 13618 ec = n;
bgneal@312 13619 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
bgneal@312 13620
bgneal@312 13621 // Place first chop part into before block element
bgneal@312 13622 if (sc.nodeName == bn)
bgneal@312 13623 rb.setStart(sc, 0);
bgneal@312 13624 else
bgneal@312 13625 rb.setStartBefore(sc);
bgneal@312 13626
bgneal@312 13627 rb.setEnd(sn, so);
bgneal@312 13628 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@312 13629
bgneal@312 13630 // Place secnd chop part within new block element
bgneal@312 13631 try {
bgneal@312 13632 ra.setEndAfter(ec);
bgneal@312 13633 } catch(ex) {
bgneal@312 13634 //console.debug(s.focusNode, s.focusOffset);
bgneal@312 13635 }
bgneal@312 13636
bgneal@312 13637 ra.setStart(en, eo);
bgneal@312 13638 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
bgneal@312 13639
bgneal@312 13640 // Create range around everything
bgneal@312 13641 r = d.createRange();
bgneal@312 13642 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
bgneal@312 13643 r.setStartBefore(sc.parentNode);
bgneal@312 13644 } else {
bgneal@312 13645 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
bgneal@312 13646 r.setStartBefore(rb.startContainer);
bgneal@312 13647 else
bgneal@312 13648 r.setStart(rb.startContainer, rb.startOffset);
bgneal@312 13649 }
bgneal@312 13650
bgneal@312 13651 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
bgneal@312 13652 r.setEndAfter(ec.parentNode);
bgneal@312 13653 else
bgneal@312 13654 r.setEnd(ra.endContainer, ra.endOffset);
bgneal@312 13655
bgneal@312 13656 // Delete and replace it with new block elements
bgneal@312 13657 r.deleteContents();
bgneal@312 13658
bgneal@312 13659 if (isOpera)
bgneal@312 13660 ed.getWin().scrollTo(0, vp.y);
bgneal@312 13661
bgneal@312 13662 // Never wrap blocks in blocks
bgneal@312 13663 if (bef.firstChild && bef.firstChild.nodeName == bn)
bgneal@312 13664 bef.innerHTML = bef.firstChild.innerHTML;
bgneal@312 13665
bgneal@312 13666 if (aft.firstChild && aft.firstChild.nodeName == bn)
bgneal@312 13667 aft.innerHTML = aft.firstChild.innerHTML;
bgneal@312 13668
bgneal@312 13669 // Padd empty blocks
bgneal@442 13670 if (dom.isEmpty(bef))
bgneal@312 13671 bef.innerHTML = '<br />';
bgneal@312 13672
bgneal@312 13673 function appendStyles(e, en) {
bgneal@312 13674 var nl = [], nn, n, i;
bgneal@312 13675
bgneal@312 13676 e.innerHTML = '';
bgneal@312 13677
bgneal@312 13678 // Make clones of style elements
bgneal@312 13679 if (se.keep_styles) {
bgneal@312 13680 n = en;
bgneal@312 13681 do {
bgneal@312 13682 // We only want style specific elements
bgneal@312 13683 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
bgneal@312 13684 nn = n.cloneNode(FALSE);
bgneal@312 13685 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
bgneal@312 13686 nl.push(nn);
bgneal@312 13687 }
bgneal@312 13688 } while (n = n.parentNode);
bgneal@312 13689 }
bgneal@312 13690
bgneal@312 13691 // Append style elements to aft
bgneal@312 13692 if (nl.length > 0) {
bgneal@312 13693 for (i = nl.length - 1, nn = e; i >= 0; i--)
bgneal@312 13694 nn = nn.appendChild(nl[i]);
bgneal@312 13695
bgneal@312 13696 // Padd most inner style element
bgneal@442 13697 nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@312 13698 return nl[0]; // Move caret to most inner element
bgneal@312 13699 } else
bgneal@442 13700 e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
bgneal@312 13701 };
bgneal@312 13702
bgneal@312 13703 // Fill empty afterblook with current style
bgneal@442 13704 if (dom.isEmpty(aft))
bgneal@312 13705 car = appendStyles(aft, en);
bgneal@312 13706
bgneal@312 13707 // Opera needs this one backwards for older versions
bgneal@312 13708 if (isOpera && parseFloat(opera.version()) < 9.5) {
bgneal@312 13709 r.insertNode(bef);
bgneal@312 13710 r.insertNode(aft);
bgneal@312 13711 } else {
bgneal@312 13712 r.insertNode(aft);
bgneal@312 13713 r.insertNode(bef);
bgneal@312 13714 }
bgneal@312 13715
bgneal@312 13716 // Normalize
bgneal@312 13717 aft.normalize();
bgneal@312 13718 bef.normalize();
bgneal@312 13719
bgneal@312 13720 function first(n) {
bgneal@312 13721 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
bgneal@312 13722 };
bgneal@312 13723
bgneal@312 13724 // Move cursor and scroll into view
bgneal@312 13725 r = d.createRange();
bgneal@312 13726 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
bgneal@312 13727 r.collapse(1);
bgneal@312 13728 s.removeAllRanges();
bgneal@312 13729 s.addRange(r);
bgneal@312 13730
bgneal@312 13731 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
bgneal@312 13732 y = ed.dom.getPos(aft).y;
bgneal@442 13733 //ch = aft.clientHeight;
bgneal@312 13734
bgneal@312 13735 // Is element within viewport
bgneal@442 13736 if (y < vp.y || y + 25 > vp.y + vp.h) {
bgneal@312 13737 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@442 13738
bgneal@442 13739 /*console.debug(
bgneal@442 13740 'Element: y=' + y + ', h=' + ch + ', ' +
bgneal@442 13741 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
bgneal@442 13742 );*/
bgneal@442 13743 }
bgneal@442 13744
bgneal@442 13745 ed.undoManager.add();
bgneal@312 13746
bgneal@312 13747 return FALSE;
bgneal@312 13748 },
bgneal@312 13749
bgneal@312 13750 backspaceDelete : function(e, bs) {
bgneal@312 13751 var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
bgneal@312 13752
bgneal@312 13753 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
bgneal@312 13754 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
bgneal@312 13755 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
bgneal@312 13756
bgneal@312 13757 // Walk the dom backwards until we find a text node
bgneal@312 13758 for (n = sc.lastChild; n; n = walker.prev()) {
bgneal@312 13759 if (n.nodeType == 3) {
bgneal@312 13760 r.setStart(n, n.nodeValue.length);
bgneal@312 13761 r.collapse(true);
bgneal@312 13762 se.setRng(r);
bgneal@312 13763 return;
bgneal@312 13764 }
bgneal@312 13765 }
bgneal@312 13766 }
bgneal@312 13767
bgneal@312 13768 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
bgneal@312 13769 // This workaround removes the element by hand and moves the caret to the previous element
bgneal@312 13770 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
bgneal@312 13771 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
bgneal@312 13772 // Find previous block element
bgneal@312 13773 n = sc;
bgneal@312 13774 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
bgneal@312 13775
bgneal@312 13776 if (n) {
bgneal@312 13777 if (sc != b.firstChild) {
bgneal@312 13778 // Find last text node
bgneal@312 13779 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
bgneal@312 13780 while (tn = w.nextNode())
bgneal@312 13781 n = tn;
bgneal@312 13782
bgneal@312 13783 // Place caret at the end of last text node
bgneal@312 13784 r = ed.getDoc().createRange();
bgneal@312 13785 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@312 13786 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
bgneal@312 13787 se.setRng(r);
bgneal@312 13788
bgneal@312 13789 // Remove the target container
bgneal@312 13790 ed.dom.remove(sc);
bgneal@312 13791 }
bgneal@312 13792
bgneal@312 13793 return Event.cancel(e);
bgneal@312 13794 }
bgneal@312 13795 }
bgneal@312 13796 }
bgneal@312 13797 }
bgneal@312 13798 });
bgneal@312 13799 })(tinymce);
bgneal@312 13800
bgneal@312 13801 (function(tinymce) {
bgneal@312 13802 // Shorten names
bgneal@312 13803 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
bgneal@312 13804
bgneal@312 13805 tinymce.create('tinymce.ControlManager', {
bgneal@312 13806 ControlManager : function(ed, s) {
bgneal@312 13807 var t = this, i;
bgneal@312 13808
bgneal@312 13809 s = s || {};
bgneal@312 13810 t.editor = ed;
bgneal@312 13811 t.controls = {};
bgneal@312 13812 t.onAdd = new tinymce.util.Dispatcher(t);
bgneal@312 13813 t.onPostRender = new tinymce.util.Dispatcher(t);
bgneal@312 13814 t.prefix = s.prefix || ed.id + '_';
bgneal@312 13815 t._cls = {};
bgneal@312 13816
bgneal@312 13817 t.onPostRender.add(function() {
bgneal@312 13818 each(t.controls, function(c) {
bgneal@312 13819 c.postRender();
bgneal@312 13820 });
bgneal@312 13821 });
bgneal@312 13822 },
bgneal@312 13823
bgneal@312 13824 get : function(id) {
bgneal@312 13825 return this.controls[this.prefix + id] || this.controls[id];
bgneal@312 13826 },
bgneal@312 13827
bgneal@312 13828 setActive : function(id, s) {
bgneal@312 13829 var c = null;
bgneal@312 13830
bgneal@312 13831 if (c = this.get(id))
bgneal@312 13832 c.setActive(s);
bgneal@312 13833
bgneal@312 13834 return c;
bgneal@312 13835 },
bgneal@312 13836
bgneal@312 13837 setDisabled : function(id, s) {
bgneal@312 13838 var c = null;
bgneal@312 13839
bgneal@312 13840 if (c = this.get(id))
bgneal@312 13841 c.setDisabled(s);
bgneal@312 13842
bgneal@312 13843 return c;
bgneal@312 13844 },
bgneal@312 13845
bgneal@312 13846 add : function(c) {
bgneal@312 13847 var t = this;
bgneal@312 13848
bgneal@312 13849 if (c) {
bgneal@312 13850 t.controls[c.id] = c;
bgneal@312 13851 t.onAdd.dispatch(c, t);
bgneal@312 13852 }
bgneal@312 13853
bgneal@312 13854 return c;
bgneal@312 13855 },
bgneal@312 13856
bgneal@312 13857 createControl : function(n) {
bgneal@312 13858 var c, t = this, ed = t.editor;
bgneal@312 13859
bgneal@312 13860 each(ed.plugins, function(p) {
bgneal@312 13861 if (p.createControl) {
bgneal@312 13862 c = p.createControl(n, t);
bgneal@312 13863
bgneal@312 13864 if (c)
bgneal@312 13865 return false;
bgneal@312 13866 }
bgneal@312 13867 });
bgneal@312 13868
bgneal@312 13869 switch (n) {
bgneal@312 13870 case "|":
bgneal@312 13871 case "separator":
bgneal@312 13872 return t.createSeparator();
bgneal@312 13873 }
bgneal@312 13874
bgneal@312 13875 if (!c && ed.buttons && (c = ed.buttons[n]))
bgneal@312 13876 return t.createButton(n, c);
bgneal@312 13877
bgneal@312 13878 return t.add(c);
bgneal@312 13879 },
bgneal@312 13880
bgneal@312 13881 createDropMenu : function(id, s, cc) {
bgneal@312 13882 var t = this, ed = t.editor, c, bm, v, cls;
bgneal@312 13883
bgneal@312 13884 s = extend({
bgneal@312 13885 'class' : 'mceDropDown',
bgneal@312 13886 constrain : ed.settings.constrain_menus
bgneal@312 13887 }, s);
bgneal@312 13888
bgneal@312 13889 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
bgneal@312 13890 if (v = ed.getParam('skin_variant'))
bgneal@312 13891 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
bgneal@312 13892
bgneal@312 13893 id = t.prefix + id;
bgneal@312 13894 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
bgneal@312 13895 c = t.controls[id] = new cls(id, s);
bgneal@312 13896 c.onAddItem.add(function(c, o) {
bgneal@312 13897 var s = o.settings;
bgneal@312 13898
bgneal@312 13899 s.title = ed.getLang(s.title, s.title);
bgneal@312 13900
bgneal@312 13901 if (!s.onclick) {
bgneal@312 13902 s.onclick = function(v) {
bgneal@312 13903 if (s.cmd)
bgneal@312 13904 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@312 13905 };
bgneal@312 13906 }
bgneal@312 13907 });
bgneal@312 13908
bgneal@312 13909 ed.onRemove.add(function() {
bgneal@312 13910 c.destroy();
bgneal@312 13911 });
bgneal@312 13912
bgneal@312 13913 // Fix for bug #1897785, #1898007
bgneal@312 13914 if (tinymce.isIE) {
bgneal@312 13915 c.onShowMenu.add(function() {
bgneal@312 13916 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@312 13917 ed.focus();
bgneal@312 13918
bgneal@312 13919 bm = ed.selection.getBookmark(1);
bgneal@312 13920 });
bgneal@312 13921
bgneal@312 13922 c.onHideMenu.add(function() {
bgneal@312 13923 if (bm) {
bgneal@312 13924 ed.selection.moveToBookmark(bm);
bgneal@312 13925 bm = 0;
bgneal@312 13926 }
bgneal@312 13927 });
bgneal@312 13928 }
bgneal@312 13929
bgneal@312 13930 return t.add(c);
bgneal@312 13931 },
bgneal@312 13932
bgneal@312 13933 createListBox : function(id, s, cc) {
bgneal@312 13934 var t = this, ed = t.editor, cmd, c, cls;
bgneal@312 13935
bgneal@312 13936 if (t.get(id))
bgneal@312 13937 return null;
bgneal@312 13938
bgneal@312 13939 s.title = ed.translate(s.title);
bgneal@312 13940 s.scope = s.scope || ed;
bgneal@312 13941
bgneal@312 13942 if (!s.onselect) {
bgneal@312 13943 s.onselect = function(v) {
bgneal@312 13944 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 13945 };
bgneal@312 13946 }
bgneal@312 13947
bgneal@312 13948 s = extend({
bgneal@312 13949 title : s.title,
bgneal@312 13950 'class' : 'mce_' + id,
bgneal@312 13951 scope : s.scope,
bgneal@312 13952 control_manager : t
bgneal@312 13953 }, s);
bgneal@312 13954
bgneal@312 13955 id = t.prefix + id;
bgneal@312 13956
bgneal@312 13957 if (ed.settings.use_native_selects)
bgneal@312 13958 c = new tinymce.ui.NativeListBox(id, s);
bgneal@312 13959 else {
bgneal@312 13960 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
bgneal@442 13961 c = new cls(id, s, ed);
bgneal@312 13962 }
bgneal@312 13963
bgneal@312 13964 t.controls[id] = c;
bgneal@312 13965
bgneal@312 13966 // Fix focus problem in Safari
bgneal@312 13967 if (tinymce.isWebKit) {
bgneal@312 13968 c.onPostRender.add(function(c, n) {
bgneal@312 13969 // Store bookmark on mousedown
bgneal@312 13970 Event.add(n, 'mousedown', function() {
bgneal@312 13971 ed.bookmark = ed.selection.getBookmark(1);
bgneal@312 13972 });
bgneal@312 13973
bgneal@312 13974 // Restore on focus, since it might be lost
bgneal@312 13975 Event.add(n, 'focus', function() {
bgneal@312 13976 ed.selection.moveToBookmark(ed.bookmark);
bgneal@312 13977 ed.bookmark = null;
bgneal@312 13978 });
bgneal@312 13979 });
bgneal@312 13980 }
bgneal@312 13981
bgneal@312 13982 if (c.hideMenu)
bgneal@312 13983 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 13984
bgneal@312 13985 return t.add(c);
bgneal@312 13986 },
bgneal@312 13987
bgneal@312 13988 createButton : function(id, s, cc) {
bgneal@312 13989 var t = this, ed = t.editor, o, c, cls;
bgneal@312 13990
bgneal@312 13991 if (t.get(id))
bgneal@312 13992 return null;
bgneal@312 13993
bgneal@312 13994 s.title = ed.translate(s.title);
bgneal@312 13995 s.label = ed.translate(s.label);
bgneal@312 13996 s.scope = s.scope || ed;
bgneal@312 13997
bgneal@312 13998 if (!s.onclick && !s.menu_button) {
bgneal@312 13999 s.onclick = function() {
bgneal@312 14000 ed.execCommand(s.cmd, s.ui || false, s.value);
bgneal@312 14001 };
bgneal@312 14002 }
bgneal@312 14003
bgneal@312 14004 s = extend({
bgneal@312 14005 title : s.title,
bgneal@312 14006 'class' : 'mce_' + id,
bgneal@312 14007 unavailable_prefix : ed.getLang('unavailable', ''),
bgneal@312 14008 scope : s.scope,
bgneal@312 14009 control_manager : t
bgneal@312 14010 }, s);
bgneal@312 14011
bgneal@312 14012 id = t.prefix + id;
bgneal@312 14013
bgneal@312 14014 if (s.menu_button) {
bgneal@312 14015 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
bgneal@442 14016 c = new cls(id, s, ed);
bgneal@312 14017 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 14018 } else {
bgneal@312 14019 cls = t._cls.button || tinymce.ui.Button;
bgneal@312 14020 c = new cls(id, s);
bgneal@312 14021 }
bgneal@312 14022
bgneal@312 14023 return t.add(c);
bgneal@312 14024 },
bgneal@312 14025
bgneal@312 14026 createMenuButton : function(id, s, cc) {
bgneal@312 14027 s = s || {};
bgneal@312 14028 s.menu_button = 1;
bgneal@312 14029
bgneal@312 14030 return this.createButton(id, s, cc);
bgneal@312 14031 },
bgneal@312 14032
bgneal@312 14033 createSplitButton : function(id, s, cc) {
bgneal@312 14034 var t = this, ed = t.editor, cmd, c, cls;
bgneal@312 14035
bgneal@312 14036 if (t.get(id))
bgneal@312 14037 return null;
bgneal@312 14038
bgneal@312 14039 s.title = ed.translate(s.title);
bgneal@312 14040 s.scope = s.scope || ed;
bgneal@312 14041
bgneal@312 14042 if (!s.onclick) {
bgneal@312 14043 s.onclick = function(v) {
bgneal@312 14044 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 14045 };
bgneal@312 14046 }
bgneal@312 14047
bgneal@312 14048 if (!s.onselect) {
bgneal@312 14049 s.onselect = function(v) {
bgneal@312 14050 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 14051 };
bgneal@312 14052 }
bgneal@312 14053
bgneal@312 14054 s = extend({
bgneal@312 14055 title : s.title,
bgneal@312 14056 'class' : 'mce_' + id,
bgneal@312 14057 scope : s.scope,
bgneal@312 14058 control_manager : t
bgneal@312 14059 }, s);
bgneal@312 14060
bgneal@312 14061 id = t.prefix + id;
bgneal@312 14062 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
bgneal@442 14063 c = t.add(new cls(id, s, ed));
bgneal@312 14064 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 14065
bgneal@312 14066 return c;
bgneal@312 14067 },
bgneal@312 14068
bgneal@312 14069 createColorSplitButton : function(id, s, cc) {
bgneal@312 14070 var t = this, ed = t.editor, cmd, c, cls, bm;
bgneal@312 14071
bgneal@312 14072 if (t.get(id))
bgneal@312 14073 return null;
bgneal@312 14074
bgneal@312 14075 s.title = ed.translate(s.title);
bgneal@312 14076 s.scope = s.scope || ed;
bgneal@312 14077
bgneal@312 14078 if (!s.onclick) {
bgneal@312 14079 s.onclick = function(v) {
bgneal@312 14080 if (tinymce.isIE)
bgneal@312 14081 bm = ed.selection.getBookmark(1);
bgneal@312 14082
bgneal@312 14083 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 14084 };
bgneal@312 14085 }
bgneal@312 14086
bgneal@312 14087 if (!s.onselect) {
bgneal@312 14088 s.onselect = function(v) {
bgneal@312 14089 ed.execCommand(s.cmd, s.ui || false, v || s.value);
bgneal@312 14090 };
bgneal@312 14091 }
bgneal@312 14092
bgneal@312 14093 s = extend({
bgneal@312 14094 title : s.title,
bgneal@312 14095 'class' : 'mce_' + id,
bgneal@312 14096 'menu_class' : ed.getParam('skin') + 'Skin',
bgneal@312 14097 scope : s.scope,
bgneal@312 14098 more_colors_title : ed.getLang('more_colors')
bgneal@312 14099 }, s);
bgneal@312 14100
bgneal@312 14101 id = t.prefix + id;
bgneal@312 14102 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
bgneal@442 14103 c = new cls(id, s, ed);
bgneal@312 14104 ed.onMouseDown.add(c.hideMenu, c);
bgneal@312 14105
bgneal@312 14106 // Remove the menu element when the editor is removed
bgneal@312 14107 ed.onRemove.add(function() {
bgneal@312 14108 c.destroy();
bgneal@312 14109 });
bgneal@312 14110
bgneal@312 14111 // Fix for bug #1897785, #1898007
bgneal@312 14112 if (tinymce.isIE) {
bgneal@312 14113 c.onShowMenu.add(function() {
bgneal@312 14114 // IE 8 needs focus in order to store away a range with the current collapsed caret location
bgneal@312 14115 ed.focus();
bgneal@312 14116 bm = ed.selection.getBookmark(1);
bgneal@312 14117 });
bgneal@312 14118
bgneal@312 14119 c.onHideMenu.add(function() {
bgneal@312 14120 if (bm) {
bgneal@312 14121 ed.selection.moveToBookmark(bm);
bgneal@312 14122 bm = 0;
bgneal@312 14123 }
bgneal@312 14124 });
bgneal@312 14125 }
bgneal@312 14126
bgneal@312 14127 return t.add(c);
bgneal@312 14128 },
bgneal@312 14129
bgneal@312 14130 createToolbar : function(id, s, cc) {
bgneal@312 14131 var c, t = this, cls;
bgneal@312 14132
bgneal@312 14133 id = t.prefix + id;
bgneal@312 14134 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
bgneal@442 14135 c = new cls(id, s, t.editor);
bgneal@312 14136
bgneal@312 14137 if (t.get(id))
bgneal@312 14138 return null;
bgneal@312 14139
bgneal@312 14140 return t.add(c);
bgneal@312 14141 },
bgneal@442 14142
bgneal@442 14143 createToolbarGroup : function(id, s, cc) {
bgneal@442 14144 var c, t = this, cls;
bgneal@442 14145 id = t.prefix + id;
bgneal@442 14146 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
bgneal@442 14147 c = new cls(id, s, t.editor);
bgneal@442 14148
bgneal@442 14149 if (t.get(id))
bgneal@442 14150 return null;
bgneal@442 14151
bgneal@442 14152 return t.add(c);
bgneal@442 14153 },
bgneal@312 14154
bgneal@312 14155 createSeparator : function(cc) {
bgneal@312 14156 var cls = cc || this._cls.separator || tinymce.ui.Separator;
bgneal@312 14157
bgneal@312 14158 return new cls();
bgneal@312 14159 },
bgneal@312 14160
bgneal@312 14161 setControlType : function(n, c) {
bgneal@312 14162 return this._cls[n.toLowerCase()] = c;
bgneal@312 14163 },
bgneal@312 14164
bgneal@312 14165 destroy : function() {
bgneal@312 14166 each(this.controls, function(c) {
bgneal@312 14167 c.destroy();
bgneal@312 14168 });
bgneal@312 14169
bgneal@312 14170 this.controls = null;
bgneal@312 14171 }
bgneal@312 14172 });
bgneal@312 14173 })(tinymce);
bgneal@312 14174
bgneal@312 14175 (function(tinymce) {
bgneal@312 14176 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
bgneal@312 14177
bgneal@312 14178 tinymce.create('tinymce.WindowManager', {
bgneal@312 14179 WindowManager : function(ed) {
bgneal@312 14180 var t = this;
bgneal@312 14181
bgneal@312 14182 t.editor = ed;
bgneal@312 14183 t.onOpen = new Dispatcher(t);
bgneal@312 14184 t.onClose = new Dispatcher(t);
bgneal@312 14185 t.params = {};
bgneal@312 14186 t.features = {};
bgneal@312 14187 },
bgneal@312 14188
bgneal@312 14189 open : function(s, p) {
bgneal@312 14190 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
bgneal@312 14191
bgneal@312 14192 // Default some options
bgneal@312 14193 s = s || {};
bgneal@312 14194 p = p || {};
bgneal@312 14195 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
bgneal@312 14196 sh = isOpera ? vp.h : screen.height;
bgneal@312 14197 s.name = s.name || 'mc_' + new Date().getTime();
bgneal@312 14198 s.width = parseInt(s.width || 320);
bgneal@312 14199 s.height = parseInt(s.height || 240);
bgneal@312 14200 s.resizable = true;
bgneal@312 14201 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
bgneal@312 14202 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
bgneal@312 14203 p.inline = false;
bgneal@312 14204 p.mce_width = s.width;
bgneal@312 14205 p.mce_height = s.height;
bgneal@312 14206 p.mce_auto_focus = s.auto_focus;
bgneal@312 14207
bgneal@312 14208 if (mo) {
bgneal@312 14209 if (isIE) {
bgneal@312 14210 s.center = true;
bgneal@312 14211 s.help = false;
bgneal@312 14212 s.dialogWidth = s.width + 'px';
bgneal@312 14213 s.dialogHeight = s.height + 'px';
bgneal@312 14214 s.scroll = s.scrollbars || false;
bgneal@312 14215 }
bgneal@312 14216 }
bgneal@312 14217
bgneal@312 14218 // Build features string
bgneal@312 14219 each(s, function(v, k) {
bgneal@312 14220 if (tinymce.is(v, 'boolean'))
bgneal@312 14221 v = v ? 'yes' : 'no';
bgneal@312 14222
bgneal@312 14223 if (!/^(name|url)$/.test(k)) {
bgneal@312 14224 if (isIE && mo)
bgneal@312 14225 f += (f ? ';' : '') + k + ':' + v;
bgneal@312 14226 else
bgneal@312 14227 f += (f ? ',' : '') + k + '=' + v;
bgneal@312 14228 }
bgneal@312 14229 });
bgneal@312 14230
bgneal@312 14231 t.features = s;
bgneal@312 14232 t.params = p;
bgneal@312 14233 t.onOpen.dispatch(t, s, p);
bgneal@312 14234
bgneal@312 14235 u = s.url || s.file;
bgneal@312 14236 u = tinymce._addVer(u);
bgneal@312 14237
bgneal@312 14238 try {
bgneal@312 14239 if (isIE && mo) {
bgneal@312 14240 w = 1;
bgneal@312 14241 window.showModalDialog(u, window, f);
bgneal@312 14242 } else
bgneal@312 14243 w = window.open(u, s.name, f);
bgneal@312 14244 } catch (ex) {
bgneal@312 14245 // Ignore
bgneal@312 14246 }
bgneal@312 14247
bgneal@312 14248 if (!w)
bgneal@312 14249 alert(t.editor.getLang('popup_blocked'));
bgneal@312 14250 },
bgneal@312 14251
bgneal@312 14252 close : function(w) {
bgneal@312 14253 w.close();
bgneal@312 14254 this.onClose.dispatch(this);
bgneal@312 14255 },
bgneal@312 14256
bgneal@312 14257 createInstance : function(cl, a, b, c, d, e) {
bgneal@312 14258 var f = tinymce.resolve(cl);
bgneal@312 14259
bgneal@312 14260 return new f(a, b, c, d, e);
bgneal@312 14261 },
bgneal@312 14262
bgneal@312 14263 confirm : function(t, cb, s, w) {
bgneal@312 14264 w = w || window;
bgneal@312 14265
bgneal@312 14266 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
bgneal@312 14267 },
bgneal@312 14268
bgneal@312 14269 alert : function(tx, cb, s, w) {
bgneal@312 14270 var t = this;
bgneal@312 14271
bgneal@312 14272 w = w || window;
bgneal@312 14273 w.alert(t._decode(t.editor.getLang(tx, tx)));
bgneal@312 14274
bgneal@312 14275 if (cb)
bgneal@312 14276 cb.call(s || t);
bgneal@312 14277 },
bgneal@312 14278
bgneal@312 14279 resizeBy : function(dw, dh, win) {
bgneal@312 14280 win.resizeBy(dw, dh);
bgneal@312 14281 },
bgneal@312 14282
bgneal@312 14283 // Internal functions
bgneal@312 14284
bgneal@312 14285 _decode : function(s) {
bgneal@312 14286 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
bgneal@312 14287 }
bgneal@312 14288 });
bgneal@312 14289 }(tinymce));
bgneal@312 14290 (function(tinymce) {
bgneal@312 14291 tinymce.Formatter = function(ed) {
bgneal@312 14292 var formats = {},
bgneal@312 14293 each = tinymce.each,
bgneal@312 14294 dom = ed.dom,
bgneal@312 14295 selection = ed.selection,
bgneal@312 14296 TreeWalker = tinymce.dom.TreeWalker,
bgneal@312 14297 rangeUtils = new tinymce.dom.RangeUtils(dom),
bgneal@442 14298 isValid = ed.schema.isValidChild,
bgneal@312 14299 isBlock = dom.isBlock,
bgneal@312 14300 forcedRootBlock = ed.settings.forced_root_block,
bgneal@312 14301 nodeIndex = dom.nodeIndex,
bgneal@312 14302 INVISIBLE_CHAR = '\uFEFF',
bgneal@312 14303 MCE_ATTR_RE = /^(src|href|style)$/,
bgneal@312 14304 FALSE = false,
bgneal@312 14305 TRUE = true,
bgneal@312 14306 undefined,
bgneal@312 14307 pendingFormats = {apply : [], remove : []};
bgneal@312 14308
bgneal@312 14309 function isArray(obj) {
bgneal@312 14310 return obj instanceof Array;
bgneal@312 14311 };
bgneal@312 14312
bgneal@312 14313 function getParents(node, selector) {
bgneal@312 14314 return dom.getParents(node, selector, dom.getRoot());
bgneal@312 14315 };
bgneal@312 14316
bgneal@312 14317 function isCaretNode(node) {
bgneal@312 14318 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
bgneal@312 14319 };
bgneal@312 14320
bgneal@312 14321 // Public functions
bgneal@312 14322
bgneal@312 14323 function get(name) {
bgneal@312 14324 return name ? formats[name] : formats;
bgneal@312 14325 };
bgneal@312 14326
bgneal@312 14327 function register(name, format) {
bgneal@312 14328 if (name) {
bgneal@312 14329 if (typeof(name) !== 'string') {
bgneal@312 14330 each(name, function(format, name) {
bgneal@312 14331 register(name, format);
bgneal@312 14332 });
bgneal@312 14333 } else {
bgneal@312 14334 // Force format into array and add it to internal collection
bgneal@312 14335 format = format.length ? format : [format];
bgneal@312 14336
bgneal@312 14337 each(format, function(format) {
bgneal@312 14338 // Set deep to false by default on selector formats this to avoid removing
bgneal@312 14339 // alignment on images inside paragraphs when alignment is changed on paragraphs
bgneal@312 14340 if (format.deep === undefined)
bgneal@312 14341 format.deep = !format.selector;
bgneal@312 14342
bgneal@312 14343 // Default to true
bgneal@312 14344 if (format.split === undefined)
bgneal@312 14345 format.split = !format.selector || format.inline;
bgneal@312 14346
bgneal@312 14347 // Default to true
bgneal@312 14348 if (format.remove === undefined && format.selector && !format.inline)
bgneal@312 14349 format.remove = 'none';
bgneal@312 14350
bgneal@312 14351 // Mark format as a mixed format inline + block level
bgneal@312 14352 if (format.selector && format.inline) {
bgneal@312 14353 format.mixed = true;
bgneal@312 14354 format.block_expand = true;
bgneal@312 14355 }
bgneal@312 14356
bgneal@312 14357 // Split classes if needed
bgneal@312 14358 if (typeof(format.classes) === 'string')
bgneal@312 14359 format.classes = format.classes.split(/\s+/);
bgneal@312 14360 });
bgneal@312 14361
bgneal@312 14362 formats[name] = format;
bgneal@312 14363 }
bgneal@312 14364 }
bgneal@312 14365 };
bgneal@312 14366
bgneal@442 14367 var getTextDecoration = function(node) {
bgneal@442 14368 var decoration;
bgneal@442 14369
bgneal@442 14370 ed.dom.getParent(node, function(n) {
bgneal@442 14371 decoration = ed.dom.getStyle(n, 'text-decoration');
bgneal@442 14372 return decoration && decoration !== 'none';
bgneal@442 14373 });
bgneal@442 14374
bgneal@442 14375 return decoration;
bgneal@442 14376 };
bgneal@442 14377
bgneal@442 14378 var processUnderlineAndColor = function(node) {
bgneal@442 14379 var textDecoration;
bgneal@442 14380 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
bgneal@442 14381 textDecoration = getTextDecoration(node.parentNode);
bgneal@442 14382 if (ed.dom.getStyle(node, 'color') && textDecoration) {
bgneal@442 14383 ed.dom.setStyle(node, 'text-decoration', textDecoration);
bgneal@442 14384 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
bgneal@442 14385 ed.dom.setStyle(node, 'text-decoration', null);
bgneal@442 14386 }
bgneal@442 14387 }
bgneal@442 14388 };
bgneal@442 14389
bgneal@312 14390 function apply(name, vars, node) {
bgneal@442 14391 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
bgneal@312 14392
bgneal@312 14393 function moveStart(rng) {
bgneal@312 14394 var container = rng.startContainer,
bgneal@312 14395 offset = rng.startOffset,
bgneal@312 14396 walker, node;
bgneal@312 14397
bgneal@312 14398 // Move startContainer/startOffset in to a suitable node
bgneal@312 14399 if (container.nodeType == 1 || container.nodeValue === "") {
bgneal@312 14400 container = container.nodeType == 1 ? container.childNodes[offset] : container;
bgneal@312 14401
bgneal@312 14402 // Might fail if the offset is behind the last element in it's container
bgneal@312 14403 if (container) {
bgneal@312 14404 walker = new TreeWalker(container, container.parentNode);
bgneal@312 14405 for (node = walker.current(); node; node = walker.next()) {
bgneal@312 14406 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@312 14407 rng.setStart(node, 0);
bgneal@312 14408 break;
bgneal@312 14409 }
bgneal@312 14410 }
bgneal@312 14411 }
bgneal@312 14412 }
bgneal@312 14413
bgneal@312 14414 return rng;
bgneal@312 14415 };
bgneal@312 14416
bgneal@312 14417 function setElementFormat(elm, fmt) {
bgneal@312 14418 fmt = fmt || format;
bgneal@312 14419
bgneal@312 14420 if (elm) {
bgneal@312 14421 each(fmt.styles, function(value, name) {
bgneal@312 14422 dom.setStyle(elm, name, replaceVars(value, vars));
bgneal@312 14423 });
bgneal@312 14424
bgneal@312 14425 each(fmt.attributes, function(value, name) {
bgneal@312 14426 dom.setAttrib(elm, name, replaceVars(value, vars));
bgneal@312 14427 });
bgneal@312 14428
bgneal@312 14429 each(fmt.classes, function(value) {
bgneal@312 14430 value = replaceVars(value, vars);
bgneal@312 14431
bgneal@312 14432 if (!dom.hasClass(elm, value))
bgneal@312 14433 dom.addClass(elm, value);
bgneal@312 14434 });
bgneal@312 14435 }
bgneal@312 14436 };
bgneal@312 14437
bgneal@312 14438 function applyRngStyle(rng) {
bgneal@312 14439 var newWrappers = [], wrapName, wrapElm;
bgneal@312 14440
bgneal@312 14441 // Setup wrapper element
bgneal@312 14442 wrapName = format.inline || format.block;
bgneal@312 14443 wrapElm = dom.create(wrapName);
bgneal@312 14444 setElementFormat(wrapElm);
bgneal@312 14445
bgneal@312 14446 rangeUtils.walk(rng, function(nodes) {
bgneal@312 14447 var currentWrapElm;
bgneal@312 14448
bgneal@312 14449 function process(node) {
bgneal@312 14450 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
bgneal@312 14451
bgneal@312 14452 // Stop wrapping on br elements
bgneal@312 14453 if (isEq(nodeName, 'br')) {
bgneal@312 14454 currentWrapElm = 0;
bgneal@312 14455
bgneal@312 14456 // Remove any br elements when we wrap things
bgneal@312 14457 if (format.block)
bgneal@312 14458 dom.remove(node);
bgneal@312 14459
bgneal@312 14460 return;
bgneal@312 14461 }
bgneal@312 14462
bgneal@312 14463 // If node is wrapper type
bgneal@312 14464 if (format.wrapper && matchNode(node, name, vars)) {
bgneal@312 14465 currentWrapElm = 0;
bgneal@312 14466 return;
bgneal@312 14467 }
bgneal@312 14468
bgneal@312 14469 // Can we rename the block
bgneal@312 14470 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
bgneal@312 14471 node = dom.rename(node, wrapName);
bgneal@312 14472 setElementFormat(node);
bgneal@312 14473 newWrappers.push(node);
bgneal@312 14474 currentWrapElm = 0;
bgneal@312 14475 return;
bgneal@312 14476 }
bgneal@312 14477
bgneal@312 14478 // Handle selector patterns
bgneal@312 14479 if (format.selector) {
bgneal@312 14480 // Look for matching formats
bgneal@312 14481 each(formatList, function(format) {
bgneal@442 14482 // Check collapsed state if it exists
bgneal@442 14483 if ('collapsed' in format && format.collapsed !== isCollapsed) {
bgneal@442 14484 return;
bgneal@442 14485 }
bgneal@442 14486
bgneal@312 14487 if (dom.is(node, format.selector) && !isCaretNode(node)) {
bgneal@312 14488 setElementFormat(node, format);
bgneal@312 14489 found = true;
bgneal@312 14490 }
bgneal@312 14491 });
bgneal@312 14492
bgneal@312 14493 // Continue processing if a selector match wasn't found and a inline element is defined
bgneal@312 14494 if (!format.inline || found) {
bgneal@312 14495 currentWrapElm = 0;
bgneal@312 14496 return;
bgneal@312 14497 }
bgneal@312 14498 }
bgneal@312 14499
bgneal@312 14500 // Is it valid to wrap this item
bgneal@442 14501 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
bgneal@442 14502 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
bgneal@312 14503 // Start wrapping
bgneal@312 14504 if (!currentWrapElm) {
bgneal@312 14505 // Wrap the node
bgneal@312 14506 currentWrapElm = wrapElm.cloneNode(FALSE);
bgneal@312 14507 node.parentNode.insertBefore(currentWrapElm, node);
bgneal@312 14508 newWrappers.push(currentWrapElm);
bgneal@312 14509 }
bgneal@312 14510
bgneal@312 14511 currentWrapElm.appendChild(node);
bgneal@312 14512 } else {
bgneal@312 14513 // Start a new wrapper for possible children
bgneal@312 14514 currentWrapElm = 0;
bgneal@312 14515
bgneal@312 14516 each(tinymce.grep(node.childNodes), process);
bgneal@312 14517
bgneal@312 14518 // End the last wrapper
bgneal@312 14519 currentWrapElm = 0;
bgneal@312 14520 }
bgneal@312 14521 };
bgneal@312 14522
bgneal@312 14523 // Process siblings from range
bgneal@312 14524 each(nodes, process);
bgneal@312 14525 });
bgneal@312 14526
bgneal@442 14527 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
bgneal@442 14528 if (format.wrap_links === false) {
bgneal@442 14529 each(newWrappers, function(node) {
bgneal@442 14530 function process(node) {
bgneal@442 14531 var i, currentWrapElm, children;
bgneal@442 14532
bgneal@442 14533 if (node.nodeName === 'A') {
bgneal@442 14534 currentWrapElm = wrapElm.cloneNode(FALSE);
bgneal@442 14535 newWrappers.push(currentWrapElm);
bgneal@442 14536
bgneal@442 14537 children = tinymce.grep(node.childNodes);
bgneal@442 14538 for (i = 0; i < children.length; i++)
bgneal@442 14539 currentWrapElm.appendChild(children[i]);
bgneal@442 14540
bgneal@442 14541 node.appendChild(currentWrapElm);
bgneal@442 14542 }
bgneal@442 14543
bgneal@442 14544 each(tinymce.grep(node.childNodes), process);
bgneal@442 14545 };
bgneal@442 14546
bgneal@442 14547 process(node);
bgneal@442 14548 });
bgneal@442 14549 }
bgneal@442 14550
bgneal@312 14551 // Cleanup
bgneal@312 14552 each(newWrappers, function(node) {
bgneal@312 14553 var childCount;
bgneal@312 14554
bgneal@312 14555 function getChildCount(node) {
bgneal@312 14556 var count = 0;
bgneal@312 14557
bgneal@312 14558 each(node.childNodes, function(node) {
bgneal@312 14559 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
bgneal@312 14560 count++;
bgneal@312 14561 });
bgneal@312 14562
bgneal@312 14563 return count;
bgneal@312 14564 };
bgneal@312 14565
bgneal@312 14566 function mergeStyles(node) {
bgneal@312 14567 var child, clone;
bgneal@312 14568
bgneal@312 14569 each(node.childNodes, function(node) {
bgneal@312 14570 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
bgneal@312 14571 child = node;
bgneal@312 14572 return FALSE; // break loop
bgneal@312 14573 }
bgneal@312 14574 });
bgneal@312 14575
bgneal@312 14576 // If child was found and of the same type as the current node
bgneal@312 14577 if (child && matchName(child, format)) {
bgneal@312 14578 clone = child.cloneNode(FALSE);
bgneal@312 14579 setElementFormat(clone);
bgneal@312 14580
bgneal@312 14581 dom.replace(clone, node, TRUE);
bgneal@312 14582 dom.remove(child, 1);
bgneal@312 14583 }
bgneal@312 14584
bgneal@312 14585 return clone || node;
bgneal@312 14586 };
bgneal@312 14587
bgneal@312 14588 childCount = getChildCount(node);
bgneal@312 14589
bgneal@442 14590 // Remove empty nodes but only if there is multiple wrappers and they are not block
bgneal@442 14591 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
bgneal@442 14592 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
bgneal@312 14593 dom.remove(node, 1);
bgneal@312 14594 return;
bgneal@312 14595 }
bgneal@312 14596
bgneal@312 14597 if (format.inline || format.wrapper) {
bgneal@312 14598 // Merges the current node with it's children of similar type to reduce the number of elements
bgneal@312 14599 if (!format.exact && childCount === 1)
bgneal@312 14600 node = mergeStyles(node);
bgneal@312 14601
bgneal@312 14602 // Remove/merge children
bgneal@312 14603 each(formatList, function(format) {
bgneal@312 14604 // Merge all children of similar type will move styles from child to parent
bgneal@312 14605 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
bgneal@312 14606 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
bgneal@312 14607 each(dom.select(format.inline, node), function(child) {
bgneal@442 14608 var parent;
bgneal@442 14609
bgneal@442 14610 // When wrap_links is set to false we don't want
bgneal@442 14611 // to remove the format on children within links
bgneal@442 14612 if (format.wrap_links === false) {
bgneal@442 14613 parent = child.parentNode;
bgneal@442 14614
bgneal@442 14615 do {
bgneal@442 14616 if (parent.nodeName === 'A')
bgneal@442 14617 return;
bgneal@442 14618 } while (parent = parent.parentNode);
bgneal@442 14619 }
bgneal@442 14620
bgneal@312 14621 removeFormat(format, vars, child, format.exact ? child : null);
bgneal@312 14622 });
bgneal@312 14623 });
bgneal@312 14624
bgneal@312 14625 // Remove child if direct parent is of same type
bgneal@312 14626 if (matchNode(node.parentNode, name, vars)) {
bgneal@312 14627 dom.remove(node, 1);
bgneal@312 14628 node = 0;
bgneal@312 14629 return TRUE;
bgneal@312 14630 }
bgneal@312 14631
bgneal@312 14632 // Look for parent with similar style format
bgneal@312 14633 if (format.merge_with_parents) {
bgneal@312 14634 dom.getParent(node.parentNode, function(parent) {
bgneal@312 14635 if (matchNode(parent, name, vars)) {
bgneal@312 14636 dom.remove(node, 1);
bgneal@312 14637 node = 0;
bgneal@312 14638 return TRUE;
bgneal@312 14639 }
bgneal@312 14640 });
bgneal@312 14641 }
bgneal@312 14642
bgneal@312 14643 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
bgneal@312 14644 if (node) {
bgneal@312 14645 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
bgneal@312 14646 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
bgneal@312 14647 }
bgneal@312 14648 }
bgneal@312 14649 });
bgneal@312 14650 };
bgneal@312 14651
bgneal@312 14652 if (format) {
bgneal@312 14653 if (node) {
bgneal@312 14654 rng = dom.createRng();
bgneal@312 14655
bgneal@312 14656 rng.setStartBefore(node);
bgneal@312 14657 rng.setEndAfter(node);
bgneal@312 14658
bgneal@312 14659 applyRngStyle(expandRng(rng, formatList));
bgneal@312 14660 } else {
bgneal@442 14661 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
bgneal@442 14662 // Obtain selection node before selection is unselected by applyRngStyle()
bgneal@442 14663 var curSelNode = ed.selection.getNode();
bgneal@442 14664
bgneal@312 14665 // Apply formatting to selection
bgneal@312 14666 bookmark = selection.getBookmark();
bgneal@312 14667 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
bgneal@312 14668
bgneal@442 14669 // Colored nodes should be underlined so that the color of the underline matches the text color.
bgneal@442 14670 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
bgneal@442 14671 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
bgneal@442 14672 processUnderlineAndColor(curSelNode);
bgneal@442 14673 }
bgneal@442 14674
bgneal@312 14675 selection.moveToBookmark(bookmark);
bgneal@312 14676 selection.setRng(moveStart(selection.getRng(TRUE)));
bgneal@312 14677 ed.nodeChanged();
bgneal@312 14678 } else
bgneal@312 14679 performCaretAction('apply', name, vars);
bgneal@312 14680 }
bgneal@312 14681 }
bgneal@312 14682 };
bgneal@312 14683
bgneal@312 14684 function remove(name, vars, node) {
bgneal@312 14685 var formatList = get(name), format = formatList[0], bookmark, i, rng;
bgneal@312 14686
bgneal@312 14687 function moveStart(rng) {
bgneal@312 14688 var container = rng.startContainer,
bgneal@312 14689 offset = rng.startOffset,
bgneal@312 14690 walker, node, nodes, tmpNode;
bgneal@312 14691
bgneal@312 14692 // Convert text node into index if possible
bgneal@312 14693 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
bgneal@312 14694 container = container.parentNode;
bgneal@312 14695 offset = nodeIndex(container) + 1;
bgneal@312 14696 }
bgneal@312 14697
bgneal@312 14698 // Move startContainer/startOffset in to a suitable node
bgneal@312 14699 if (container.nodeType == 1) {
bgneal@312 14700 nodes = container.childNodes;
bgneal@312 14701 container = nodes[Math.min(offset, nodes.length - 1)];
bgneal@312 14702 walker = new TreeWalker(container);
bgneal@312 14703
bgneal@312 14704 // If offset is at end of the parent node walk to the next one
bgneal@312 14705 if (offset > nodes.length - 1)
bgneal@312 14706 walker.next();
bgneal@312 14707
bgneal@312 14708 for (node = walker.current(); node; node = walker.next()) {
bgneal@312 14709 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
bgneal@312 14710 // IE has a "neat" feature where it moves the start node into the closest element
bgneal@312 14711 // we can avoid this by inserting an element before it and then remove it after we set the selection
bgneal@312 14712 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
bgneal@312 14713 node.parentNode.insertBefore(tmpNode, node);
bgneal@312 14714
bgneal@312 14715 // Set selection and remove tmpNode
bgneal@312 14716 rng.setStart(node, 0);
bgneal@312 14717 selection.setRng(rng);
bgneal@312 14718 dom.remove(tmpNode);
bgneal@312 14719
bgneal@312 14720 return;
bgneal@312 14721 }
bgneal@312 14722 }
bgneal@312 14723 }
bgneal@312 14724 };
bgneal@312 14725
bgneal@312 14726 // Merges the styles for each node
bgneal@312 14727 function process(node) {
bgneal@312 14728 var children, i, l;
bgneal@312 14729
bgneal@312 14730 // Grab the children first since the nodelist might be changed
bgneal@312 14731 children = tinymce.grep(node.childNodes);
bgneal@312 14732
bgneal@312 14733 // Process current node
bgneal@312 14734 for (i = 0, l = formatList.length; i < l; i++) {
bgneal@312 14735 if (removeFormat(formatList[i], vars, node, node))
bgneal@312 14736 break;
bgneal@312 14737 }
bgneal@312 14738
bgneal@312 14739 // Process the children
bgneal@312 14740 if (format.deep) {
bgneal@312 14741 for (i = 0, l = children.length; i < l; i++)
bgneal@312 14742 process(children[i]);
bgneal@312 14743 }
bgneal@312 14744 };
bgneal@312 14745
bgneal@312 14746 function findFormatRoot(container) {
bgneal@312 14747 var formatRoot;
bgneal@312 14748
bgneal@312 14749 // Find format root
bgneal@312 14750 each(getParents(container.parentNode).reverse(), function(parent) {
bgneal@312 14751 var format;
bgneal@312 14752
bgneal@312 14753 // Find format root element
bgneal@312 14754 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
bgneal@312 14755 // Is the node matching the format we are looking for
bgneal@312 14756 format = matchNode(parent, name, vars);
bgneal@312 14757 if (format && format.split !== false)
bgneal@312 14758 formatRoot = parent;
bgneal@312 14759 }
bgneal@312 14760 });
bgneal@312 14761
bgneal@312 14762 return formatRoot;
bgneal@312 14763 };
bgneal@312 14764
bgneal@312 14765 function wrapAndSplit(format_root, container, target, split) {
bgneal@312 14766 var parent, clone, lastClone, firstClone, i, formatRootParent;
bgneal@312 14767
bgneal@312 14768 // Format root found then clone formats and split it
bgneal@312 14769 if (format_root) {
bgneal@312 14770 formatRootParent = format_root.parentNode;
bgneal@312 14771
bgneal@312 14772 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
bgneal@312 14773 clone = parent.cloneNode(FALSE);
bgneal@312 14774
bgneal@312 14775 for (i = 0; i < formatList.length; i++) {
bgneal@312 14776 if (removeFormat(formatList[i], vars, clone, clone)) {
bgneal@312 14777 clone = 0;
bgneal@312 14778 break;
bgneal@312 14779 }
bgneal@312 14780 }
bgneal@312 14781
bgneal@312 14782 // Build wrapper node
bgneal@312 14783 if (clone) {
bgneal@312 14784 if (lastClone)
bgneal@312 14785 clone.appendChild(lastClone);
bgneal@312 14786
bgneal@312 14787 if (!firstClone)
bgneal@312 14788 firstClone = clone;
bgneal@312 14789
bgneal@312 14790 lastClone = clone;
bgneal@312 14791 }
bgneal@312 14792 }
bgneal@312 14793
bgneal@312 14794 // Never split block elements if the format is mixed
bgneal@312 14795 if (split && (!format.mixed || !isBlock(format_root)))
bgneal@312 14796 container = dom.split(format_root, container);
bgneal@312 14797
bgneal@312 14798 // Wrap container in cloned formats
bgneal@312 14799 if (lastClone) {
bgneal@312 14800 target.parentNode.insertBefore(lastClone, target);
bgneal@312 14801 firstClone.appendChild(target);
bgneal@312 14802 }
bgneal@312 14803 }
bgneal@312 14804
bgneal@312 14805 return container;
bgneal@312 14806 };
bgneal@312 14807
bgneal@312 14808 function splitToFormatRoot(container) {
bgneal@312 14809 return wrapAndSplit(findFormatRoot(container), container, container, true);
bgneal@312 14810 };
bgneal@312 14811
bgneal@312 14812 function unwrap(start) {
bgneal@312 14813 var node = dom.get(start ? '_start' : '_end'),
bgneal@312 14814 out = node[start ? 'firstChild' : 'lastChild'];
bgneal@312 14815
bgneal@312 14816 // If the end is placed within the start the result will be removed
bgneal@312 14817 // So this checks if the out node is a bookmark node if it is it
bgneal@312 14818 // checks for another more suitable node
bgneal@312 14819 if (isBookmarkNode(out))
bgneal@312 14820 out = out[start ? 'firstChild' : 'lastChild'];
bgneal@312 14821
bgneal@312 14822 dom.remove(node, true);
bgneal@312 14823
bgneal@312 14824 return out;
bgneal@312 14825 };
bgneal@312 14826
bgneal@312 14827 function removeRngStyle(rng) {
bgneal@312 14828 var startContainer, endContainer;
bgneal@312 14829
bgneal@312 14830 rng = expandRng(rng, formatList, TRUE);
bgneal@312 14831
bgneal@312 14832 if (format.split) {
bgneal@312 14833 startContainer = getContainer(rng, TRUE);
bgneal@312 14834 endContainer = getContainer(rng);
bgneal@312 14835
bgneal@312 14836 if (startContainer != endContainer) {
bgneal@312 14837 // Wrap start/end nodes in span element since these might be cloned/moved
bgneal@442 14838 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
bgneal@442 14839 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
bgneal@312 14840
bgneal@312 14841 // Split start/end
bgneal@312 14842 splitToFormatRoot(startContainer);
bgneal@312 14843 splitToFormatRoot(endContainer);
bgneal@312 14844
bgneal@312 14845 // Unwrap start/end to get real elements again
bgneal@312 14846 startContainer = unwrap(TRUE);
bgneal@312 14847 endContainer = unwrap();
bgneal@312 14848 } else
bgneal@312 14849 startContainer = endContainer = splitToFormatRoot(startContainer);
bgneal@312 14850
bgneal@312 14851 // Update range positions since they might have changed after the split operations
bgneal@312 14852 rng.startContainer = startContainer.parentNode;
bgneal@312 14853 rng.startOffset = nodeIndex(startContainer);
bgneal@312 14854 rng.endContainer = endContainer.parentNode;
bgneal@312 14855 rng.endOffset = nodeIndex(endContainer) + 1;
bgneal@312 14856 }
bgneal@312 14857
bgneal@312 14858 // Remove items between start/end
bgneal@312 14859 rangeUtils.walk(rng, function(nodes) {
bgneal@312 14860 each(nodes, function(node) {
bgneal@312 14861 process(node);
bgneal@442 14862
bgneal@442 14863 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
bgneal@442 14864 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
bgneal@442 14865 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
bgneal@442 14866 }
bgneal@312 14867 });
bgneal@312 14868 });
bgneal@312 14869 };
bgneal@312 14870
bgneal@312 14871 // Handle node
bgneal@312 14872 if (node) {
bgneal@312 14873 rng = dom.createRng();
bgneal@312 14874 rng.setStartBefore(node);
bgneal@312 14875 rng.setEndAfter(node);
bgneal@312 14876 removeRngStyle(rng);
bgneal@312 14877 return;
bgneal@312 14878 }
bgneal@312 14879
bgneal@442 14880 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
bgneal@312 14881 bookmark = selection.getBookmark();
bgneal@312 14882 removeRngStyle(selection.getRng(TRUE));
bgneal@312 14883 selection.moveToBookmark(bookmark);
bgneal@312 14884
bgneal@312 14885 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
bgneal@312 14886 if (match(name, vars, selection.getStart())) {
bgneal@312 14887 moveStart(selection.getRng(true));
bgneal@312 14888 }
bgneal@312 14889
bgneal@312 14890 ed.nodeChanged();
bgneal@312 14891 } else
bgneal@312 14892 performCaretAction('remove', name, vars);
bgneal@312 14893 };
bgneal@312 14894
bgneal@312 14895 function toggle(name, vars, node) {
bgneal@442 14896 var fmt = get(name);
bgneal@442 14897
bgneal@442 14898 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
bgneal@312 14899 remove(name, vars, node);
bgneal@312 14900 else
bgneal@312 14901 apply(name, vars, node);
bgneal@312 14902 };
bgneal@312 14903
bgneal@312 14904 function matchNode(node, name, vars, similar) {
bgneal@312 14905 var formatList = get(name), format, i, classes;
bgneal@312 14906
bgneal@312 14907 function matchItems(node, format, item_name) {
bgneal@312 14908 var key, value, items = format[item_name], i;
bgneal@312 14909
bgneal@312 14910 // Check all items
bgneal@312 14911 if (items) {
bgneal@312 14912 // Non indexed object
bgneal@312 14913 if (items.length === undefined) {
bgneal@312 14914 for (key in items) {
bgneal@312 14915 if (items.hasOwnProperty(key)) {
bgneal@312 14916 if (item_name === 'attributes')
bgneal@312 14917 value = dom.getAttrib(node, key);
bgneal@312 14918 else
bgneal@312 14919 value = getStyle(node, key);
bgneal@312 14920
bgneal@312 14921 if (similar && !value && !format.exact)
bgneal@312 14922 return;
bgneal@312 14923
bgneal@312 14924 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
bgneal@312 14925 return;
bgneal@312 14926 }
bgneal@312 14927 }
bgneal@312 14928 } else {
bgneal@312 14929 // Only one match needed for indexed arrays
bgneal@312 14930 for (i = 0; i < items.length; i++) {
bgneal@312 14931 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
bgneal@312 14932 return format;
bgneal@312 14933 }
bgneal@312 14934 }
bgneal@312 14935 }
bgneal@312 14936
bgneal@312 14937 return format;
bgneal@312 14938 };
bgneal@312 14939
bgneal@312 14940 if (formatList && node) {
bgneal@312 14941 // Check each format in list
bgneal@312 14942 for (i = 0; i < formatList.length; i++) {
bgneal@312 14943 format = formatList[i];
bgneal@312 14944
bgneal@312 14945 // Name name, attributes, styles and classes
bgneal@312 14946 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
bgneal@312 14947 // Match classes
bgneal@312 14948 if (classes = format.classes) {
bgneal@312 14949 for (i = 0; i < classes.length; i++) {
bgneal@312 14950 if (!dom.hasClass(node, classes[i]))
bgneal@312 14951 return;
bgneal@312 14952 }
bgneal@312 14953 }
bgneal@312 14954
bgneal@312 14955 return format;
bgneal@312 14956 }
bgneal@312 14957 }
bgneal@312 14958 }
bgneal@312 14959 };
bgneal@312 14960
bgneal@312 14961 function match(name, vars, node) {
bgneal@312 14962 var startNode, i;
bgneal@312 14963
bgneal@312 14964 function matchParents(node) {
bgneal@312 14965 // Find first node with similar format settings
bgneal@312 14966 node = dom.getParent(node, function(node) {
bgneal@312 14967 return !!matchNode(node, name, vars, true);
bgneal@312 14968 });
bgneal@312 14969
bgneal@312 14970 // Do an exact check on the similar format element
bgneal@312 14971 return matchNode(node, name, vars);
bgneal@312 14972 };
bgneal@312 14973
bgneal@312 14974 // Check specified node
bgneal@312 14975 if (node)
bgneal@312 14976 return matchParents(node);
bgneal@312 14977
bgneal@312 14978 // Check pending formats
bgneal@312 14979 if (selection.isCollapsed()) {
bgneal@312 14980 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@312 14981 if (pendingFormats.apply[i].name == name)
bgneal@312 14982 return true;
bgneal@312 14983 }
bgneal@312 14984
bgneal@312 14985 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@312 14986 if (pendingFormats.remove[i].name == name)
bgneal@312 14987 return false;
bgneal@312 14988 }
bgneal@312 14989
bgneal@312 14990 return matchParents(selection.getNode());
bgneal@312 14991 }
bgneal@312 14992
bgneal@312 14993 // Check selected node
bgneal@312 14994 node = selection.getNode();
bgneal@312 14995 if (matchParents(node))
bgneal@312 14996 return TRUE;
bgneal@312 14997
bgneal@312 14998 // Check start node if it's different
bgneal@312 14999 startNode = selection.getStart();
bgneal@312 15000 if (startNode != node) {
bgneal@312 15001 if (matchParents(startNode))
bgneal@312 15002 return TRUE;
bgneal@312 15003 }
bgneal@312 15004
bgneal@312 15005 return FALSE;
bgneal@312 15006 };
bgneal@312 15007
bgneal@312 15008 function matchAll(names, vars) {
bgneal@312 15009 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
bgneal@312 15010
bgneal@312 15011 // If the selection is collapsed then check pending formats
bgneal@312 15012 if (selection.isCollapsed()) {
bgneal@312 15013 for (ni = 0; ni < names.length; ni++) {
bgneal@312 15014 // If the name is to be removed, then stop it from being added
bgneal@312 15015 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
bgneal@312 15016 name = names[ni];
bgneal@312 15017
bgneal@312 15018 if (pendingFormats.remove[i].name == name) {
bgneal@312 15019 checkedMap[name] = true;
bgneal@312 15020 break;
bgneal@312 15021 }
bgneal@312 15022 }
bgneal@312 15023 }
bgneal@312 15024
bgneal@312 15025 // If the format is to be applied
bgneal@312 15026 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
bgneal@312 15027 for (ni = 0; ni < names.length; ni++) {
bgneal@312 15028 name = names[ni];
bgneal@312 15029
bgneal@312 15030 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
bgneal@312 15031 checkedMap[name] = true;
bgneal@312 15032 matchedFormatNames.push(name);
bgneal@312 15033 }
bgneal@312 15034 }
bgneal@312 15035 }
bgneal@312 15036 }
bgneal@312 15037
bgneal@312 15038 // Check start of selection for formats
bgneal@312 15039 startElement = selection.getStart();
bgneal@312 15040 dom.getParent(startElement, function(node) {
bgneal@312 15041 var i, name;
bgneal@312 15042
bgneal@312 15043 for (i = 0; i < names.length; i++) {
bgneal@312 15044 name = names[i];
bgneal@312 15045
bgneal@312 15046 if (!checkedMap[name] && matchNode(node, name, vars)) {
bgneal@312 15047 checkedMap[name] = true;
bgneal@312 15048 matchedFormatNames.push(name);
bgneal@312 15049 }
bgneal@312 15050 }
bgneal@312 15051 });
bgneal@312 15052
bgneal@312 15053 return matchedFormatNames;
bgneal@312 15054 };
bgneal@312 15055
bgneal@312 15056 function canApply(name) {
bgneal@312 15057 var formatList = get(name), startNode, parents, i, x, selector;
bgneal@312 15058
bgneal@312 15059 if (formatList) {
bgneal@312 15060 startNode = selection.getStart();
bgneal@312 15061 parents = getParents(startNode);
bgneal@312 15062
bgneal@312 15063 for (x = formatList.length - 1; x >= 0; x--) {
bgneal@312 15064 selector = formatList[x].selector;
bgneal@312 15065
bgneal@312 15066 // Format is not selector based, then always return TRUE
bgneal@312 15067 if (!selector)
bgneal@312 15068 return TRUE;
bgneal@312 15069
bgneal@312 15070 for (i = parents.length - 1; i >= 0; i--) {
bgneal@312 15071 if (dom.is(parents[i], selector))
bgneal@312 15072 return TRUE;
bgneal@312 15073 }
bgneal@312 15074 }
bgneal@312 15075 }
bgneal@312 15076
bgneal@312 15077 return FALSE;
bgneal@312 15078 };
bgneal@312 15079
bgneal@312 15080 // Expose to public
bgneal@312 15081 tinymce.extend(this, {
bgneal@312 15082 get : get,
bgneal@312 15083 register : register,
bgneal@312 15084 apply : apply,
bgneal@312 15085 remove : remove,
bgneal@312 15086 toggle : toggle,
bgneal@312 15087 match : match,
bgneal@312 15088 matchAll : matchAll,
bgneal@312 15089 matchNode : matchNode,
bgneal@312 15090 canApply : canApply
bgneal@312 15091 });
bgneal@312 15092
bgneal@312 15093 // Private functions
bgneal@312 15094
bgneal@312 15095 function matchName(node, format) {
bgneal@312 15096 // Check for inline match
bgneal@312 15097 if (isEq(node, format.inline))
bgneal@312 15098 return TRUE;
bgneal@312 15099
bgneal@312 15100 // Check for block match
bgneal@312 15101 if (isEq(node, format.block))
bgneal@312 15102 return TRUE;
bgneal@312 15103
bgneal@312 15104 // Check for selector match
bgneal@312 15105 if (format.selector)
bgneal@312 15106 return dom.is(node, format.selector);
bgneal@312 15107 };
bgneal@312 15108
bgneal@312 15109 function isEq(str1, str2) {
bgneal@312 15110 str1 = str1 || '';
bgneal@312 15111 str2 = str2 || '';
bgneal@312 15112
bgneal@312 15113 str1 = '' + (str1.nodeName || str1);
bgneal@312 15114 str2 = '' + (str2.nodeName || str2);
bgneal@312 15115
bgneal@312 15116 return str1.toLowerCase() == str2.toLowerCase();
bgneal@312 15117 };
bgneal@312 15118
bgneal@312 15119 function getStyle(node, name) {
bgneal@312 15120 var styleVal = dom.getStyle(node, name);
bgneal@312 15121
bgneal@312 15122 // Force the format to hex
bgneal@312 15123 if (name == 'color' || name == 'backgroundColor')
bgneal@312 15124 styleVal = dom.toHex(styleVal);
bgneal@312 15125
bgneal@312 15126 // Opera will return bold as 700
bgneal@312 15127 if (name == 'fontWeight' && styleVal == 700)
bgneal@312 15128 styleVal = 'bold';
bgneal@312 15129
bgneal@312 15130 return '' + styleVal;
bgneal@312 15131 };
bgneal@312 15132
bgneal@312 15133 function replaceVars(value, vars) {
bgneal@312 15134 if (typeof(value) != "string")
bgneal@312 15135 value = value(vars);
bgneal@312 15136 else if (vars) {
bgneal@312 15137 value = value.replace(/%(\w+)/g, function(str, name) {
bgneal@312 15138 return vars[name] || str;
bgneal@312 15139 });
bgneal@312 15140 }
bgneal@312 15141
bgneal@312 15142 return value;
bgneal@312 15143 };
bgneal@312 15144
bgneal@312 15145 function isWhiteSpaceNode(node) {
bgneal@312 15146 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
bgneal@312 15147 };
bgneal@312 15148
bgneal@312 15149 function wrap(node, name, attrs) {
bgneal@312 15150 var wrapper = dom.create(name, attrs);
bgneal@312 15151
bgneal@312 15152 node.parentNode.insertBefore(wrapper, node);
bgneal@312 15153 wrapper.appendChild(node);
bgneal@312 15154
bgneal@312 15155 return wrapper;
bgneal@312 15156 };
bgneal@312 15157
bgneal@312 15158 function expandRng(rng, format, remove) {
bgneal@312 15159 var startContainer = rng.startContainer,
bgneal@312 15160 startOffset = rng.startOffset,
bgneal@312 15161 endContainer = rng.endContainer,
bgneal@442 15162 endOffset = rng.endOffset, sibling, lastIdx, leaf;
bgneal@312 15163
bgneal@312 15164 // This function walks up the tree if there is no siblings before/after the node
bgneal@312 15165 function findParentContainer(container, child_name, sibling_name, root) {
bgneal@312 15166 var parent, child;
bgneal@312 15167
bgneal@312 15168 root = root || dom.getRoot();
bgneal@312 15169
bgneal@312 15170 for (;;) {
bgneal@312 15171 // Check if we can move up are we at root level or body level
bgneal@312 15172 parent = container.parentNode;
bgneal@312 15173
bgneal@312 15174 // Stop expanding on block elements or root depending on format
bgneal@312 15175 if (parent == root || (!format[0].block_expand && isBlock(parent)))
bgneal@312 15176 return container;
bgneal@312 15177
bgneal@312 15178 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
bgneal@312 15179 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@312 15180 return container;
bgneal@312 15181
bgneal@312 15182 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
bgneal@312 15183 return container;
bgneal@312 15184 }
bgneal@312 15185
bgneal@312 15186 container = container.parentNode;
bgneal@312 15187 }
bgneal@312 15188
bgneal@312 15189 return container;
bgneal@312 15190 };
bgneal@312 15191
bgneal@442 15192 // This function walks down the tree to find the leaf at the selection.
bgneal@442 15193 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
bgneal@442 15194 function findLeaf(node, offset) {
bgneal@442 15195 if (offset === undefined)
bgneal@442 15196 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
bgneal@442 15197 while (node && node.hasChildNodes()) {
bgneal@442 15198 node = node.childNodes[offset];
bgneal@442 15199 if (node)
bgneal@442 15200 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
bgneal@442 15201 }
bgneal@442 15202 return { node: node, offset: offset };
bgneal@442 15203 }
bgneal@442 15204
bgneal@312 15205 // If index based start position then resolve it
bgneal@312 15206 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
bgneal@312 15207 lastIdx = startContainer.childNodes.length - 1;
bgneal@312 15208 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
bgneal@312 15209
bgneal@312 15210 if (startContainer.nodeType == 3)
bgneal@312 15211 startOffset = 0;
bgneal@312 15212 }
bgneal@312 15213
bgneal@312 15214 // If index based end position then resolve it
bgneal@312 15215 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
bgneal@312 15216 lastIdx = endContainer.childNodes.length - 1;
bgneal@312 15217 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
bgneal@312 15218
bgneal@312 15219 if (endContainer.nodeType == 3)
bgneal@312 15220 endOffset = endContainer.nodeValue.length;
bgneal@312 15221 }
bgneal@312 15222
bgneal@312 15223 // Exclude bookmark nodes if possible
bgneal@312 15224 if (isBookmarkNode(startContainer.parentNode))
bgneal@312 15225 startContainer = startContainer.parentNode;
bgneal@312 15226
bgneal@312 15227 if (isBookmarkNode(startContainer))
bgneal@312 15228 startContainer = startContainer.nextSibling || startContainer;
bgneal@312 15229
bgneal@442 15230 if (isBookmarkNode(endContainer.parentNode)) {
bgneal@442 15231 endOffset = dom.nodeIndex(endContainer);
bgneal@312 15232 endContainer = endContainer.parentNode;
bgneal@442 15233 }
bgneal@442 15234
bgneal@442 15235 if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
bgneal@442 15236 endContainer = endContainer.previousSibling;
bgneal@442 15237 endOffset = endContainer.length;
bgneal@442 15238 }
bgneal@442 15239
bgneal@442 15240 if (format[0].inline) {
bgneal@442 15241 // Avoid applying formatting to a trailing space.
bgneal@442 15242 leaf = findLeaf(endContainer, endOffset);
bgneal@442 15243 if (leaf.node) {
bgneal@442 15244 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
bgneal@442 15245 leaf = findLeaf(leaf.node.previousSibling);
bgneal@442 15246
bgneal@442 15247 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
bgneal@442 15248 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
bgneal@442 15249
bgneal@442 15250 if (leaf.offset > 1) {
bgneal@442 15251 endContainer = leaf.node;
bgneal@442 15252 endContainer.splitText(leaf.offset - 1);
bgneal@442 15253 } else if (leaf.node.previousSibling) {
bgneal@442 15254 endContainer = leaf.node.previousSibling;
bgneal@442 15255 }
bgneal@442 15256 }
bgneal@442 15257 }
bgneal@442 15258 }
bgneal@442 15259
bgneal@312 15260 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
bgneal@312 15261 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
bgneal@312 15262 // This will reduce the number of wrapper elements that needs to be created
bgneal@312 15263 // Move start point up the tree
bgneal@312 15264 if (format[0].inline || format[0].block_expand) {
bgneal@312 15265 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@312 15266 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@312 15267 }
bgneal@312 15268
bgneal@312 15269 // Expand start/end container to matching selector
bgneal@312 15270 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
bgneal@312 15271 function findSelectorEndPoint(container, sibling_name) {
bgneal@442 15272 var parents, i, y, curFormat;
bgneal@312 15273
bgneal@312 15274 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
bgneal@312 15275 container = container[sibling_name];
bgneal@312 15276
bgneal@312 15277 parents = getParents(container);
bgneal@312 15278 for (i = 0; i < parents.length; i++) {
bgneal@312 15279 for (y = 0; y < format.length; y++) {
bgneal@442 15280 curFormat = format[y];
bgneal@442 15281
bgneal@442 15282 // If collapsed state is set then skip formats that doesn't match that
bgneal@442 15283 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
bgneal@442 15284 continue;
bgneal@442 15285
bgneal@442 15286 if (dom.is(parents[i], curFormat.selector))
bgneal@312 15287 return parents[i];
bgneal@312 15288 }
bgneal@312 15289 }
bgneal@312 15290
bgneal@312 15291 return container;
bgneal@312 15292 };
bgneal@312 15293
bgneal@312 15294 // Find new startContainer/endContainer if there is better one
bgneal@312 15295 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
bgneal@312 15296 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
bgneal@312 15297 }
bgneal@312 15298
bgneal@312 15299 // Expand start/end container to matching block element or text node
bgneal@312 15300 if (format[0].block || format[0].selector) {
bgneal@312 15301 function findBlockEndPoint(container, sibling_name, sibling_name2) {
bgneal@312 15302 var node;
bgneal@312 15303
bgneal@312 15304 // Expand to block of similar type
bgneal@312 15305 if (!format[0].wrapper)
bgneal@312 15306 node = dom.getParent(container, format[0].block);
bgneal@312 15307
bgneal@312 15308 // Expand to first wrappable block element or any block element
bgneal@312 15309 if (!node)
bgneal@312 15310 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
bgneal@312 15311
bgneal@312 15312 // Exclude inner lists from wrapping
bgneal@312 15313 if (node && format[0].wrapper)
bgneal@312 15314 node = getParents(node, 'ul,ol').reverse()[0] || node;
bgneal@312 15315
bgneal@312 15316 // Didn't find a block element look for first/last wrappable element
bgneal@312 15317 if (!node) {
bgneal@312 15318 node = container;
bgneal@312 15319
bgneal@312 15320 while (node[sibling_name] && !isBlock(node[sibling_name])) {
bgneal@312 15321 node = node[sibling_name];
bgneal@312 15322
bgneal@312 15323 // Break on BR but include it will be removed later on
bgneal@312 15324 // we can't remove it now since we need to check if it can be wrapped
bgneal@312 15325 if (isEq(node, 'br'))
bgneal@312 15326 break;
bgneal@312 15327 }
bgneal@312 15328 }
bgneal@312 15329
bgneal@312 15330 return node || container;
bgneal@312 15331 };
bgneal@312 15332
bgneal@312 15333 // Find new startContainer/endContainer if there is better one
bgneal@312 15334 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
bgneal@312 15335 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
bgneal@312 15336
bgneal@312 15337 // Non block element then try to expand up the leaf
bgneal@312 15338 if (format[0].block) {
bgneal@312 15339 if (!isBlock(startContainer))
bgneal@312 15340 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
bgneal@312 15341
bgneal@312 15342 if (!isBlock(endContainer))
bgneal@312 15343 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
bgneal@312 15344 }
bgneal@312 15345 }
bgneal@312 15346
bgneal@312 15347 // Setup index for startContainer
bgneal@312 15348 if (startContainer.nodeType == 1) {
bgneal@312 15349 startOffset = nodeIndex(startContainer);
bgneal@312 15350 startContainer = startContainer.parentNode;
bgneal@312 15351 }
bgneal@312 15352
bgneal@312 15353 // Setup index for endContainer
bgneal@312 15354 if (endContainer.nodeType == 1) {
bgneal@312 15355 endOffset = nodeIndex(endContainer) + 1;
bgneal@312 15356 endContainer = endContainer.parentNode;
bgneal@312 15357 }
bgneal@312 15358
bgneal@312 15359 // Return new range like object
bgneal@312 15360 return {
bgneal@312 15361 startContainer : startContainer,
bgneal@312 15362 startOffset : startOffset,
bgneal@312 15363 endContainer : endContainer,
bgneal@312 15364 endOffset : endOffset
bgneal@312 15365 };
bgneal@312 15366 }
bgneal@312 15367
bgneal@312 15368 function removeFormat(format, vars, node, compare_node) {
bgneal@312 15369 var i, attrs, stylesModified;
bgneal@312 15370
bgneal@312 15371 // Check if node matches format
bgneal@312 15372 if (!matchName(node, format))
bgneal@312 15373 return FALSE;
bgneal@312 15374
bgneal@312 15375 // Should we compare with format attribs and styles
bgneal@312 15376 if (format.remove != 'all') {
bgneal@312 15377 // Remove styles
bgneal@312 15378 each(format.styles, function(value, name) {
bgneal@312 15379 value = replaceVars(value, vars);
bgneal@312 15380
bgneal@312 15381 // Indexed array
bgneal@312 15382 if (typeof(name) === 'number') {
bgneal@312 15383 name = value;
bgneal@312 15384 compare_node = 0;
bgneal@312 15385 }
bgneal@312 15386
bgneal@312 15387 if (!compare_node || isEq(getStyle(compare_node, name), value))
bgneal@312 15388 dom.setStyle(node, name, '');
bgneal@312 15389
bgneal@312 15390 stylesModified = 1;
bgneal@312 15391 });
bgneal@312 15392
bgneal@312 15393 // Remove style attribute if it's empty
bgneal@312 15394 if (stylesModified && dom.getAttrib(node, 'style') == '') {
bgneal@312 15395 node.removeAttribute('style');
bgneal@442 15396 node.removeAttribute('data-mce-style');
bgneal@312 15397 }
bgneal@312 15398
bgneal@312 15399 // Remove attributes
bgneal@312 15400 each(format.attributes, function(value, name) {
bgneal@312 15401 var valueOut;
bgneal@312 15402
bgneal@312 15403 value = replaceVars(value, vars);
bgneal@312 15404
bgneal@312 15405 // Indexed array
bgneal@312 15406 if (typeof(name) === 'number') {
bgneal@312 15407 name = value;
bgneal@312 15408 compare_node = 0;
bgneal@312 15409 }
bgneal@312 15410
bgneal@312 15411 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
bgneal@312 15412 // Keep internal classes
bgneal@312 15413 if (name == 'class') {
bgneal@312 15414 value = dom.getAttrib(node, name);
bgneal@312 15415 if (value) {
bgneal@312 15416 // Build new class value where everything is removed except the internal prefixed classes
bgneal@312 15417 valueOut = '';
bgneal@312 15418 each(value.split(/\s+/), function(cls) {
bgneal@312 15419 if (/mce\w+/.test(cls))
bgneal@312 15420 valueOut += (valueOut ? ' ' : '') + cls;
bgneal@312 15421 });
bgneal@312 15422
bgneal@312 15423 // We got some internal classes left
bgneal@312 15424 if (valueOut) {
bgneal@312 15425 dom.setAttrib(node, name, valueOut);
bgneal@312 15426 return;
bgneal@312 15427 }
bgneal@312 15428 }
bgneal@312 15429 }
bgneal@312 15430
bgneal@312 15431 // IE6 has a bug where the attribute doesn't get removed correctly
bgneal@312 15432 if (name == "class")
bgneal@312 15433 node.removeAttribute('className');
bgneal@312 15434
bgneal@312 15435 // Remove mce prefixed attributes
bgneal@312 15436 if (MCE_ATTR_RE.test(name))
bgneal@442 15437 node.removeAttribute('data-mce-' + name);
bgneal@312 15438
bgneal@312 15439 node.removeAttribute(name);
bgneal@312 15440 }
bgneal@312 15441 });
bgneal@312 15442
bgneal@312 15443 // Remove classes
bgneal@312 15444 each(format.classes, function(value) {
bgneal@312 15445 value = replaceVars(value, vars);
bgneal@312 15446
bgneal@312 15447 if (!compare_node || dom.hasClass(compare_node, value))
bgneal@312 15448 dom.removeClass(node, value);
bgneal@312 15449 });
bgneal@312 15450
bgneal@312 15451 // Check for non internal attributes
bgneal@312 15452 attrs = dom.getAttribs(node);
bgneal@312 15453 for (i = 0; i < attrs.length; i++) {
bgneal@312 15454 if (attrs[i].nodeName.indexOf('_') !== 0)
bgneal@312 15455 return FALSE;
bgneal@312 15456 }
bgneal@312 15457 }
bgneal@312 15458
bgneal@312 15459 // Remove the inline child if it's empty for example <b> or <span>
bgneal@312 15460 if (format.remove != 'none') {
bgneal@312 15461 removeNode(node, format);
bgneal@312 15462 return TRUE;
bgneal@312 15463 }
bgneal@312 15464 };
bgneal@312 15465
bgneal@312 15466 function removeNode(node, format) {
bgneal@312 15467 var parentNode = node.parentNode, rootBlockElm;
bgneal@312 15468
bgneal@312 15469 if (format.block) {
bgneal@312 15470 if (!forcedRootBlock) {
bgneal@312 15471 function find(node, next, inc) {
bgneal@312 15472 node = getNonWhiteSpaceSibling(node, next, inc);
bgneal@312 15473
bgneal@312 15474 return !node || (node.nodeName == 'BR' || isBlock(node));
bgneal@312 15475 };
bgneal@312 15476
bgneal@312 15477 // Append BR elements if needed before we remove the block
bgneal@312 15478 if (isBlock(node) && !isBlock(parentNode)) {
bgneal@312 15479 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
bgneal@312 15480 node.insertBefore(dom.create('br'), node.firstChild);
bgneal@312 15481
bgneal@312 15482 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
bgneal@312 15483 node.appendChild(dom.create('br'));
bgneal@312 15484 }
bgneal@312 15485 } else {
bgneal@312 15486 // Wrap the block in a forcedRootBlock if we are at the root of document
bgneal@312 15487 if (parentNode == dom.getRoot()) {
bgneal@312 15488 if (!format.list_block || !isEq(node, format.list_block)) {
bgneal@312 15489 each(tinymce.grep(node.childNodes), function(node) {
bgneal@312 15490 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
bgneal@312 15491 if (!rootBlockElm)
bgneal@312 15492 rootBlockElm = wrap(node, forcedRootBlock);
bgneal@312 15493 else
bgneal@312 15494 rootBlockElm.appendChild(node);
bgneal@312 15495 } else
bgneal@312 15496 rootBlockElm = 0;
bgneal@312 15497 });
bgneal@312 15498 }
bgneal@312 15499 }
bgneal@312 15500 }
bgneal@312 15501 }
bgneal@312 15502
bgneal@312 15503 // Never remove nodes that isn't the specified inline element if a selector is specified too
bgneal@312 15504 if (format.selector && format.inline && !isEq(format.inline, node))
bgneal@312 15505 return;
bgneal@312 15506
bgneal@312 15507 dom.remove(node, 1);
bgneal@312 15508 };
bgneal@312 15509
bgneal@312 15510 function getNonWhiteSpaceSibling(node, next, inc) {
bgneal@312 15511 if (node) {
bgneal@312 15512 next = next ? 'nextSibling' : 'previousSibling';
bgneal@312 15513
bgneal@312 15514 for (node = inc ? node : node[next]; node; node = node[next]) {
bgneal@312 15515 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
bgneal@312 15516 return node;
bgneal@312 15517 }
bgneal@312 15518 }
bgneal@312 15519 };
bgneal@312 15520
bgneal@312 15521 function isBookmarkNode(node) {
bgneal@442 15522 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
bgneal@312 15523 };
bgneal@312 15524
bgneal@312 15525 function mergeSiblings(prev, next) {
bgneal@312 15526 var marker, sibling, tmpSibling;
bgneal@312 15527
bgneal@312 15528 function compareElements(node1, node2) {
bgneal@312 15529 // Not the same name
bgneal@312 15530 if (node1.nodeName != node2.nodeName)
bgneal@312 15531 return FALSE;
bgneal@312 15532
bgneal@312 15533 function getAttribs(node) {
bgneal@312 15534 var attribs = {};
bgneal@312 15535
bgneal@312 15536 each(dom.getAttribs(node), function(attr) {
bgneal@312 15537 var name = attr.nodeName.toLowerCase();
bgneal@312 15538
bgneal@312 15539 // Don't compare internal attributes or style
bgneal@312 15540 if (name.indexOf('_') !== 0 && name !== 'style')
bgneal@312 15541 attribs[name] = dom.getAttrib(node, name);
bgneal@312 15542 });
bgneal@312 15543
bgneal@312 15544 return attribs;
bgneal@312 15545 };
bgneal@312 15546
bgneal@312 15547 function compareObjects(obj1, obj2) {
bgneal@312 15548 var value, name;
bgneal@312 15549
bgneal@312 15550 for (name in obj1) {
bgneal@312 15551 // Obj1 has item obj2 doesn't have
bgneal@312 15552 if (obj1.hasOwnProperty(name)) {
bgneal@312 15553 value = obj2[name];
bgneal@312 15554
bgneal@312 15555 // Obj2 doesn't have obj1 item
bgneal@312 15556 if (value === undefined)
bgneal@312 15557 return FALSE;
bgneal@312 15558
bgneal@312 15559 // Obj2 item has a different value
bgneal@312 15560 if (obj1[name] != value)
bgneal@312 15561 return FALSE;
bgneal@312 15562
bgneal@312 15563 // Delete similar value
bgneal@312 15564 delete obj2[name];
bgneal@312 15565 }
bgneal@312 15566 }
bgneal@312 15567
bgneal@312 15568 // Check if obj 2 has something obj 1 doesn't have
bgneal@312 15569 for (name in obj2) {
bgneal@312 15570 // Obj2 has item obj1 doesn't have
bgneal@312 15571 if (obj2.hasOwnProperty(name))
bgneal@312 15572 return FALSE;
bgneal@312 15573 }
bgneal@312 15574
bgneal@312 15575 return TRUE;
bgneal@312 15576 };
bgneal@312 15577
bgneal@312 15578 // Attribs are not the same
bgneal@312 15579 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
bgneal@312 15580 return FALSE;
bgneal@312 15581
bgneal@312 15582 // Styles are not the same
bgneal@312 15583 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
bgneal@312 15584 return FALSE;
bgneal@312 15585
bgneal@312 15586 return TRUE;
bgneal@312 15587 };
bgneal@312 15588
bgneal@312 15589 // Check if next/prev exists and that they are elements
bgneal@312 15590 if (prev && next) {
bgneal@312 15591 function findElementSibling(node, sibling_name) {
bgneal@312 15592 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
bgneal@442 15593 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
bgneal@312 15594 return node;
bgneal@312 15595
bgneal@312 15596 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
bgneal@312 15597 return sibling;
bgneal@312 15598 }
bgneal@312 15599
bgneal@312 15600 return node;
bgneal@312 15601 };
bgneal@312 15602
bgneal@312 15603 // If previous sibling is empty then jump over it
bgneal@312 15604 prev = findElementSibling(prev, 'previousSibling');
bgneal@312 15605 next = findElementSibling(next, 'nextSibling');
bgneal@312 15606
bgneal@312 15607 // Compare next and previous nodes
bgneal@312 15608 if (compareElements(prev, next)) {
bgneal@312 15609 // Append nodes between
bgneal@312 15610 for (sibling = prev.nextSibling; sibling && sibling != next;) {
bgneal@312 15611 tmpSibling = sibling;
bgneal@312 15612 sibling = sibling.nextSibling;
bgneal@312 15613 prev.appendChild(tmpSibling);
bgneal@312 15614 }
bgneal@312 15615
bgneal@312 15616 // Remove next node
bgneal@312 15617 dom.remove(next);
bgneal@312 15618
bgneal@312 15619 // Move children into prev node
bgneal@312 15620 each(tinymce.grep(next.childNodes), function(node) {
bgneal@312 15621 prev.appendChild(node);
bgneal@312 15622 });
bgneal@312 15623
bgneal@312 15624 return prev;
bgneal@312 15625 }
bgneal@312 15626 }
bgneal@312 15627
bgneal@312 15628 return next;
bgneal@312 15629 };
bgneal@312 15630
bgneal@312 15631 function isTextBlock(name) {
bgneal@312 15632 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
bgneal@312 15633 };
bgneal@312 15634
bgneal@312 15635 function getContainer(rng, start) {
bgneal@312 15636 var container, offset, lastIdx;
bgneal@312 15637
bgneal@312 15638 container = rng[start ? 'startContainer' : 'endContainer'];
bgneal@312 15639 offset = rng[start ? 'startOffset' : 'endOffset'];
bgneal@312 15640
bgneal@312 15641 if (container.nodeType == 1) {
bgneal@312 15642 lastIdx = container.childNodes.length - 1;
bgneal@312 15643
bgneal@312 15644 if (!start && offset)
bgneal@312 15645 offset--;
bgneal@312 15646
bgneal@312 15647 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
bgneal@312 15648 }
bgneal@312 15649
bgneal@312 15650 return container;
bgneal@312 15651 };
bgneal@312 15652
bgneal@312 15653 function performCaretAction(type, name, vars) {
bgneal@312 15654 var i, currentPendingFormats = pendingFormats[type],
bgneal@312 15655 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
bgneal@312 15656
bgneal@312 15657 function hasPending() {
bgneal@312 15658 return pendingFormats.apply.length || pendingFormats.remove.length;
bgneal@312 15659 };
bgneal@312 15660
bgneal@312 15661 function resetPending() {
bgneal@312 15662 pendingFormats.apply = [];
bgneal@312 15663 pendingFormats.remove = [];
bgneal@312 15664 };
bgneal@312 15665
bgneal@312 15666 function perform(caret_node) {
bgneal@312 15667 // Apply pending formats
bgneal@312 15668 each(pendingFormats.apply.reverse(), function(item) {
bgneal@312 15669 apply(item.name, item.vars, caret_node);
bgneal@442 15670
bgneal@442 15671 // Colored nodes should be underlined so that the color of the underline matches the text color.
bgneal@442 15672 if (item.name === 'forecolor' && item.vars.value)
bgneal@442 15673 processUnderlineAndColor(caret_node.parentNode);
bgneal@312 15674 });
bgneal@312 15675
bgneal@312 15676 // Remove pending formats
bgneal@312 15677 each(pendingFormats.remove.reverse(), function(item) {
bgneal@312 15678 remove(item.name, item.vars, caret_node);
bgneal@312 15679 });
bgneal@312 15680
bgneal@312 15681 dom.remove(caret_node, 1);
bgneal@312 15682 resetPending();
bgneal@312 15683 };
bgneal@312 15684
bgneal@312 15685 // Check if it already exists then ignore it
bgneal@312 15686 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
bgneal@312 15687 if (currentPendingFormats[i].name == name)
bgneal@312 15688 return;
bgneal@312 15689 }
bgneal@312 15690
bgneal@312 15691 currentPendingFormats.push({name : name, vars : vars});
bgneal@312 15692
bgneal@312 15693 // Check if it's in the other type, then remove it
bgneal@312 15694 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
bgneal@312 15695 if (otherPendingFormats[i].name == name)
bgneal@312 15696 otherPendingFormats.splice(i, 1);
bgneal@312 15697 }
bgneal@312 15698
bgneal@312 15699 // Pending apply or remove formats
bgneal@312 15700 if (hasPending()) {
bgneal@312 15701 ed.getDoc().execCommand('FontName', false, 'mceinline');
bgneal@312 15702 pendingFormats.lastRng = selection.getRng();
bgneal@312 15703
bgneal@312 15704 // IE will convert the current word
bgneal@312 15705 each(dom.select('font,span'), function(node) {
bgneal@312 15706 var bookmark;
bgneal@312 15707
bgneal@312 15708 if (isCaretNode(node)) {
bgneal@312 15709 bookmark = selection.getBookmark();
bgneal@312 15710 perform(node);
bgneal@312 15711 selection.moveToBookmark(bookmark);
bgneal@312 15712 ed.nodeChanged();
bgneal@312 15713 }
bgneal@312 15714 });
bgneal@312 15715
bgneal@312 15716 // Only register listeners once if we need to
bgneal@312 15717 if (!pendingFormats.isListening && hasPending()) {
bgneal@312 15718 pendingFormats.isListening = true;
bgneal@312 15719
bgneal@312 15720 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
bgneal@312 15721 ed[event].addToTop(function(ed, e) {
bgneal@312 15722 // Do we have pending formats and is the selection moved has moved
bgneal@312 15723 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
bgneal@312 15724 each(dom.select('font,span'), function(node) {
bgneal@312 15725 var textNode, rng;
bgneal@312 15726
bgneal@312 15727 // Look for marker
bgneal@312 15728 if (isCaretNode(node)) {
bgneal@312 15729 textNode = node.firstChild;
bgneal@312 15730
bgneal@312 15731 if (textNode) {
bgneal@312 15732 perform(node);
bgneal@312 15733
bgneal@312 15734 rng = dom.createRng();
bgneal@312 15735 rng.setStart(textNode, textNode.nodeValue.length);
bgneal@312 15736 rng.setEnd(textNode, textNode.nodeValue.length);
bgneal@312 15737 selection.setRng(rng);
bgneal@312 15738 ed.nodeChanged();
bgneal@312 15739 } else
bgneal@312 15740 dom.remove(node);
bgneal@312 15741 }
bgneal@312 15742 });
bgneal@312 15743
bgneal@312 15744 // Always unbind and clear pending styles on keyup
bgneal@312 15745 if (e.type == 'keyup' || e.type == 'mouseup')
bgneal@312 15746 resetPending();
bgneal@312 15747 }
bgneal@312 15748 });
bgneal@312 15749 });
bgneal@312 15750 }
bgneal@312 15751 }
bgneal@312 15752 };
bgneal@312 15753 };
bgneal@312 15754 })(tinymce);
bgneal@312 15755
bgneal@312 15756 tinymce.onAddEditor.add(function(tinymce, ed) {
bgneal@312 15757 var filters, fontSizes, dom, settings = ed.settings;
bgneal@312 15758
bgneal@312 15759 if (settings.inline_styles) {
bgneal@312 15760 fontSizes = tinymce.explode(settings.font_size_style_values);
bgneal@312 15761
bgneal@312 15762 function replaceWithSpan(node, styles) {
bgneal@312 15763 tinymce.each(styles, function(value, name) {
bgneal@312 15764 if (value)
bgneal@312 15765 dom.setStyle(node, name, value);
bgneal@312 15766 });
bgneal@312 15767
bgneal@312 15768 dom.rename(node, 'span');
bgneal@312 15769 };
bgneal@312 15770
bgneal@312 15771 filters = {
bgneal@312 15772 font : function(dom, node) {
bgneal@312 15773 replaceWithSpan(node, {
bgneal@312 15774 backgroundColor : node.style.backgroundColor,
bgneal@312 15775 color : node.color,
bgneal@312 15776 fontFamily : node.face,
bgneal@312 15777 fontSize : fontSizes[parseInt(node.size) - 1]
bgneal@312 15778 });
bgneal@312 15779 },
bgneal@312 15780
bgneal@312 15781 u : function(dom, node) {
bgneal@312 15782 replaceWithSpan(node, {
bgneal@312 15783 textDecoration : 'underline'
bgneal@312 15784 });
bgneal@312 15785 },
bgneal@312 15786
bgneal@312 15787 strike : function(dom, node) {
bgneal@312 15788 replaceWithSpan(node, {
bgneal@312 15789 textDecoration : 'line-through'
bgneal@312 15790 });
bgneal@312 15791 }
bgneal@312 15792 };
bgneal@312 15793
bgneal@312 15794 function convert(editor, params) {
bgneal@312 15795 dom = editor.dom;
bgneal@312 15796
bgneal@312 15797 if (settings.convert_fonts_to_spans) {
bgneal@312 15798 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
bgneal@312 15799 filters[node.nodeName.toLowerCase()](ed.dom, node);
bgneal@312 15800 });
bgneal@312 15801 }
bgneal@312 15802 };
bgneal@312 15803
bgneal@312 15804 ed.onPreProcess.add(convert);
bgneal@442 15805 ed.onSetContent.add(convert);
bgneal@312 15806
bgneal@312 15807 ed.onInit.add(function() {
bgneal@312 15808 ed.selection.onSetContent.add(convert);
bgneal@312 15809 });
bgneal@312 15810 }
bgneal@312 15811 });
bgneal@312 15812