[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 2 (function(win) { 3 var whiteSpaceRe = /^\s*|\s*$/g, 4 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 5 6 var tinymce = { 7 majorVersion : '3', 8 9 minorVersion : '5.11', 10 11 releaseDate : '2014-05-08', 12 13 _init : function() { 14 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 15 16 t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1); 17 18 t.isOpera = win.opera && opera.buildNumber; 19 20 t.isWebKit = /WebKit/.test(ua); 21 22 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11; 23 24 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 25 26 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 27 28 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 29 30 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 31 32 t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua); 33 34 t.isMac = ua.indexOf('Mac') != -1; 35 36 t.isAir = /adobeair/i.test(ua); 37 38 t.isIDevice = /(iPad|iPhone)/.test(ua); 39 40 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 41 42 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 43 if (win.tinyMCEPreInit) { 44 t.suffix = tinyMCEPreInit.suffix; 45 t.baseURL = tinyMCEPreInit.base; 46 t.query = tinyMCEPreInit.query; 47 return; 48 } 49 50 // Get suffix and base 51 t.suffix = ''; 52 53 // If base element found, add that infront of baseURL 54 nl = d.getElementsByTagName('base'); 55 for (i=0; i<nl.length; i++) { 56 v = nl[i].href; 57 if (v) { 58 // Host only value like http://site.com or http://site.com:8008 59 if (/^https?:\/\/[^\/]+$/.test(v)) 60 v += '/'; 61 62 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 63 } 64 } 65 66 function getBase(n) { 67 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 68 if (/_(src|dev)\.js/g.test(n.src)) 69 t.suffix = '_src'; 70 71 if ((p = n.src.indexOf('?')) != -1) 72 t.query = n.src.substring(p + 1); 73 74 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 75 76 // If path to script is relative and a base href was found add that one infront 77 // the src property will always be an absolute one on non IE browsers and IE 8 78 // so this logic will basically only be executed on older IE versions 79 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 80 t.baseURL = base + t.baseURL; 81 82 return t.baseURL; 83 } 84 85 return null; 86 }; 87 88 // Check document 89 nl = d.getElementsByTagName('script'); 90 for (i=0; i<nl.length; i++) { 91 if (getBase(nl[i])) 92 return; 93 } 94 95 // Check head 96 n = d.getElementsByTagName('head')[0]; 97 if (n) { 98 nl = n.getElementsByTagName('script'); 99 for (i=0; i<nl.length; i++) { 100 if (getBase(nl[i])) 101 return; 102 } 103 } 104 105 return; 106 }, 107 108 is : function(o, t) { 109 if (!t) 110 return o !== undef; 111 112 if (t == 'array' && tinymce.isArray(o)) 113 return true; 114 115 return typeof(o) == t; 116 }, 117 118 isArray: Array.isArray || function(obj) { 119 return Object.prototype.toString.call(obj) === "[object Array]"; 120 }, 121 122 makeMap : function(items, delim, map) { 123 var i; 124 125 items = items || []; 126 delim = delim || ','; 127 128 if (typeof(items) == "string") 129 items = items.split(delim); 130 131 map = map || {}; 132 133 i = items.length; 134 while (i--) 135 map[items[i]] = {}; 136 137 return map; 138 }, 139 140 each : function(o, cb, s) { 141 var n, l; 142 143 if (!o) 144 return 0; 145 146 s = s || o; 147 148 if (o.length !== undef) { 149 // Indexed arrays, needed for Safari 150 for (n=0, l = o.length; n < l; n++) { 151 if (cb.call(s, o[n], n, o) === false) 152 return 0; 153 } 154 } else { 155 // Hashtables 156 for (n in o) { 157 if (o.hasOwnProperty(n)) { 158 if (cb.call(s, o[n], n, o) === false) 159 return 0; 160 } 161 } 162 } 163 164 return 1; 165 }, 166 167 168 map : function(a, f) { 169 var o = []; 170 171 tinymce.each(a, function(v) { 172 o.push(f(v)); 173 }); 174 175 return o; 176 }, 177 178 grep : function(a, f) { 179 var o = []; 180 181 tinymce.each(a, function(v) { 182 if (!f || f(v)) 183 o.push(v); 184 }); 185 186 return o; 187 }, 188 189 inArray : function(a, v) { 190 var i, l; 191 192 if (a) { 193 for (i = 0, l = a.length; i < l; i++) { 194 if (a[i] === v) 195 return i; 196 } 197 } 198 199 return -1; 200 }, 201 202 extend : function(obj, ext) { 203 var i, l, name, args = arguments, value; 204 205 for (i = 1, l = args.length; i < l; i++) { 206 ext = args[i]; 207 for (name in ext) { 208 if (ext.hasOwnProperty(name)) { 209 value = ext[name]; 210 211 if (value !== undef) { 212 obj[name] = value; 213 } 214 } 215 } 216 } 217 218 return obj; 219 }, 220 221 222 trim : function(s) { 223 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 224 }, 225 226 create : function(s, p, root) { 227 var t = this, sp, ns, cn, scn, c, de = 0; 228 229 // Parse : <prefix> <class>:<super class> 230 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 231 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 232 233 // Create namespace for new class 234 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 235 236 // Class already exists 237 if (ns[cn]) 238 return; 239 240 // Make pure static class 241 if (s[2] == 'static') { 242 ns[cn] = p; 243 244 if (this.onCreate) 245 this.onCreate(s[2], s[3], ns[cn]); 246 247 return; 248 } 249 250 // Create default constructor 251 if (!p[cn]) { 252 p[cn] = function() {}; 253 de = 1; 254 } 255 256 // Add constructor and methods 257 ns[cn] = p[cn]; 258 t.extend(ns[cn].prototype, p); 259 260 // Extend 261 if (s[5]) { 262 sp = t.resolve(s[5]).prototype; 263 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 264 265 // Extend constructor 266 c = ns[cn]; 267 if (de) { 268 // Add passthrough constructor 269 ns[cn] = function() { 270 return sp[scn].apply(this, arguments); 271 }; 272 } else { 273 // Add inherit constructor 274 ns[cn] = function() { 275 this.parent = sp[scn]; 276 return c.apply(this, arguments); 277 }; 278 } 279 ns[cn].prototype[cn] = ns[cn]; 280 281 // Add super methods 282 t.each(sp, function(f, n) { 283 ns[cn].prototype[n] = sp[n]; 284 }); 285 286 // Add overridden methods 287 t.each(p, function(f, n) { 288 // Extend methods if needed 289 if (sp[n]) { 290 ns[cn].prototype[n] = function() { 291 this.parent = sp[n]; 292 return f.apply(this, arguments); 293 }; 294 } else { 295 if (n != cn) 296 ns[cn].prototype[n] = f; 297 } 298 }); 299 } 300 301 // Add static methods 302 t.each(p['static'], function(f, n) { 303 ns[cn][n] = f; 304 }); 305 306 if (this.onCreate) 307 this.onCreate(s[2], s[3], ns[cn].prototype); 308 }, 309 310 walk : function(o, f, n, s) { 311 s = s || this; 312 313 if (o) { 314 if (n) 315 o = o[n]; 316 317 tinymce.each(o, function(o, i) { 318 if (f.call(s, o, i, n) === false) 319 return false; 320 321 tinymce.walk(o, f, n, s); 322 }); 323 } 324 }, 325 326 createNS : function(n, o) { 327 var i, v; 328 329 o = o || win; 330 331 n = n.split('.'); 332 for (i=0; i<n.length; i++) { 333 v = n[i]; 334 335 if (!o[v]) 336 o[v] = {}; 337 338 o = o[v]; 339 } 340 341 return o; 342 }, 343 344 resolve : function(n, o) { 345 var i, l; 346 347 o = o || win; 348 349 n = n.split('.'); 350 for (i = 0, l = n.length; i < l; i++) { 351 o = o[n[i]]; 352 353 if (!o) 354 break; 355 } 356 357 return o; 358 }, 359 360 addUnload : function(f, s) { 361 var t = this, unload; 362 363 unload = function() { 364 var li = t.unloads, o, n; 365 366 if (li) { 367 // Call unload handlers 368 for (n in li) { 369 o = li[n]; 370 371 if (o && o.func) 372 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 373 } 374 375 // Detach unload function 376 if (win.detachEvent) { 377 win.detachEvent('onbeforeunload', fakeUnload); 378 win.detachEvent('onunload', unload); 379 } else if (win.removeEventListener) 380 win.removeEventListener('unload', unload, false); 381 382 // Destroy references 383 t.unloads = o = li = w = unload = 0; 384 385 // Run garbarge collector on IE 386 if (win.CollectGarbage) 387 CollectGarbage(); 388 } 389 }; 390 391 function fakeUnload() { 392 var d = document; 393 394 function stop() { 395 // Prevent memory leak 396 d.detachEvent('onstop', stop); 397 398 // Call unload handler 399 if (unload) 400 unload(); 401 402 d = 0; 403 }; 404 405 // Is there things still loading, then do some magic 406 if (d.readyState == 'interactive') { 407 // Fire unload when the currently loading page is stopped 408 if (d) 409 d.attachEvent('onstop', stop); 410 411 // Remove onstop listener after a while to prevent the unload function 412 // to execute if the user presses cancel in an onbeforeunload 413 // confirm dialog and then presses the browser stop button 414 win.setTimeout(function() { 415 if (d) 416 d.detachEvent('onstop', stop); 417 }, 0); 418 } 419 }; 420 421 f = {func : f, scope : s || this}; 422 423 if (!t.unloads) { 424 // Attach unload handler 425 if (win.attachEvent) { 426 win.attachEvent('onunload', unload); 427 win.attachEvent('onbeforeunload', fakeUnload); 428 } else if (win.addEventListener) 429 win.addEventListener('unload', unload, false); 430 431 // Setup initial unload handler array 432 t.unloads = [f]; 433 } else 434 t.unloads.push(f); 435 436 return f; 437 }, 438 439 removeUnload : function(f) { 440 var u = this.unloads, r = null; 441 442 tinymce.each(u, function(o, i) { 443 if (o && o.func == f) { 444 u.splice(i, 1); 445 r = f; 446 return false; 447 } 448 }); 449 450 return r; 451 }, 452 453 explode : function(s, d) { 454 if (!s || tinymce.is(s, 'array')) { 455 return s; 456 } 457 458 return tinymce.map(s.split(d || ','), tinymce.trim); 459 }, 460 461 _addVer : function(u) { 462 var v; 463 464 if (!this.query) 465 return u; 466 467 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 468 469 if (u.indexOf('#') == -1) 470 return u + v; 471 472 return u.replace('#', v + '#'); 473 }, 474 475 // Fix function for IE 9 where regexps isn't working correctly 476 // Todo: remove me once MS fixes the bug 477 _replace : function(find, replace, str) { 478 // On IE9 we have to fake $x replacement 479 if (isRegExpBroken) { 480 return str.replace(find, function() { 481 var val = replace, args = arguments, i; 482 483 for (i = 0; i < args.length - 2; i++) { 484 if (args[i] === undef) { 485 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 486 } else { 487 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 488 } 489 } 490 491 return val; 492 }); 493 } 494 495 return str.replace(find, replace); 496 } 497 498 }; 499 500 // Initialize the API 501 tinymce._init(); 502 503 // Expose tinymce namespace to the global namespace (window) 504 win.tinymce = win.tinyMCE = tinymce; 505 506 // Describe the different namespaces 507 508 })(window); 509 tinymce.create('tinymce.util.Dispatcher', { 510 scope : null, 511 listeners : null, 512 inDispatch: false, 513 514 Dispatcher : function(scope) { 515 this.scope = scope || this; 516 this.listeners = []; 517 }, 518 519 add : function(callback, scope) { 520 this.listeners.push({cb : callback, scope : scope || this.scope}); 521 522 return callback; 523 }, 524 525 addToTop : function(callback, scope) { 526 var self = this, listener = {cb : callback, scope : scope || self.scope}; 527 528 // Create new listeners if addToTop is executed in a dispatch loop 529 if (self.inDispatch) { 530 self.listeners = [listener].concat(self.listeners); 531 } else { 532 self.listeners.unshift(listener); 533 } 534 535 return callback; 536 }, 537 538 remove : function(callback) { 539 var listeners = this.listeners, output = null; 540 541 tinymce.each(listeners, function(listener, i) { 542 if (callback == listener.cb) { 543 output = listener; 544 listeners.splice(i, 1); 545 return false; 546 } 547 }); 548 549 return output; 550 }, 551 552 dispatch : function() { 553 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 554 555 self.inDispatch = true; 556 557 // Needs to be a real loop since the listener count might change while looping 558 // And this is also more efficient 559 for (i = 0; i < listeners.length; i++) { 560 listener = listeners[i]; 561 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 562 563 if (returnValue === false) 564 break; 565 } 566 567 self.inDispatch = false; 568 569 return returnValue; 570 } 571 572 }); 573 (function() { 574 var each = tinymce.each; 575 576 tinymce.create('tinymce.util.URI', { 577 URI : function(u, s) { 578 var t = this, o, a, b, base_url; 579 580 // Trim whitespace 581 u = tinymce.trim(u); 582 583 // Default settings 584 s = t.settings = s || {}; 585 586 // Strange app protocol that isn't http/https or local anchor 587 // For example: mailto,skype,tel etc. 588 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 589 t.source = u; 590 return; 591 } 592 593 // Absolute path with no host, fake host and protocol 594 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 595 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 596 597 // Relative path http:// or protocol relative //path 598 if (!/^[\w\-]*:?\/\//.test(u)) { 599 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 600 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 601 } 602 603 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 604 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 605 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 606 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 607 var s = u[i]; 608 609 // Zope 3 workaround, they use @@something 610 if (s) 611 s = s.replace(/\(mce_at\)/g, '@@'); 612 613 t[v] = s; 614 }); 615 616 b = s.base_uri; 617 if (b) { 618 if (!t.protocol) 619 t.protocol = b.protocol; 620 621 if (!t.userInfo) 622 t.userInfo = b.userInfo; 623 624 if (!t.port && t.host === 'mce_host') 625 t.port = b.port; 626 627 if (!t.host || t.host === 'mce_host') 628 t.host = b.host; 629 630 t.source = ''; 631 } 632 633 //t.path = t.path || '/'; 634 }, 635 636 setPath : function(p) { 637 var t = this; 638 639 p = /^(.*?)\/?(\w+)?$/.exec(p); 640 641 // Update path parts 642 t.path = p[0]; 643 t.directory = p[1]; 644 t.file = p[2]; 645 646 // Rebuild source 647 t.source = ''; 648 t.getURI(); 649 }, 650 651 toRelative : function(u) { 652 var t = this, o; 653 654 if (u === "./") 655 return u; 656 657 u = new tinymce.util.URI(u, {base_uri : t}); 658 659 // Not on same domain/port or protocol 660 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 661 return u.getURI(); 662 663 var tu = t.getURI(), uu = u.getURI(); 664 665 // Allow usage of the base_uri when relative_urls = true 666 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 667 return tu; 668 669 o = t.toRelPath(t.path, u.path); 670 671 // Add query 672 if (u.query) 673 o += '?' + u.query; 674 675 // Add anchor 676 if (u.anchor) 677 o += '#' + u.anchor; 678 679 return o; 680 }, 681 682 toAbsolute : function(u, nh) { 683 u = new tinymce.util.URI(u, {base_uri : this}); 684 685 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 686 }, 687 688 toRelPath : function(base, path) { 689 var items, bp = 0, out = '', i, l; 690 691 // Split the paths 692 base = base.substring(0, base.lastIndexOf('/')); 693 base = base.split('/'); 694 items = path.split('/'); 695 696 if (base.length >= items.length) { 697 for (i = 0, l = base.length; i < l; i++) { 698 if (i >= items.length || base[i] != items[i]) { 699 bp = i + 1; 700 break; 701 } 702 } 703 } 704 705 if (base.length < items.length) { 706 for (i = 0, l = items.length; i < l; i++) { 707 if (i >= base.length || base[i] != items[i]) { 708 bp = i + 1; 709 break; 710 } 711 } 712 } 713 714 if (bp === 1) 715 return path; 716 717 for (i = 0, l = base.length - (bp - 1); i < l; i++) 718 out += "../"; 719 720 for (i = bp - 1, l = items.length; i < l; i++) { 721 if (i != bp - 1) 722 out += "/" + items[i]; 723 else 724 out += items[i]; 725 } 726 727 return out; 728 }, 729 730 toAbsPath : function(base, path) { 731 var i, nb = 0, o = [], tr, outPath; 732 733 // Split paths 734 tr = /\/$/.test(path) ? '/' : ''; 735 base = base.split('/'); 736 path = path.split('/'); 737 738 // Remove empty chunks 739 each(base, function(k) { 740 if (k) 741 o.push(k); 742 }); 743 744 base = o; 745 746 // Merge relURLParts chunks 747 for (i = path.length - 1, o = []; i >= 0; i--) { 748 // Ignore empty or . 749 if (path[i].length === 0 || path[i] === ".") 750 continue; 751 752 // Is parent 753 if (path[i] === '..') { 754 nb++; 755 continue; 756 } 757 758 // Move up 759 if (nb > 0) { 760 nb--; 761 continue; 762 } 763 764 o.push(path[i]); 765 } 766 767 i = base.length - nb; 768 769 // If /a/b/c or / 770 if (i <= 0) 771 outPath = o.reverse().join('/'); 772 else 773 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 774 775 // Add front / if it's needed 776 if (outPath.indexOf('/') !== 0) 777 outPath = '/' + outPath; 778 779 // Add traling / if it's needed 780 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 781 outPath += tr; 782 783 return outPath; 784 }, 785 786 getURI : function(nh) { 787 var s, t = this; 788 789 // Rebuild source 790 if (!t.source || nh) { 791 s = ''; 792 793 if (!nh) { 794 if (t.protocol) 795 s += t.protocol + '://'; 796 797 if (t.userInfo) 798 s += t.userInfo + '@'; 799 800 if (t.host) 801 s += t.host; 802 803 if (t.port) 804 s += ':' + t.port; 805 } 806 807 if (t.path) 808 s += t.path; 809 810 if (t.query) 811 s += '?' + t.query; 812 813 if (t.anchor) 814 s += '#' + t.anchor; 815 816 t.source = s; 817 } 818 819 return t.source; 820 } 821 }); 822 })(); 823 (function() { 824 var each = tinymce.each; 825 826 tinymce.create('static tinymce.util.Cookie', { 827 getHash : function(n) { 828 var v = this.get(n), h; 829 830 if (v) { 831 each(v.split('&'), function(v) { 832 v = v.split('='); 833 h = h || {}; 834 h[unescape(v[0])] = unescape(v[1]); 835 }); 836 } 837 838 return h; 839 }, 840 841 setHash : function(n, v, e, p, d, s) { 842 var o = ''; 843 844 each(v, function(v, k) { 845 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 846 }); 847 848 this.set(n, o, e, p, d, s); 849 }, 850 851 get : function(n) { 852 var c = document.cookie, e, p = n + "=", b; 853 854 // Strict mode 855 if (!c) 856 return; 857 858 b = c.indexOf("; " + p); 859 860 if (b == -1) { 861 b = c.indexOf(p); 862 863 if (b !== 0) 864 return null; 865 } else 866 b += 2; 867 868 e = c.indexOf(";", b); 869 870 if (e == -1) 871 e = c.length; 872 873 return unescape(c.substring(b + p.length, e)); 874 }, 875 876 set : function(n, v, e, p, d, s) { 877 document.cookie = n + "=" + escape(v) + 878 ((e) ? "; expires=" + e.toGMTString() : "") + 879 ((p) ? "; path=" + escape(p) : "") + 880 ((d) ? "; domain=" + d : "") + 881 ((s) ? "; secure" : ""); 882 }, 883 884 remove : function(name, path, domain) { 885 var date = new Date(); 886 887 date.setTime(date.getTime() - 1000); 888 889 this.set(name, '', date, path, domain); 890 } 891 }); 892 })(); 893 (function() { 894 function serialize(o, quote) { 895 var i, v, t, name; 896 897 quote = quote || '"'; 898 899 if (o == null) 900 return 'null'; 901 902 t = typeof o; 903 904 if (t == 'string') { 905 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 906 907 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 908 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 909 if (quote === '"' && a === "'") 910 return a; 911 912 i = v.indexOf(b); 913 914 if (i + 1) 915 return '\\' + v.charAt(i + 1); 916 917 a = b.charCodeAt().toString(16); 918 919 return '\\u' + '0000'.substring(a.length) + a; 920 }) + quote; 921 } 922 923 if (t == 'object') { 924 if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { 925 for (i=0, v = '['; i<o.length; i++) 926 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 927 928 return v + ']'; 929 } 930 931 v = '{'; 932 933 for (name in o) { 934 if (o.hasOwnProperty(name)) { 935 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 936 } 937 } 938 939 return v + '}'; 940 } 941 942 return '' + o; 943 }; 944 945 tinymce.util.JSON = { 946 serialize: serialize, 947 948 parse: function(s) { 949 try { 950 return eval('(' + s + ')'); 951 } catch (ex) { 952 // Ignore 953 } 954 } 955 956 }; 957 })(); 958 tinymce.create('static tinymce.util.XHR', { 959 send : function(o) { 960 var x, t, w = window, c = 0; 961 962 function ready() { 963 if (!o.async || x.readyState == 4 || c++ > 10000) { 964 if (o.success && c < 10000 && x.status == 200) 965 o.success.call(o.success_scope, '' + x.responseText, x, o); 966 else if (o.error) 967 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 968 969 x = null; 970 } else 971 w.setTimeout(ready, 10); 972 }; 973 974 // Default settings 975 o.scope = o.scope || this; 976 o.success_scope = o.success_scope || o.scope; 977 o.error_scope = o.error_scope || o.scope; 978 o.async = o.async === false ? false : true; 979 o.data = o.data || ''; 980 981 function get(s) { 982 x = 0; 983 984 try { 985 x = new ActiveXObject(s); 986 } catch (ex) { 987 } 988 989 return x; 990 }; 991 992 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 993 994 if (x) { 995 if (x.overrideMimeType) 996 x.overrideMimeType(o.content_type); 997 998 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 999 1000 if (o.content_type) 1001 x.setRequestHeader('Content-Type', o.content_type); 1002 1003 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1004 1005 x.send(o.data); 1006 1007 // Syncronous request 1008 if (!o.async) 1009 return ready(); 1010 1011 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1012 t = w.setTimeout(ready, 10); 1013 } 1014 } 1015 }); 1016 (function() { 1017 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1018 1019 tinymce.create('tinymce.util.JSONRequest', { 1020 JSONRequest : function(s) { 1021 this.settings = extend({ 1022 }, s); 1023 this.count = 0; 1024 }, 1025 1026 send : function(o) { 1027 var ecb = o.error, scb = o.success; 1028 1029 o = extend(this.settings, o); 1030 1031 o.success = function(c, x) { 1032 c = JSON.parse(c); 1033 1034 if (typeof(c) == 'undefined') { 1035 c = { 1036 error : 'JSON Parse error.' 1037 }; 1038 } 1039 1040 if (c.error) 1041 ecb.call(o.error_scope || o.scope, c.error, x); 1042 else 1043 scb.call(o.success_scope || o.scope, c.result); 1044 }; 1045 1046 o.error = function(ty, x) { 1047 if (ecb) 1048 ecb.call(o.error_scope || o.scope, ty, x); 1049 }; 1050 1051 o.data = JSON.serialize({ 1052 id : o.id || 'c' + (this.count++), 1053 method : o.method, 1054 params : o.params 1055 }); 1056 1057 // JSON content type for Ruby on rails. Bug: #1883287 1058 o.content_type = 'application/json'; 1059 1060 XHR.send(o); 1061 }, 1062 1063 'static' : { 1064 sendRPC : function(o) { 1065 return new tinymce.util.JSONRequest().send(o); 1066 } 1067 } 1068 }); 1069 }()); 1070 (function(tinymce){ 1071 tinymce.VK = { 1072 BACKSPACE: 8, 1073 DELETE: 46, 1074 DOWN: 40, 1075 ENTER: 13, 1076 LEFT: 37, 1077 RIGHT: 39, 1078 SPACEBAR: 32, 1079 TAB: 9, 1080 UP: 38, 1081 1082 modifierPressed: function (e) { 1083 return e.shiftKey || e.ctrlKey || e.altKey; 1084 }, 1085 1086 metaKeyPressed: function(e) { 1087 // Check if ctrl or meta key is pressed also check if alt is false for Polish users 1088 return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey; 1089 } 1090 }; 1091 })(tinymce); 1092 tinymce.util.Quirks = function(editor) { 1093 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, 1094 settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each; 1095 1096 function setEditorCommandState(cmd, state) { 1097 try { 1098 editor.getDoc().execCommand(cmd, false, state); 1099 } catch (ex) { 1100 // Ignore 1101 } 1102 } 1103 1104 function getDocumentMode() { 1105 var documentMode = editor.getDoc().documentMode; 1106 1107 return documentMode ? documentMode : 6; 1108 }; 1109 1110 function isDefaultPrevented(e) { 1111 return e.isDefaultPrevented(); 1112 }; 1113 1114 function cleanupStylesWhenDeleting() { 1115 function removeMergedFormatSpans(isDelete) { 1116 var rng, blockElm, wrapperElm, bookmark, container, offset, elm; 1117 1118 function isAtStartOrEndOfElm() { 1119 if (container.nodeType == 3) { 1120 if (isDelete && offset == container.length) { 1121 return true; 1122 } 1123 1124 if (!isDelete && offset === 0) { 1125 return true; 1126 } 1127 } 1128 } 1129 1130 rng = selection.getRng(); 1131 var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset]; 1132 1133 if (!rng.collapsed) { 1134 isDelete = true; 1135 } 1136 1137 container = rng[(isDelete ? 'start' : 'end') + 'Container']; 1138 offset = rng[(isDelete ? 'start' : 'end') + 'Offset']; 1139 1140 if (container.nodeType == 3) { 1141 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1142 1143 // On delete clone the root span of the next block element 1144 if (isDelete) { 1145 blockElm = dom.getNext(blockElm, dom.isBlock); 1146 } 1147 1148 if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) { 1149 // Wrap children of block in a EM and let WebKit stick is 1150 // runtime styles junk into that EM 1151 wrapperElm = dom.create('em', {'id': '__mceDel'}); 1152 1153 each(tinymce.grep(blockElm.childNodes), function(node) { 1154 wrapperElm.appendChild(node); 1155 }); 1156 1157 blockElm.appendChild(wrapperElm); 1158 } 1159 } 1160 1161 // Do the backspace/delete action 1162 rng = dom.createRng(); 1163 rng.setStart(tmpRng[0], tmpRng[1]); 1164 rng.setEnd(tmpRng[2], tmpRng[3]); 1165 selection.setRng(rng); 1166 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1167 1168 // Remove temp wrapper element 1169 if (wrapperElm) { 1170 bookmark = selection.getBookmark(); 1171 1172 while (elm = dom.get('__mceDel')) { 1173 dom.remove(elm, true); 1174 } 1175 1176 selection.moveToBookmark(bookmark); 1177 } 1178 } 1179 1180 editor.onKeyDown.add(function(editor, e) { 1181 var isDelete; 1182 1183 isDelete = e.keyCode == DELETE; 1184 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1185 e.preventDefault(); 1186 removeMergedFormatSpans(isDelete); 1187 } 1188 }); 1189 1190 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1191 }; 1192 1193 function emptyEditorWhenDeleting() { 1194 function serializeRng(rng) { 1195 var body = dom.create("body"); 1196 var contents = rng.cloneContents(); 1197 body.appendChild(contents); 1198 return selection.serializer.serialize(body, {format: 'html'}); 1199 } 1200 1201 function allContentsSelected(rng) { 1202 var selection = serializeRng(rng); 1203 1204 var allRng = dom.createRng(); 1205 allRng.selectNode(editor.getBody()); 1206 1207 var allSelection = serializeRng(allRng); 1208 return selection === allSelection; 1209 } 1210 1211 editor.onKeyDown.add(function(editor, e) { 1212 var keyCode = e.keyCode, isCollapsed; 1213 1214 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1215 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { 1216 isCollapsed = editor.selection.isCollapsed(); 1217 1218 // Selection is collapsed but the editor isn't empty 1219 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1220 return; 1221 } 1222 1223 // IE deletes all contents correctly when everything is selected 1224 if (tinymce.isIE && !isCollapsed) { 1225 return; 1226 } 1227 1228 // Selection isn't collapsed but not all the contents is selected 1229 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1230 return; 1231 } 1232 1233 // Manually empty the editor 1234 editor.setContent(''); 1235 editor.selection.setCursorLocation(editor.getBody(), 0); 1236 editor.nodeChanged(); 1237 } 1238 }); 1239 }; 1240 1241 function selectAll() { 1242 editor.onKeyDown.add(function(editor, e) { 1243 if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { 1244 e.preventDefault(); 1245 editor.execCommand('SelectAll'); 1246 } 1247 }); 1248 }; 1249 1250 function inputMethodFocus() { 1251 if (!editor.settings.content_editable) { 1252 // Case 1 IME doesn't initialize if you focus the document 1253 dom.bind(editor.getDoc(), 'focusin', function(e) { 1254 selection.setRng(selection.getRng()); 1255 }); 1256 1257 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1258 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1259 if (e.target == editor.getDoc().documentElement) { 1260 editor.getWin().focus(); 1261 selection.setRng(selection.getRng()); 1262 } 1263 }); 1264 } 1265 }; 1266 1267 function removeHrOnBackspace() { 1268 editor.onKeyDown.add(function(editor, e) { 1269 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 1270 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1271 var node = selection.getNode(); 1272 var previousSibling = node.previousSibling; 1273 1274 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1275 dom.remove(previousSibling); 1276 tinymce.dom.Event.cancel(e); 1277 } 1278 } 1279 } 1280 }) 1281 } 1282 1283 function focusBody() { 1284 // Fix for a focus bug in FF 3.x where the body element 1285 // wouldn't get proper focus if the user clicked on the HTML element 1286 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1287 editor.onMouseDown.add(function(editor, e) { 1288 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { 1289 var body = editor.getBody(); 1290 1291 // Blur the body it's focused but not correctly focused 1292 body.blur(); 1293 1294 // Refocus the body after a little while 1295 setTimeout(function() { 1296 body.focus(); 1297 }, 0); 1298 } 1299 }); 1300 } 1301 }; 1302 1303 function selectControlElements() { 1304 editor.onClick.add(function(editor, e) { 1305 e = e.target; 1306 1307 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1308 // WebKit can't even do simple things like selecting an image 1309 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1310 if (/^(IMG|HR)$/.test(e.nodeName)) { 1311 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1312 } 1313 1314 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1315 selection.select(e); 1316 } 1317 1318 editor.nodeChanged(); 1319 }); 1320 }; 1321 1322 function removeStylesWhenDeletingAccrossBlockElements() { 1323 function getAttributeApplyFunction() { 1324 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1325 1326 return function() { 1327 var target = selection.getStart(); 1328 1329 if (target !== editor.getBody()) { 1330 dom.setAttrib(target, "style", null); 1331 1332 each(template, function(attr) { 1333 target.setAttributeNode(attr.cloneNode(true)); 1334 }); 1335 } 1336 }; 1337 } 1338 1339 function isSelectionAcrossElements() { 1340 return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); 1341 } 1342 1343 function blockEvent(editor, e) { 1344 e.preventDefault(); 1345 return false; 1346 } 1347 1348 editor.onKeyPress.add(function(editor, e) { 1349 var applyAttributes; 1350 1351 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1352 applyAttributes = getAttributeApplyFunction(); 1353 editor.getDoc().execCommand('delete', false, null); 1354 applyAttributes(); 1355 e.preventDefault(); 1356 return false; 1357 } 1358 }); 1359 1360 dom.bind(editor.getDoc(), 'cut', function(e) { 1361 var applyAttributes; 1362 1363 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { 1364 applyAttributes = getAttributeApplyFunction(); 1365 editor.onKeyUp.addToTop(blockEvent); 1366 1367 setTimeout(function() { 1368 applyAttributes(); 1369 editor.onKeyUp.remove(blockEvent); 1370 }, 0); 1371 } 1372 }); 1373 } 1374 1375 function selectionChangeNodeChanged() { 1376 var lastRng, selectionTimer; 1377 1378 dom.bind(editor.getDoc(), 'selectionchange', function() { 1379 if (selectionTimer) { 1380 clearTimeout(selectionTimer); 1381 selectionTimer = 0; 1382 } 1383 1384 selectionTimer = window.setTimeout(function() { 1385 var rng = selection.getRng(); 1386 1387 // Compare the ranges to see if it was a real change or not 1388 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1389 editor.nodeChanged(); 1390 lastRng = rng; 1391 } 1392 }, 50); 1393 }); 1394 } 1395 1396 function ensureBodyHasRoleApplication() { 1397 document.body.setAttribute("role", "application"); 1398 } 1399 1400 function disableBackspaceIntoATable() { 1401 editor.onKeyDown.add(function(editor, e) { 1402 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 1403 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1404 var previousSibling = selection.getNode().previousSibling; 1405 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1406 return tinymce.dom.Event.cancel(e); 1407 } 1408 } 1409 } 1410 }) 1411 } 1412 1413 function addNewLinesBeforeBrInPre() { 1414 // IE8+ rendering mode does the right thing with BR in PRE 1415 if (getDocumentMode() > 7) { 1416 return; 1417 } 1418 1419 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1420 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1421 setEditorCommandState('RespectVisibilityInDesign', true); 1422 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1423 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1424 1425 // Adds a \n before all BR elements in PRE to get them visual 1426 parser.addNodeFilter('pre', function(nodes, name) { 1427 var i = nodes.length, brNodes, j, brElm, sibling; 1428 1429 while (i--) { 1430 brNodes = nodes[i].getAll('br'); 1431 j = brNodes.length; 1432 while (j--) { 1433 brElm = brNodes[j]; 1434 1435 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1436 sibling = brElm.prev; 1437 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1438 sibling.value += '\n'; 1439 } else { 1440 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1441 } 1442 } 1443 } 1444 }); 1445 1446 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1447 serializer.addNodeFilter('pre', function(nodes, name) { 1448 var i = nodes.length, brNodes, j, brElm, sibling; 1449 1450 while (i--) { 1451 brNodes = nodes[i].getAll('br'); 1452 j = brNodes.length; 1453 while (j--) { 1454 brElm = brNodes[j]; 1455 sibling = brElm.prev; 1456 if (sibling && sibling.type == 3) { 1457 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1458 } 1459 } 1460 } 1461 }); 1462 } 1463 1464 function removePreSerializedStylesWhenSelectingControls() { 1465 dom.bind(editor.getBody(), 'mouseup', function(e) { 1466 var value, node = selection.getNode(); 1467 1468 // Moved styles to attributes on IMG eements 1469 if (node.nodeName == 'IMG') { 1470 // Convert style width to width attribute 1471 if (value = dom.getStyle(node, 'width')) { 1472 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1473 dom.setStyle(node, 'width', ''); 1474 } 1475 1476 // Convert style height to height attribute 1477 if (value = dom.getStyle(node, 'height')) { 1478 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1479 dom.setStyle(node, 'height', ''); 1480 } 1481 } 1482 }); 1483 } 1484 1485 function keepInlineElementOnDeleteBackspace() { 1486 editor.onKeyDown.add(function(editor, e) { 1487 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1488 1489 isDelete = e.keyCode == DELETE; 1490 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1491 rng = selection.getRng(); 1492 container = rng.startContainer; 1493 offset = rng.startOffset; 1494 collapsed = rng.collapsed; 1495 1496 // Override delete if the start container is a text node and is at the beginning of text or 1497 // just before/after the last character to be deleted in collapsed mode 1498 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1499 // Edge case when deleting <p><b><img> |x</b></p> 1500 sibling = container.previousSibling; 1501 if (sibling && sibling.nodeName == "IMG") { 1502 return; 1503 } 1504 1505 nonEmptyElements = editor.schema.getNonEmptyElements(); 1506 1507 // Prevent default logic since it's broken 1508 e.preventDefault(); 1509 1510 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1511 brElm = dom.create('br', {id: '__tmp'}); 1512 container.parentNode.insertBefore(brElm, container); 1513 1514 // Do the browser delete 1515 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1516 1517 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1518 container = selection.getRng().startContainer; 1519 sibling = container.previousSibling; 1520 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1521 dom.remove(sibling); 1522 } 1523 1524 // Remove the temp element we inserted 1525 dom.remove('__tmp'); 1526 } 1527 } 1528 }); 1529 } 1530 1531 function removeBlockQuoteOnBackSpace() { 1532 // Add block quote deletion handler 1533 editor.onKeyDown.add(function(editor, e) { 1534 var rng, container, offset, root, parent; 1535 1536 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { 1537 return; 1538 } 1539 1540 rng = selection.getRng(); 1541 container = rng.startContainer; 1542 offset = rng.startOffset; 1543 root = dom.getRoot(); 1544 parent = container; 1545 1546 if (!rng.collapsed || offset !== 0) { 1547 return; 1548 } 1549 1550 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1551 parent = parent.parentNode; 1552 } 1553 1554 // Is the cursor at the beginning of a blockquote? 1555 if (parent.tagName === 'BLOCKQUOTE') { 1556 // Remove the blockquote 1557 editor.formatter.toggle('blockquote', null, parent); 1558 1559 // Move the caret to the beginning of container 1560 rng = dom.createRng(); 1561 rng.setStart(container, 0); 1562 rng.setEnd(container, 0); 1563 selection.setRng(rng); 1564 } 1565 }); 1566 }; 1567 1568 function setGeckoEditingOptions() { 1569 function setOpts() { 1570 editor._refreshContentEditable(); 1571 1572 setEditorCommandState("StyleWithCSS", false); 1573 setEditorCommandState("enableInlineTableEditing", false); 1574 1575 if (!settings.object_resizing) { 1576 setEditorCommandState("enableObjectResizing", false); 1577 } 1578 }; 1579 1580 if (!settings.readonly) { 1581 editor.onBeforeExecCommand.add(setOpts); 1582 editor.onMouseDown.add(setOpts); 1583 } 1584 }; 1585 1586 function addBrAfterLastLinks() { 1587 function fixLinks(editor, o) { 1588 each(dom.select('a'), function(node) { 1589 var parentNode = node.parentNode, root = dom.getRoot(); 1590 1591 if (parentNode.lastChild === node) { 1592 while (parentNode && !dom.isBlock(parentNode)) { 1593 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1594 return; 1595 } 1596 1597 parentNode = parentNode.parentNode; 1598 } 1599 1600 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1601 } 1602 }); 1603 }; 1604 1605 editor.onExecCommand.add(function(editor, cmd) { 1606 if (cmd === 'CreateLink') { 1607 fixLinks(editor); 1608 } 1609 }); 1610 1611 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1612 }; 1613 1614 function setDefaultBlockType() { 1615 if (settings.forced_root_block) { 1616 editor.onInit.add(function() { 1617 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1618 }); 1619 } 1620 } 1621 1622 function removeGhostSelection() { 1623 function repaint(sender, args) { 1624 if (!sender || !args.initial) { 1625 editor.execCommand('mceRepaint'); 1626 } 1627 }; 1628 1629 editor.onUndo.add(repaint); 1630 editor.onRedo.add(repaint); 1631 editor.onSetContent.add(repaint); 1632 }; 1633 1634 function deleteControlItemOnBackSpace() { 1635 editor.onKeyDown.add(function(editor, e) { 1636 var rng; 1637 1638 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { 1639 rng = editor.getDoc().selection.createRange(); 1640 if (rng && rng.item) { 1641 e.preventDefault(); 1642 editor.undoManager.beforeChange(); 1643 dom.remove(rng.item(0)); 1644 editor.undoManager.add(); 1645 } 1646 } 1647 }); 1648 }; 1649 1650 function renderEmptyBlocksFix() { 1651 var emptyBlocksCSS; 1652 1653 // IE10+ 1654 if (getDocumentMode() >= 10) { 1655 emptyBlocksCSS = ''; 1656 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1657 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1658 }); 1659 1660 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1661 } 1662 }; 1663 1664 function fakeImageResize() { 1665 var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio, 1666 resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc(); 1667 1668 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1669 return; 1670 } 1671 1672 // Try disabling object resizing if WebKit implements resizing in the future 1673 setEditorCommandState("enableObjectResizing", false); 1674 1675 // Details about each resize handle how to scale etc 1676 resizeHandles = { 1677 // Name: x multiplier, y multiplier, delta size x, delta size y 1678 n: [.5, 0, 0, -1], 1679 e: [1, .5, 1, 0], 1680 s: [.5, 1, 0, 1], 1681 w: [0, .5, -1, 0], 1682 nw: [0, 0, -1, -1], 1683 ne: [1, 0, 1, -1], 1684 se: [1, 1, 1, 1], 1685 sw : [0, 1, -1, 1] 1686 }; 1687 1688 function resizeElement(e) { 1689 var deltaX, deltaY; 1690 1691 // Calc new width/height 1692 deltaX = e.screenX - startX; 1693 deltaY = e.screenY - startY; 1694 1695 // Calc new size 1696 width = deltaX * selectedHandle[2] + startW; 1697 height = deltaY * selectedHandle[3] + startH; 1698 1699 // Never scale down lower than 5 pixels 1700 width = width < 5 ? 5 : width; 1701 height = height < 5 ? 5 : height; 1702 1703 // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image 1704 if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) { 1705 width = Math.round(height / ratio); 1706 height = Math.round(width * ratio); 1707 } 1708 1709 // Update ghost size 1710 dom.setStyles(selectedElmGhost, { 1711 width: width, 1712 height: height 1713 }); 1714 1715 // Update ghost X position if needed 1716 if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { 1717 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); 1718 } 1719 1720 // Update ghost Y position if needed 1721 if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { 1722 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); 1723 } 1724 } 1725 1726 function endResize() { 1727 function setSizeProp(name, value) { 1728 if (value) { 1729 // Resize by using style or attribute 1730 if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { 1731 dom.setStyle(selectedElm, name, value); 1732 } else { 1733 dom.setAttrib(selectedElm, name, value); 1734 } 1735 } 1736 } 1737 1738 // Set width/height properties 1739 setSizeProp('width', width); 1740 setSizeProp('height', height); 1741 1742 dom.unbind(editableDoc, 'mousemove', resizeElement); 1743 dom.unbind(editableDoc, 'mouseup', endResize); 1744 1745 if (rootDocument != editableDoc) { 1746 dom.unbind(rootDocument, 'mousemove', resizeElement); 1747 dom.unbind(rootDocument, 'mouseup', endResize); 1748 } 1749 1750 // Remove ghost and update resize handle positions 1751 dom.remove(selectedElmGhost); 1752 showResizeRect(selectedElm); 1753 } 1754 1755 function showResizeRect(targetElm) { 1756 var position, targetWidth, targetHeight; 1757 1758 hideResizeRect(); 1759 1760 // Get position and size of target 1761 position = dom.getPos(targetElm); 1762 selectedElmX = position.x; 1763 selectedElmY = position.y; 1764 targetWidth = targetElm.offsetWidth; 1765 targetHeight = targetElm.offsetHeight; 1766 1767 // Reset width/height if user selects a new image/table 1768 if (selectedElm != targetElm) { 1769 selectedElm = targetElm; 1770 width = height = 0; 1771 } 1772 1773 each(resizeHandles, function(handle, name) { 1774 var handleElm; 1775 1776 // Get existing or render resize handle 1777 handleElm = dom.get('mceResizeHandle' + name); 1778 if (!handleElm) { 1779 handleElm = dom.add(editableDoc.documentElement, 'div', { 1780 id: 'mceResizeHandle' + name, 1781 'class': 'mceResizeHandle', 1782 style: 'cursor:' + name + '-resize; margin:0; padding:0' 1783 }); 1784 1785 dom.bind(handleElm, 'mousedown', function(e) { 1786 e.preventDefault(); 1787 1788 endResize(); 1789 1790 startX = e.screenX; 1791 startY = e.screenY; 1792 startW = selectedElm.clientWidth; 1793 startH = selectedElm.clientHeight; 1794 ratio = startH / startW; 1795 selectedHandle = handle; 1796 1797 selectedElmGhost = selectedElm.cloneNode(true); 1798 dom.addClass(selectedElmGhost, 'mceClonedResizable'); 1799 dom.setStyles(selectedElmGhost, { 1800 left: selectedElmX, 1801 top: selectedElmY, 1802 margin: 0 1803 }); 1804 1805 editableDoc.documentElement.appendChild(selectedElmGhost); 1806 1807 dom.bind(editableDoc, 'mousemove', resizeElement); 1808 dom.bind(editableDoc, 'mouseup', endResize); 1809 1810 if (rootDocument != editableDoc) { 1811 dom.bind(rootDocument, 'mousemove', resizeElement); 1812 dom.bind(rootDocument, 'mouseup', endResize); 1813 } 1814 }); 1815 } else { 1816 dom.show(handleElm); 1817 } 1818 1819 // Position element 1820 dom.setStyles(handleElm, { 1821 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), 1822 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) 1823 }); 1824 }); 1825 1826 // Only add resize rectangle on WebKit and only on images 1827 if (!tinymce.isOpera && selectedElm.nodeName == "IMG") { 1828 selectedElm.setAttribute('data-mce-selected', '1'); 1829 } 1830 } 1831 1832 function hideResizeRect() { 1833 if (selectedElm) { 1834 selectedElm.removeAttribute('data-mce-selected'); 1835 } 1836 1837 for (var name in resizeHandles) { 1838 dom.hide('mceResizeHandle' + name); 1839 } 1840 } 1841 1842 // Add CSS for resize handles, cloned element and selected 1843 editor.contentStyles.push( 1844 '.mceResizeHandle {' + 1845 'position: absolute;' + 1846 'border: 1px solid black;' + 1847 'background: #FFF;' + 1848 'width: 5px;' + 1849 'height: 5px;' + 1850 'z-index: 10000' + 1851 '}' + 1852 '.mceResizeHandle:hover {' + 1853 'background: #000' + 1854 '}' + 1855 'img[data-mce-selected] {' + 1856 'outline: 1px solid black' + 1857 '}' + 1858 'img.mceClonedResizable, table.mceClonedResizable {' + 1859 'position: absolute;' + 1860 'outline: 1px dashed black;' + 1861 'opacity: .5;' + 1862 'z-index: 10000' + 1863 '}' 1864 ); 1865 1866 function updateResizeRect() { 1867 var controlElm = dom.getParent(selection.getNode(), 'table,img'); 1868 1869 // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v 1870 each(dom.select('img[data-mce-selected]'), function(img) { 1871 img.removeAttribute('data-mce-selected'); 1872 }); 1873 1874 if (controlElm) { 1875 showResizeRect(controlElm); 1876 } else { 1877 hideResizeRect(); 1878 } 1879 } 1880 1881 // Show/hide resize rect when image is selected 1882 editor.onNodeChange.add(updateResizeRect); 1883 1884 // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container 1885 dom.bind(editableDoc, 'selectionchange', updateResizeRect); 1886 1887 // Remove the internal attribute when serializing the DOM 1888 editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) { 1889 var i = nodes.length; 1890 1891 while (i--) { 1892 nodes[i].attr(name, null); 1893 } 1894 }); 1895 } 1896 1897 function keepNoScriptContents() { 1898 if (getDocumentMode() < 9) { 1899 parser.addNodeFilter('noscript', function(nodes) { 1900 var i = nodes.length, node, textNode; 1901 1902 while (i--) { 1903 node = nodes[i]; 1904 textNode = node.firstChild; 1905 1906 if (textNode) { 1907 node.attr('data-mce-innertext', textNode.value); 1908 } 1909 } 1910 }); 1911 1912 serializer.addNodeFilter('noscript', function(nodes) { 1913 var i = nodes.length, node, textNode, value; 1914 1915 while (i--) { 1916 node = nodes[i]; 1917 textNode = nodes[i].firstChild; 1918 1919 if (textNode) { 1920 textNode.value = tinymce.html.Entities.decode(textNode.value); 1921 } else { 1922 // Old IE can't retain noscript value so an attribute is used to store it 1923 value = node.attributes.map['data-mce-innertext']; 1924 if (value) { 1925 node.attr('data-mce-innertext', null); 1926 textNode = new tinymce.html.Node('#text', 3); 1927 textNode.value = value; 1928 textNode.raw = true; 1929 node.append(textNode); 1930 } 1931 } 1932 } 1933 }); 1934 } 1935 } 1936 1937 function bodyHeight() { 1938 editor.contentStyles.push('body {min-height: 100px}'); 1939 editor.onClick.add(function(ed, e) { 1940 if (e.target.nodeName == 'HTML') { 1941 editor.execCommand('SelectAll'); 1942 editor.selection.collapse(true); 1943 editor.nodeChanged(); 1944 } 1945 }); 1946 } 1947 1948 function fixControlSelection() { 1949 editor.onInit.add(function() { 1950 var selectedRng; 1951 1952 editor.getBody().addEventListener('mscontrolselect', function(e) { 1953 setTimeout(function() { 1954 if (editor.selection.getNode() != e.target) { 1955 selectedRng = editor.selection.getRng(); 1956 selection.fakeRng = editor.dom.createRng(); 1957 selection.fakeRng.setStartBefore(e.target); 1958 selection.fakeRng.setEndAfter(e.target); 1959 } 1960 }, 0); 1961 }, false); 1962 1963 editor.getDoc().addEventListener('selectionchange', function(e) { 1964 if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) { 1965 selection.fakeRng = selectedRng = null; 1966 } 1967 }, false); 1968 }); 1969 } 1970 1971 // All browsers 1972 disableBackspaceIntoATable(); 1973 removeBlockQuoteOnBackSpace(); 1974 emptyEditorWhenDeleting(); 1975 1976 // WebKit 1977 if (tinymce.isWebKit) { 1978 keepInlineElementOnDeleteBackspace(); 1979 cleanupStylesWhenDeleting(); 1980 inputMethodFocus(); 1981 selectControlElements(); 1982 setDefaultBlockType(); 1983 1984 // iOS 1985 if (tinymce.isIDevice) { 1986 selectionChangeNodeChanged(); 1987 } else { 1988 fakeImageResize(); 1989 selectAll(); 1990 } 1991 } 1992 1993 // IE 1994 if (tinymce.isIE && !tinymce.isIE11) { 1995 removeHrOnBackspace(); 1996 ensureBodyHasRoleApplication(); 1997 addNewLinesBeforeBrInPre(); 1998 removePreSerializedStylesWhenSelectingControls(); 1999 deleteControlItemOnBackSpace(); 2000 renderEmptyBlocksFix(); 2001 keepNoScriptContents(); 2002 } 2003 2004 // IE 11+ 2005 if (tinymce.isIE11) { 2006 bodyHeight(); 2007 fixControlSelection(); 2008 } 2009 2010 // Gecko 2011 if (tinymce.isGecko && !tinymce.isIE11) { 2012 removeHrOnBackspace(); 2013 focusBody(); 2014 removeStylesWhenDeletingAccrossBlockElements(); 2015 setGeckoEditingOptions(); 2016 addBrAfterLastLinks(); 2017 removeGhostSelection(); 2018 } 2019 2020 // Opera 2021 if (tinymce.isOpera) { 2022 fakeImageResize(); 2023 } 2024 }; 2025 (function(tinymce) { 2026 var namedEntities, baseEntities, reverseEntities, 2027 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2028 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2029 rawCharsRegExp = /[<>&\"\']/g, 2030 entityRegExp = /&(#x|#)?([\w]+);/g, 2031 asciiMap = { 2032 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 2033 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 2034 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 2035 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 2036 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 2037 }; 2038 2039 // Raw entities 2040 baseEntities = { 2041 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 2042 "'" : ''', 2043 '<' : '<', 2044 '>' : '>', 2045 '&' : '&' 2046 }; 2047 2048 // Reverse lookup table for raw entities 2049 reverseEntities = { 2050 '<' : '<', 2051 '>' : '>', 2052 '&' : '&', 2053 '"' : '"', 2054 ''' : "'" 2055 }; 2056 2057 // Decodes text by using the browser 2058 function nativeDecode(text) { 2059 var elm; 2060 2061 elm = document.createElement("div"); 2062 elm.innerHTML = text; 2063 2064 return elm.textContent || elm.innerText || text; 2065 }; 2066 2067 // Build a two way lookup table for the entities 2068 function buildEntitiesLookup(items, radix) { 2069 var i, chr, entity, lookup = {}; 2070 2071 if (items) { 2072 items = items.split(','); 2073 radix = radix || 10; 2074 2075 // Build entities lookup table 2076 for (i = 0; i < items.length; i += 2) { 2077 chr = String.fromCharCode(parseInt(items[i], radix)); 2078 2079 // Only add non base entities 2080 if (!baseEntities[chr]) { 2081 entity = '&' + items[i + 1] + ';'; 2082 lookup[chr] = entity; 2083 lookup[entity] = chr; 2084 } 2085 } 2086 2087 return lookup; 2088 } 2089 }; 2090 2091 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 2092 namedEntities = buildEntitiesLookup( 2093 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 2094 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 2095 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 2096 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 2097 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 2098 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 2099 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 2100 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 2101 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 2102 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 2103 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 2104 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 2105 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 2106 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 2107 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 2108 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 2109 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 2110 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 2111 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 2112 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 2113 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 2114 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 2115 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 2116 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 2117 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 2118 2119 tinymce.html = tinymce.html || {}; 2120 2121 tinymce.html.Entities = { 2122 encodeRaw : function(text, attr) { 2123 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2124 return baseEntities[chr] || chr; 2125 }); 2126 }, 2127 2128 encodeAllRaw : function(text) { 2129 return ('' + text).replace(rawCharsRegExp, function(chr) { 2130 return baseEntities[chr] || chr; 2131 }); 2132 }, 2133 2134 encodeNumeric : function(text, attr) { 2135 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2136 // Multi byte sequence convert it to a single entity 2137 if (chr.length > 1) 2138 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 2139 2140 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 2141 }); 2142 }, 2143 2144 encodeNamed : function(text, attr, entities) { 2145 entities = entities || namedEntities; 2146 2147 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2148 return baseEntities[chr] || entities[chr] || chr; 2149 }); 2150 }, 2151 2152 getEncodeFunc : function(name, entities) { 2153 var Entities = tinymce.html.Entities; 2154 2155 entities = buildEntitiesLookup(entities) || namedEntities; 2156 2157 function encodeNamedAndNumeric(text, attr) { 2158 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2159 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 2160 }); 2161 }; 2162 2163 function encodeCustomNamed(text, attr) { 2164 return Entities.encodeNamed(text, attr, entities); 2165 }; 2166 2167 // Replace + with , to be compatible with previous TinyMCE versions 2168 name = tinymce.makeMap(name.replace(/\+/g, ',')); 2169 2170 // Named and numeric encoder 2171 if (name.named && name.numeric) 2172 return encodeNamedAndNumeric; 2173 2174 // Named encoder 2175 if (name.named) { 2176 // Custom names 2177 if (entities) 2178 return encodeCustomNamed; 2179 2180 return Entities.encodeNamed; 2181 } 2182 2183 // Numeric 2184 if (name.numeric) 2185 return Entities.encodeNumeric; 2186 2187 // Raw encoder 2188 return Entities.encodeRaw; 2189 }, 2190 2191 decode : function(text) { 2192 return text.replace(entityRegExp, function(all, numeric, value) { 2193 if (numeric) { 2194 value = parseInt(value, numeric.length === 2 ? 16 : 10); 2195 2196 // Support upper UTF 2197 if (value > 0xFFFF) { 2198 value -= 0x10000; 2199 2200 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 2201 } else 2202 return asciiMap[value] || String.fromCharCode(value); 2203 } 2204 2205 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 2206 }); 2207 } 2208 }; 2209 })(tinymce); 2210 tinymce.html.Styles = function(settings, schema) { 2211 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 2212 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 2213 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 2214 trimRightRegExp = /\s+$/, 2215 urlColorRegExp = /rgb/, 2216 undef, i, encodingLookup = {}, encodingItems; 2217 2218 settings = settings || {}; 2219 2220 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 2221 for (i = 0; i < encodingItems.length; i++) { 2222 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 2223 encodingLookup['\uFEFF' + i] = encodingItems[i]; 2224 } 2225 2226 function toHex(match, r, g, b) { 2227 function hex(val) { 2228 val = parseInt(val).toString(16); 2229 2230 return val.length > 1 ? val : '0' + val; // 0 -> 00 2231 }; 2232 2233 return '#' + hex(r) + hex(g) + hex(b); 2234 }; 2235 2236 return { 2237 toHex : function(color) { 2238 return color.replace(rgbRegExp, toHex); 2239 }, 2240 2241 parse : function(css) { 2242 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 2243 2244 function compress(prefix, suffix) { 2245 var top, right, bottom, left; 2246 2247 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 2248 // So lets asume it shouldn't be there 2249 if (styles['border-image'] === 'none') { 2250 delete styles['border-image']; 2251 } 2252 2253 // Get values and check it it needs compressing 2254 top = styles[prefix + '-top' + suffix]; 2255 if (!top) 2256 return; 2257 2258 right = styles[prefix + '-right' + suffix]; 2259 if (top != right) 2260 return; 2261 2262 bottom = styles[prefix + '-bottom' + suffix]; 2263 if (right != bottom) 2264 return; 2265 2266 left = styles[prefix + '-left' + suffix]; 2267 if (bottom != left) 2268 return; 2269 2270 // Compress 2271 styles[prefix + suffix] = left; 2272 delete styles[prefix + '-top' + suffix]; 2273 delete styles[prefix + '-right' + suffix]; 2274 delete styles[prefix + '-bottom' + suffix]; 2275 delete styles[prefix + '-left' + suffix]; 2276 }; 2277 2278 function canCompress(key) { 2279 var value = styles[key], i; 2280 2281 if (!value || value.indexOf(' ') < 0) 2282 return; 2283 2284 value = value.split(' '); 2285 i = value.length; 2286 while (i--) { 2287 if (value[i] !== value[0]) 2288 return false; 2289 } 2290 2291 styles[key] = value[0]; 2292 2293 return true; 2294 }; 2295 2296 function compress2(target, a, b, c) { 2297 if (!canCompress(a)) 2298 return; 2299 2300 if (!canCompress(b)) 2301 return; 2302 2303 if (!canCompress(c)) 2304 return; 2305 2306 // Compress 2307 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2308 delete styles[a]; 2309 delete styles[b]; 2310 delete styles[c]; 2311 }; 2312 2313 // Encodes the specified string by replacing all \" \' ; : with _<num> 2314 function encode(str) { 2315 isEncoded = true; 2316 2317 return encodingLookup[str]; 2318 }; 2319 2320 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2321 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2322 function decode(str, keep_slashes) { 2323 if (isEncoded) { 2324 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2325 return encodingLookup[str]; 2326 }); 2327 } 2328 2329 if (!keep_slashes) 2330 str = str.replace(/\\([\'\";:])/g, "$1"); 2331 2332 return str; 2333 }; 2334 2335 function processUrl(match, url, url2, url3, str, str2) { 2336 str = str || str2; 2337 2338 if (str) { 2339 str = decode(str); 2340 2341 // Force strings into single quote format 2342 return "'" + str.replace(/\'/g, "\\'") + "'"; 2343 } 2344 2345 url = decode(url || url2 || url3); 2346 2347 // Convert the URL to relative/absolute depending on config 2348 if (urlConverter) 2349 url = urlConverter.call(urlConverterScope, url, 'style'); 2350 2351 // Output new URL format 2352 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2353 }; 2354 2355 if (css) { 2356 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2357 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2358 return str.replace(/[;:]/g, encode); 2359 }); 2360 2361 // Parse styles 2362 while (matches = styleRegExp.exec(css)) { 2363 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2364 value = matches[2].replace(trimRightRegExp, ''); 2365 2366 if (name && value.length > 0) { 2367 // Opera will produce 700 instead of bold in their style values 2368 if (name === 'font-weight' && value === '700') 2369 value = 'bold'; 2370 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2371 value = value.toLowerCase(); 2372 2373 // Convert RGB colors to HEX 2374 value = value.replace(rgbRegExp, toHex); 2375 2376 // Convert URLs and force them into url('value') format 2377 value = value.replace(urlOrStrRegExp, processUrl); 2378 styles[name] = isEncoded ? decode(value, true) : value; 2379 } 2380 2381 styleRegExp.lastIndex = matches.index + matches[0].length; 2382 } 2383 2384 // Compress the styles to reduce it's size for example IE will expand styles 2385 compress("border", ""); 2386 compress("border", "-width"); 2387 compress("border", "-color"); 2388 compress("border", "-style"); 2389 compress("padding", ""); 2390 compress("margin", ""); 2391 compress2('border', 'border-width', 'border-style', 'border-color'); 2392 2393 // Remove pointless border, IE produces these 2394 if (styles.border === 'medium none') 2395 delete styles.border; 2396 } 2397 2398 return styles; 2399 }, 2400 2401 serialize : function(styles, element_name) { 2402 var css = '', name, value; 2403 2404 function serializeStyles(name) { 2405 var styleList, i, l, value; 2406 2407 styleList = schema.styles[name]; 2408 if (styleList) { 2409 for (i = 0, l = styleList.length; i < l; i++) { 2410 name = styleList[i]; 2411 value = styles[name]; 2412 2413 if (value !== undef && value.length > 0) 2414 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2415 } 2416 } 2417 }; 2418 2419 // Serialize styles according to schema 2420 if (element_name && schema && schema.styles) { 2421 // Serialize global styles and element specific styles 2422 serializeStyles('*'); 2423 serializeStyles(element_name); 2424 } else { 2425 // Output the styles in the order they are inside the object 2426 for (name in styles) { 2427 value = styles[name]; 2428 2429 if (value !== undef && value.length > 0) 2430 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2431 } 2432 } 2433 2434 return css; 2435 } 2436 }; 2437 }; 2438 (function(tinymce) { 2439 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2440 2441 function split(str, delim) { 2442 return str.split(delim || ','); 2443 }; 2444 2445 function unpack(lookup, data) { 2446 var key, elements = {}; 2447 2448 function replace(value) { 2449 return value.replace(/[A-Z]+/g, function(key) { 2450 return replace(lookup[key]); 2451 }); 2452 }; 2453 2454 // Unpack lookup 2455 for (key in lookup) { 2456 if (lookup.hasOwnProperty(key)) 2457 lookup[key] = replace(lookup[key]); 2458 } 2459 2460 // Unpack and parse data into object map 2461 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2462 attributes = split(attributes, '|'); 2463 2464 elements[name] = { 2465 attributes : makeMap(attributes), 2466 attributesOrder : attributes, 2467 children : makeMap(children, '|', {'#comment' : {}}) 2468 } 2469 }); 2470 2471 return elements; 2472 }; 2473 2474 function getHTML5() { 2475 var html5 = mapCache.html5; 2476 2477 if (!html5) { 2478 html5 = mapCache.html5 = unpack({ 2479 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2480 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2481 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2482 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2483 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2484 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2485 }, 'html[A|manifest][body|head]' + 2486 'head[A][base|command|link|meta|noscript|script|style|title]' + 2487 'title[A][#]' + 2488 'base[A|href|target][]' + 2489 'link[A|href|rel|media|type|sizes][]' + 2490 'meta[A|http-equiv|name|content|charset][]' + 2491 'style[A|type|media|scoped][#]' + 2492 'script[A|charset|type|src|defer|async][#]' + 2493 'noscript[A][C]' + 2494 'body[A][C]' + 2495 'section[A][C]' + 2496 'nav[A][C]' + 2497 'article[A][C]' + 2498 'aside[A][C]' + 2499 'h1[A][B]' + 2500 'h2[A][B]' + 2501 'h3[A][B]' + 2502 'h4[A][B]' + 2503 'h5[A][B]' + 2504 'h6[A][B]' + 2505 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2506 'header[A][C]' + 2507 'footer[A][C]' + 2508 'address[A][C]' + 2509 'p[A][B]' + 2510 'br[A][]' + 2511 'pre[A][B]' + 2512 'dialog[A][dd|dt]' + 2513 'blockquote[A|cite][C]' + 2514 'ol[A|start|reversed][li]' + 2515 'ul[A][li]' + 2516 'li[A|value][C]' + 2517 'dl[A][dd|dt]' + 2518 'dt[A][B]' + 2519 'dd[A][C]' + 2520 'a[A|href|target|ping|rel|media|type][B]' + 2521 'em[A][B]' + 2522 'strong[A][B]' + 2523 'small[A][B]' + 2524 'cite[A][B]' + 2525 'q[A|cite][B]' + 2526 'dfn[A][B]' + 2527 'abbr[A][B]' + 2528 'code[A][B]' + 2529 'var[A][B]' + 2530 'samp[A][B]' + 2531 'kbd[A][B]' + 2532 'sub[A][B]' + 2533 'sup[A][B]' + 2534 'i[A][B]' + 2535 'b[A][B]' + 2536 'mark[A][B]' + 2537 'progress[A|value|max][B]' + 2538 'meter[A|value|min|max|low|high|optimum][B]' + 2539 'time[A|datetime][B]' + 2540 'ruby[A][B|rt|rp]' + 2541 'rt[A][B]' + 2542 'rp[A][B]' + 2543 'bdo[A][B]' + 2544 'span[A][B]' + 2545 'ins[A|cite|datetime][B]' + 2546 'del[A|cite|datetime][B]' + 2547 'figure[A][C|legend|figcaption]' + 2548 'figcaption[A][C]' + 2549 'img[A|alt|src|height|width|usemap|ismap][]' + 2550 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2551 'embed[A|src|height|width|type][]' + 2552 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2553 'param[A|name|value][]' + 2554 'details[A|open][C|legend]' + 2555 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2556 'menu[A|type|label][C|li]' + 2557 'legend[A][C|B]' + 2558 'div[A][C]' + 2559 'source[A|src|type|media][]' + 2560 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2561 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2562 'hr[A][]' + 2563 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2564 'fieldset[A|disabled|form|name][C|legend]' + 2565 'label[A|form|for][B]' + 2566 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2567 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2568 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2569 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2570 'datalist[A][B|option]' + 2571 'optgroup[A|disabled|label][option]' + 2572 'option[A|disabled|selected|label|value][]' + 2573 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2574 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2575 'output[A|for|form|name][B]' + 2576 'canvas[A|width|height][]' + 2577 'map[A|name][B|C]' + 2578 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2579 'mathml[A][]' + 2580 'svg[A][]' + 2581 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2582 'caption[A][C]' + 2583 'colgroup[A|span][col]' + 2584 'col[A|span][]' + 2585 'thead[A][tr]' + 2586 'tfoot[A][tr]' + 2587 'tbody[A][tr]' + 2588 'tr[A][th|td]' + 2589 'th[A|headers|rowspan|colspan|scope][B]' + 2590 'td[A|headers|rowspan|colspan][C]' + 2591 'wbr[A][]' 2592 ); 2593 } 2594 2595 return html5; 2596 }; 2597 2598 function getHTML4() { 2599 var html4 = mapCache.html4; 2600 2601 if (!html4) { 2602 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2603 html4 = mapCache.html4 = unpack({ 2604 Z : 'H|K|N|O|P', 2605 Y : 'X|form|R|Q', 2606 ZG : 'E|span|width|align|char|charoff|valign', 2607 X : 'p|T|div|U|W|isindex|fieldset|table', 2608 ZF : 'E|align|char|charoff|valign', 2609 W : 'pre|hr|blockquote|address|center|noframes', 2610 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2611 ZD : '[E][S]', 2612 U : 'ul|ol|dl|menu|dir', 2613 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2614 T : 'h1|h2|h3|h4|h5|h6', 2615 ZB : 'X|S|Q', 2616 S : 'R|P', 2617 ZA : 'a|G|J|M|O|P', 2618 R : 'a|H|K|N|O', 2619 Q : 'noscript|P', 2620 P : 'ins|del|script', 2621 O : 'input|select|textarea|label|button', 2622 N : 'M|L', 2623 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2624 L : 'sub|sup', 2625 K : 'J|I', 2626 J : 'tt|i|b|u|s|strike', 2627 I : 'big|small|font|basefont', 2628 H : 'G|F', 2629 G : 'br|span|bdo', 2630 F : 'object|applet|img|map|iframe', 2631 E : 'A|B|C', 2632 D : 'accesskey|tabindex|onfocus|onblur', 2633 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2634 B : 'lang|xml:lang|dir', 2635 A : 'id|class|style|title' 2636 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2637 'style[B|id|type|media|title|xml:space][]' + 2638 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2639 'param[id|name|value|valuetype|type][]' + 2640 'p[E|align][#|S]' + 2641 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2642 'br[A|clear][]' + 2643 'span[E][#|S]' + 2644 'bdo[A|C|B][#|S]' + 2645 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2646 'h1[E|align][#|S]' + 2647 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2648 'map[B|C|A|name][X|form|Q|area]' + 2649 'h2[E|align][#|S]' + 2650 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2651 'h3[E|align][#|S]' + 2652 'tt[E][#|S]' + 2653 'i[E][#|S]' + 2654 'b[E][#|S]' + 2655 'u[E][#|S]' + 2656 's[E][#|S]' + 2657 'strike[E][#|S]' + 2658 'big[E][#|S]' + 2659 'small[E][#|S]' + 2660 'font[A|B|size|color|face][#|S]' + 2661 'basefont[id|size|color|face][]' + 2662 'em[E][#|S]' + 2663 'strong[E][#|S]' + 2664 'dfn[E][#|S]' + 2665 'code[E][#|S]' + 2666 'q[E|cite][#|S]' + 2667 'samp[E][#|S]' + 2668 'kbd[E][#|S]' + 2669 'var[E][#|S]' + 2670 'cite[E][#|S]' + 2671 'abbr[E][#|S]' + 2672 'acronym[E][#|S]' + 2673 'sub[E][#|S]' + 2674 'sup[E][#|S]' + 2675 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2676 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2677 'optgroup[E|disabled|label][option]' + 2678 'option[E|selected|disabled|label|value][]' + 2679 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2680 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2681 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2682 'h4[E|align][#|S]' + 2683 'ins[E|cite|datetime][#|Y]' + 2684 'h5[E|align][#|S]' + 2685 'del[E|cite|datetime][#|Y]' + 2686 'h6[E|align][#|S]' + 2687 'div[E|align][#|Y]' + 2688 'ul[E|type|compact][li]' + 2689 'li[E|type|value][#|Y]' + 2690 'ol[E|type|compact|start][li]' + 2691 'dl[E|compact][dt|dd]' + 2692 'dt[E][#|S]' + 2693 'dd[E][#|Y]' + 2694 'menu[E|compact][li]' + 2695 'dir[E|compact][li]' + 2696 'pre[E|width|xml:space][#|ZA]' + 2697 'hr[E|align|noshade|size|width][]' + 2698 'blockquote[E|cite][#|Y]' + 2699 'address[E][#|S|p]' + 2700 'center[E][#|Y]' + 2701 'noframes[E][#|Y]' + 2702 'isindex[A|B|prompt][]' + 2703 'fieldset[E][#|legend|Y]' + 2704 'legend[E|accesskey|align][#|S]' + 2705 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2706 'caption[E|align][#|S]' + 2707 'col[ZG][]' + 2708 'colgroup[ZG][col]' + 2709 'thead[ZF][tr]' + 2710 'tr[ZF|bgcolor][th|td]' + 2711 'th[E|ZE][#|Y]' + 2712 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2713 'noscript[E][#|Y]' + 2714 'td[E|ZE][#|Y]' + 2715 'tfoot[ZF][tr]' + 2716 'tbody[ZF][tr]' + 2717 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2718 'base[id|href|target][]' + 2719 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2720 ); 2721 } 2722 2723 return html4; 2724 }; 2725 2726 tinymce.html.Schema = function(settings) { 2727 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2728 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2729 2730 // Creates an lookup table map object for the specified option or the default value 2731 function createLookupTable(option, default_value, extend) { 2732 var value = settings[option]; 2733 2734 if (!value) { 2735 // Get cached default map or make it if needed 2736 value = mapCache[option]; 2737 2738 if (!value) { 2739 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2740 value = tinymce.extend(value, extend); 2741 2742 mapCache[option] = value; 2743 } 2744 } else { 2745 // Create custom map 2746 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2747 } 2748 2749 return value; 2750 }; 2751 2752 settings = settings || {}; 2753 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2754 2755 // Allow all elements and attributes if verify_html is set to false 2756 if (settings.verify_html === false) 2757 settings.valid_elements = '*[*]'; 2758 2759 // Build styles list 2760 if (settings.valid_styles) { 2761 validStyles = {}; 2762 2763 // Convert styles into a rule list 2764 each(settings.valid_styles, function(value, key) { 2765 validStyles[key] = tinymce.explode(value); 2766 }); 2767 } 2768 2769 // Setup map objects 2770 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea'); 2771 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2772 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2773 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2774 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); 2775 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 2776 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 2777 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 2778 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap); 2779 2780 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2781 function patternToRegExp(str) { 2782 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2783 }; 2784 2785 // Parses the specified valid_elements string and adds to the current rules 2786 // This function is a bit hard to read since it's heavily optimized for speed 2787 function addValidElements(valid_elements) { 2788 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2789 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2790 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2791 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2792 hasPatternsRegExp = /[*?+]/; 2793 2794 if (valid_elements) { 2795 // Split valid elements into an array with rules 2796 valid_elements = split(valid_elements); 2797 2798 if (elements['@']) { 2799 globalAttributes = elements['@'].attributes; 2800 globalAttributesOrder = elements['@'].attributesOrder; 2801 } 2802 2803 // Loop all rules 2804 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2805 // Parse element rule 2806 matches = elementRuleRegExp.exec(valid_elements[ei]); 2807 if (matches) { 2808 // Setup local names for matches 2809 prefix = matches[1]; 2810 elementName = matches[2]; 2811 outputName = matches[3]; 2812 attrData = matches[4]; 2813 2814 // Create new attributes and attributesOrder 2815 attributes = {}; 2816 attributesOrder = []; 2817 2818 // Create the new element 2819 element = { 2820 attributes : attributes, 2821 attributesOrder : attributesOrder 2822 }; 2823 2824 // Padd empty elements prefix 2825 if (prefix === '#') 2826 element.paddEmpty = true; 2827 2828 // Remove empty elements prefix 2829 if (prefix === '-') 2830 element.removeEmpty = true; 2831 2832 // Copy attributes from global rule into current rule 2833 if (globalAttributes) { 2834 for (key in globalAttributes) 2835 attributes[key] = globalAttributes[key]; 2836 2837 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2838 } 2839 2840 // Attributes defined 2841 if (attrData) { 2842 attrData = split(attrData, '|'); 2843 for (ai = 0, al = attrData.length; ai < al; ai++) { 2844 matches = attrRuleRegExp.exec(attrData[ai]); 2845 if (matches) { 2846 attr = {}; 2847 attrType = matches[1]; 2848 attrName = matches[2].replace(/::/g, ':'); 2849 prefix = matches[3]; 2850 value = matches[4]; 2851 2852 // Required 2853 if (attrType === '!') { 2854 element.attributesRequired = element.attributesRequired || []; 2855 element.attributesRequired.push(attrName); 2856 attr.required = true; 2857 } 2858 2859 // Denied from global 2860 if (attrType === '-') { 2861 delete attributes[attrName]; 2862 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2863 continue; 2864 } 2865 2866 // Default value 2867 if (prefix) { 2868 // Default value 2869 if (prefix === '=') { 2870 element.attributesDefault = element.attributesDefault || []; 2871 element.attributesDefault.push({name: attrName, value: value}); 2872 attr.defaultValue = value; 2873 } 2874 2875 // Forced value 2876 if (prefix === ':') { 2877 element.attributesForced = element.attributesForced || []; 2878 element.attributesForced.push({name: attrName, value: value}); 2879 attr.forcedValue = value; 2880 } 2881 2882 // Required values 2883 if (prefix === '<') 2884 attr.validValues = makeMap(value, '?'); 2885 } 2886 2887 // Check for attribute patterns 2888 if (hasPatternsRegExp.test(attrName)) { 2889 element.attributePatterns = element.attributePatterns || []; 2890 attr.pattern = patternToRegExp(attrName); 2891 element.attributePatterns.push(attr); 2892 } else { 2893 // Add attribute to order list if it doesn't already exist 2894 if (!attributes[attrName]) 2895 attributesOrder.push(attrName); 2896 2897 attributes[attrName] = attr; 2898 } 2899 } 2900 } 2901 } 2902 2903 // Global rule, store away these for later usage 2904 if (!globalAttributes && elementName == '@') { 2905 globalAttributes = attributes; 2906 globalAttributesOrder = attributesOrder; 2907 } 2908 2909 // Handle substitute elements such as b/strong 2910 if (outputName) { 2911 element.outputName = elementName; 2912 elements[outputName] = element; 2913 } 2914 2915 // Add pattern or exact element 2916 if (hasPatternsRegExp.test(elementName)) { 2917 element.pattern = patternToRegExp(elementName); 2918 patternElements.push(element); 2919 } else 2920 elements[elementName] = element; 2921 } 2922 } 2923 } 2924 }; 2925 2926 function setValidElements(valid_elements) { 2927 elements = {}; 2928 patternElements = []; 2929 2930 addValidElements(valid_elements); 2931 2932 each(schemaItems, function(element, name) { 2933 children[name] = element.children; 2934 }); 2935 }; 2936 2937 // Adds custom non HTML elements to the schema 2938 function addCustomElements(custom_elements) { 2939 var customElementRegExp = /^(~)?(.+)$/; 2940 2941 if (custom_elements) { 2942 each(split(custom_elements), function(rule) { 2943 var matches = customElementRegExp.exec(rule), 2944 inline = matches[1] === '~', 2945 cloneName = inline ? 'span' : 'div', 2946 name = matches[2]; 2947 2948 children[name] = children[cloneName]; 2949 customElementsMap[name] = cloneName; 2950 2951 // If it's not marked as inline then add it to valid block elements 2952 if (!inline) { 2953 blockElementsMap[name.toUpperCase()] = {}; 2954 blockElementsMap[name] = {}; 2955 } 2956 2957 // Add elements clone if needed 2958 if (!elements[name]) { 2959 elements[name] = elements[cloneName]; 2960 } 2961 2962 // Add custom elements at span/div positions 2963 each(children, function(element, child) { 2964 if (element[cloneName]) 2965 element[name] = element[cloneName]; 2966 }); 2967 }); 2968 } 2969 }; 2970 2971 // Adds valid children to the schema object 2972 function addValidChildren(valid_children) { 2973 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2974 2975 if (valid_children) { 2976 each(split(valid_children), function(rule) { 2977 var matches = childRuleRegExp.exec(rule), parent, prefix; 2978 2979 if (matches) { 2980 prefix = matches[1]; 2981 2982 // Add/remove items from default 2983 if (prefix) 2984 parent = children[matches[2]]; 2985 else 2986 parent = children[matches[2]] = {'#comment' : {}}; 2987 2988 parent = children[matches[2]]; 2989 2990 each(split(matches[3], '|'), function(child) { 2991 if (prefix === '-') 2992 delete parent[child]; 2993 else 2994 parent[child] = {}; 2995 }); 2996 } 2997 }); 2998 } 2999 }; 3000 3001 function getElementRule(name) { 3002 var element = elements[name], i; 3003 3004 // Exact match found 3005 if (element) 3006 return element; 3007 3008 // No exact match then try the patterns 3009 i = patternElements.length; 3010 while (i--) { 3011 element = patternElements[i]; 3012 3013 if (element.pattern.test(name)) 3014 return element; 3015 } 3016 }; 3017 3018 if (!settings.valid_elements) { 3019 // No valid elements defined then clone the elements from the schema spec 3020 each(schemaItems, function(element, name) { 3021 elements[name] = { 3022 attributes : element.attributes, 3023 attributesOrder : element.attributesOrder 3024 }; 3025 3026 children[name] = element.children; 3027 }); 3028 3029 // Switch these on HTML4 3030 if (settings.schema != "html5") { 3031 each(split('strong/b,em/i'), function(item) { 3032 item = split(item, '/'); 3033 elements[item[1]].outputName = item[0]; 3034 }); 3035 } 3036 3037 // Add default alt attribute for images 3038 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 3039 3040 // Remove these if they are empty by default 3041 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 3042 if (elements[name]) { 3043 elements[name].removeEmpty = true; 3044 } 3045 }); 3046 3047 // Padd these by default 3048 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 3049 elements[name].paddEmpty = true; 3050 }); 3051 } else 3052 setValidElements(settings.valid_elements); 3053 3054 addCustomElements(settings.custom_elements); 3055 addValidChildren(settings.valid_children); 3056 addValidElements(settings.extended_valid_elements); 3057 3058 // Todo: Remove this when we fix list handling to be valid 3059 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 3060 3061 // Delete invalid elements 3062 if (settings.invalid_elements) { 3063 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 3064 if (elements[item]) 3065 delete elements[item]; 3066 }); 3067 } 3068 3069 // If the user didn't allow span only allow internal spans 3070 if (!getElementRule('span')) 3071 addValidElements('span[!data-mce-type|*]'); 3072 3073 self.children = children; 3074 3075 self.styles = validStyles; 3076 3077 self.getBoolAttrs = function() { 3078 return boolAttrMap; 3079 }; 3080 3081 self.getBlockElements = function() { 3082 return blockElementsMap; 3083 }; 3084 3085 self.getTextBlockElements = function() { 3086 return textBlockElementsMap; 3087 }; 3088 3089 self.getShortEndedElements = function() { 3090 return shortEndedElementsMap; 3091 }; 3092 3093 self.getSelfClosingElements = function() { 3094 return selfClosingElementsMap; 3095 }; 3096 3097 self.getNonEmptyElements = function() { 3098 return nonEmptyElementsMap; 3099 }; 3100 3101 self.getWhiteSpaceElements = function() { 3102 return whiteSpaceElementsMap; 3103 }; 3104 3105 self.isValidChild = function(name, child) { 3106 var parent = children[name]; 3107 3108 return !!(parent && parent[child]); 3109 }; 3110 3111 self.isValid = function(name, attr) { 3112 var attrPatterns, i, rule = getElementRule(name); 3113 3114 // Check if it's a valid element 3115 if (rule) { 3116 if (attr) { 3117 // Check if attribute name exists 3118 if (rule.attributes[attr]) { 3119 return true; 3120 } 3121 3122 // Check if attribute matches a regexp pattern 3123 attrPatterns = rule.attributePatterns; 3124 if (attrPatterns) { 3125 i = attrPatterns.length; 3126 while (i--) { 3127 if (attrPatterns[i].pattern.test(name)) { 3128 return true; 3129 } 3130 } 3131 } 3132 } else { 3133 return true; 3134 } 3135 } 3136 3137 // No match 3138 return false; 3139 }; 3140 3141 self.getElementRule = getElementRule; 3142 3143 self.getCustomElements = function() { 3144 return customElementsMap; 3145 }; 3146 3147 self.addValidElements = addValidElements; 3148 3149 self.setValidElements = setValidElements; 3150 3151 self.addCustomElements = addCustomElements; 3152 3153 self.addValidChildren = addValidChildren; 3154 3155 self.elements = elements; 3156 }; 3157 })(tinymce); 3158 (function(tinymce) { 3159 tinymce.html.SaxParser = function(settings, schema) { 3160 var self = this, noop = function() {}; 3161 3162 settings = settings || {}; 3163 self.schema = schema = schema || new tinymce.html.Schema(); 3164 3165 if (settings.fix_self_closing !== false) 3166 settings.fix_self_closing = true; 3167 3168 // Add handler functions from settings and setup default handlers 3169 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 3170 if (name) 3171 self[name] = settings[name] || noop; 3172 }); 3173 3174 self.parse = function(html) { 3175 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 3176 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 3177 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 3178 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 3179 3180 function processEndTag(name) { 3181 var pos, i; 3182 3183 // Find position of parent of the same type 3184 pos = stack.length; 3185 while (pos--) { 3186 if (stack[pos].name === name) 3187 break; 3188 } 3189 3190 // Found parent 3191 if (pos >= 0) { 3192 // Close all the open elements 3193 for (i = stack.length - 1; i >= pos; i--) { 3194 name = stack[i]; 3195 3196 if (name.valid) 3197 self.end(name.name); 3198 } 3199 3200 // Remove the open elements from the stack 3201 stack.length = pos; 3202 } 3203 }; 3204 3205 function parseAttribute(match, name, value, val2, val3) { 3206 var attrRule, i; 3207 3208 name = name.toLowerCase(); 3209 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 3210 3211 // Validate name and value 3212 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 3213 attrRule = validAttributesMap[name]; 3214 3215 // Find rule by pattern matching 3216 if (!attrRule && validAttributePatterns) { 3217 i = validAttributePatterns.length; 3218 while (i--) { 3219 attrRule = validAttributePatterns[i]; 3220 if (attrRule.pattern.test(name)) 3221 break; 3222 } 3223 3224 // No rule matched 3225 if (i === -1) 3226 attrRule = null; 3227 } 3228 3229 // No attribute rule found 3230 if (!attrRule) 3231 return; 3232 3233 // Validate value 3234 if (attrRule.validValues && !(value in attrRule.validValues)) 3235 return; 3236 } 3237 3238 // Add attribute to list and map 3239 attrList.map[name] = value; 3240 attrList.push({ 3241 name: name, 3242 value: value 3243 }); 3244 }; 3245 3246 // Precompile RegExps and map objects 3247 tokenRegExp = new RegExp('<(?:' + 3248 '(?:!--([\\w\\W]*?)-->)|' + // Comment 3249 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 3250 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 3251 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 3252 '(?:\\/([^>]+)>)|' + // End element 3253 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 3254 ')', 'g'); 3255 3256 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 3257 specialElements = { 3258 'script' : /<\/script[^>]*>/gi, 3259 'style' : /<\/style[^>]*>/gi, 3260 'noscript' : /<\/noscript[^>]*>/gi 3261 }; 3262 3263 // Setup lookup tables for empty elements and boolean attributes 3264 shortEndedElements = schema.getShortEndedElements(); 3265 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3266 fillAttrsMap = schema.getBoolAttrs(); 3267 validate = settings.validate; 3268 removeInternalElements = settings.remove_internals; 3269 fixSelfClosing = settings.fix_self_closing; 3270 isIE = tinymce.isIE; 3271 invalidPrefixRegExp = /^:/; 3272 3273 while (matches = tokenRegExp.exec(html)) { 3274 // Text 3275 if (index < matches.index) 3276 self.text(decode(html.substr(index, matches.index - index))); 3277 3278 if (value = matches[6]) { // End element 3279 value = value.toLowerCase(); 3280 3281 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3282 if (isIE && invalidPrefixRegExp.test(value)) 3283 value = value.substr(1); 3284 3285 processEndTag(value); 3286 } else if (value = matches[7]) { // Start element 3287 value = value.toLowerCase(); 3288 3289 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3290 if (isIE && invalidPrefixRegExp.test(value)) 3291 value = value.substr(1); 3292 3293 isShortEnded = value in shortEndedElements; 3294 3295 // Is self closing tag for example an <li> after an open <li> 3296 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3297 processEndTag(value); 3298 3299 // Validate element 3300 if (!validate || (elementRule = schema.getElementRule(value))) { 3301 isValidElement = true; 3302 3303 // Grab attributes map and patters when validation is enabled 3304 if (validate) { 3305 validAttributesMap = elementRule.attributes; 3306 validAttributePatterns = elementRule.attributePatterns; 3307 } 3308 3309 // Parse attributes 3310 if (attribsValue = matches[8]) { 3311 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3312 3313 // If the element has internal attributes then remove it if we are told to do so 3314 if (isInternalElement && removeInternalElements) 3315 isValidElement = false; 3316 3317 attrList = []; 3318 attrList.map = {}; 3319 3320 attribsValue.replace(attrRegExp, parseAttribute); 3321 } else { 3322 attrList = []; 3323 attrList.map = {}; 3324 } 3325 3326 // Process attributes if validation is enabled 3327 if (validate && !isInternalElement) { 3328 attributesRequired = elementRule.attributesRequired; 3329 attributesDefault = elementRule.attributesDefault; 3330 attributesForced = elementRule.attributesForced; 3331 3332 // Handle forced attributes 3333 if (attributesForced) { 3334 i = attributesForced.length; 3335 while (i--) { 3336 attr = attributesForced[i]; 3337 name = attr.name; 3338 attrValue = attr.value; 3339 3340 if (attrValue === '{$uid}') 3341 attrValue = 'mce_' + idCount++; 3342 3343 attrList.map[name] = attrValue; 3344 attrList.push({name: name, value: attrValue}); 3345 } 3346 } 3347 3348 // Handle default attributes 3349 if (attributesDefault) { 3350 i = attributesDefault.length; 3351 while (i--) { 3352 attr = attributesDefault[i]; 3353 name = attr.name; 3354 3355 if (!(name in attrList.map)) { 3356 attrValue = attr.value; 3357 3358 if (attrValue === '{$uid}') 3359 attrValue = 'mce_' + idCount++; 3360 3361 attrList.map[name] = attrValue; 3362 attrList.push({name: name, value: attrValue}); 3363 } 3364 } 3365 } 3366 3367 // Handle required attributes 3368 if (attributesRequired) { 3369 i = attributesRequired.length; 3370 while (i--) { 3371 if (attributesRequired[i] in attrList.map) 3372 break; 3373 } 3374 3375 // None of the required attributes where found 3376 if (i === -1) 3377 isValidElement = false; 3378 } 3379 3380 // Invalidate element if it's marked as bogus 3381 if (attrList.map['data-mce-bogus']) 3382 isValidElement = false; 3383 } 3384 3385 if (isValidElement) 3386 self.start(value, attrList, isShortEnded); 3387 } else 3388 isValidElement = false; 3389 3390 // Treat script, noscript and style a bit different since they may include code that looks like elements 3391 if (endRegExp = specialElements[value]) { 3392 endRegExp.lastIndex = index = matches.index + matches[0].length; 3393 3394 if (matches = endRegExp.exec(html)) { 3395 if (isValidElement) 3396 text = html.substr(index, matches.index - index); 3397 3398 index = matches.index + matches[0].length; 3399 } else { 3400 text = html.substr(index); 3401 index = html.length; 3402 } 3403 3404 if (isValidElement && text.length > 0) 3405 self.text(text, true); 3406 3407 if (isValidElement) 3408 self.end(value); 3409 3410 tokenRegExp.lastIndex = index; 3411 continue; 3412 } 3413 3414 // Push value on to stack 3415 if (!isShortEnded) { 3416 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3417 stack.push({name: value, valid: isValidElement}); 3418 else if (isValidElement) 3419 self.end(value); 3420 } 3421 } else if (value = matches[1]) { // Comment 3422 self.comment(value); 3423 } else if (value = matches[2]) { // CDATA 3424 self.cdata(value); 3425 } else if (value = matches[3]) { // DOCTYPE 3426 self.doctype(value); 3427 } else if (value = matches[4]) { // PI 3428 self.pi(value, matches[5]); 3429 } 3430 3431 index = matches.index + matches[0].length; 3432 } 3433 3434 // Text 3435 if (index < html.length) 3436 self.text(decode(html.substr(index))); 3437 3438 // Close any open elements 3439 for (i = stack.length - 1; i >= 0; i--) { 3440 value = stack[i]; 3441 3442 if (value.valid) 3443 self.end(value.name); 3444 } 3445 }; 3446 } 3447 })(tinymce); 3448 (function(tinymce) { 3449 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3450 '#text' : 3, 3451 '#comment' : 8, 3452 '#cdata' : 4, 3453 '#pi' : 7, 3454 '#doctype' : 10, 3455 '#document-fragment' : 11 3456 }; 3457 3458 // Walks the tree left/right 3459 function walk(node, root_node, prev) { 3460 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3461 3462 // Walk into nodes if it has a start 3463 if (node[startName]) 3464 return node[startName]; 3465 3466 // Return the sibling if it has one 3467 if (node !== root_node) { 3468 sibling = node[siblingName]; 3469 3470 if (sibling) 3471 return sibling; 3472 3473 // Walk up the parents to look for siblings 3474 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3475 sibling = parent[siblingName]; 3476 3477 if (sibling) 3478 return sibling; 3479 } 3480 } 3481 }; 3482 3483 function Node(name, type) { 3484 this.name = name; 3485 this.type = type; 3486 3487 if (type === 1) { 3488 this.attributes = []; 3489 this.attributes.map = {}; 3490 } 3491 } 3492 3493 tinymce.extend(Node.prototype, { 3494 replace : function(node) { 3495 var self = this; 3496 3497 if (node.parent) 3498 node.remove(); 3499 3500 self.insert(node, self); 3501 self.remove(); 3502 3503 return self; 3504 }, 3505 3506 attr : function(name, value) { 3507 var self = this, attrs, i, undef; 3508 3509 if (typeof name !== "string") { 3510 for (i in name) 3511 self.attr(i, name[i]); 3512 3513 return self; 3514 } 3515 3516 if (attrs = self.attributes) { 3517 if (value !== undef) { 3518 // Remove attribute 3519 if (value === null) { 3520 if (name in attrs.map) { 3521 delete attrs.map[name]; 3522 3523 i = attrs.length; 3524 while (i--) { 3525 if (attrs[i].name === name) { 3526 attrs = attrs.splice(i, 1); 3527 return self; 3528 } 3529 } 3530 } 3531 3532 return self; 3533 } 3534 3535 // Set attribute 3536 if (name in attrs.map) { 3537 // Set attribute 3538 i = attrs.length; 3539 while (i--) { 3540 if (attrs[i].name === name) { 3541 attrs[i].value = value; 3542 break; 3543 } 3544 } 3545 } else 3546 attrs.push({name: name, value: value}); 3547 3548 attrs.map[name] = value; 3549 3550 return self; 3551 } else { 3552 return attrs.map[name]; 3553 } 3554 } 3555 }, 3556 3557 clone : function() { 3558 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3559 3560 // Clone element attributes 3561 if (selfAttrs = self.attributes) { 3562 cloneAttrs = []; 3563 cloneAttrs.map = {}; 3564 3565 for (i = 0, l = selfAttrs.length; i < l; i++) { 3566 selfAttr = selfAttrs[i]; 3567 3568 // Clone everything except id 3569 if (selfAttr.name !== 'id') { 3570 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3571 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3572 } 3573 } 3574 3575 clone.attributes = cloneAttrs; 3576 } 3577 3578 clone.value = self.value; 3579 clone.shortEnded = self.shortEnded; 3580 3581 return clone; 3582 }, 3583 3584 wrap : function(wrapper) { 3585 var self = this; 3586 3587 self.parent.insert(wrapper, self); 3588 wrapper.append(self); 3589 3590 return self; 3591 }, 3592 3593 unwrap : function() { 3594 var self = this, node, next; 3595 3596 for (node = self.firstChild; node; ) { 3597 next = node.next; 3598 self.insert(node, self, true); 3599 node = next; 3600 } 3601 3602 self.remove(); 3603 }, 3604 3605 remove : function() { 3606 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3607 3608 if (parent) { 3609 if (parent.firstChild === self) { 3610 parent.firstChild = next; 3611 3612 if (next) 3613 next.prev = null; 3614 } else { 3615 prev.next = next; 3616 } 3617 3618 if (parent.lastChild === self) { 3619 parent.lastChild = prev; 3620 3621 if (prev) 3622 prev.next = null; 3623 } else { 3624 next.prev = prev; 3625 } 3626 3627 self.parent = self.next = self.prev = null; 3628 } 3629 3630 return self; 3631 }, 3632 3633 append : function(node) { 3634 var self = this, last; 3635 3636 if (node.parent) 3637 node.remove(); 3638 3639 last = self.lastChild; 3640 if (last) { 3641 last.next = node; 3642 node.prev = last; 3643 self.lastChild = node; 3644 } else 3645 self.lastChild = self.firstChild = node; 3646 3647 node.parent = self; 3648 3649 return node; 3650 }, 3651 3652 insert : function(node, ref_node, before) { 3653 var parent; 3654 3655 if (node.parent) 3656 node.remove(); 3657 3658 parent = ref_node.parent || this; 3659 3660 if (before) { 3661 if (ref_node === parent.firstChild) 3662 parent.firstChild = node; 3663 else 3664 ref_node.prev.next = node; 3665 3666 node.prev = ref_node.prev; 3667 node.next = ref_node; 3668 ref_node.prev = node; 3669 } else { 3670 if (ref_node === parent.lastChild) 3671 parent.lastChild = node; 3672 else 3673 ref_node.next.prev = node; 3674 3675 node.next = ref_node.next; 3676 node.prev = ref_node; 3677 ref_node.next = node; 3678 } 3679 3680 node.parent = parent; 3681 3682 return node; 3683 }, 3684 3685 getAll : function(name) { 3686 var self = this, node, collection = []; 3687 3688 for (node = self.firstChild; node; node = walk(node, self)) { 3689 if (node.name === name) 3690 collection.push(node); 3691 } 3692 3693 return collection; 3694 }, 3695 3696 empty : function() { 3697 var self = this, nodes, i, node; 3698 3699 // Remove all children 3700 if (self.firstChild) { 3701 nodes = []; 3702 3703 // Collect the children 3704 for (node = self.firstChild; node; node = walk(node, self)) 3705 nodes.push(node); 3706 3707 // Remove the children 3708 i = nodes.length; 3709 while (i--) { 3710 node = nodes[i]; 3711 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3712 } 3713 } 3714 3715 self.firstChild = self.lastChild = null; 3716 3717 return self; 3718 }, 3719 3720 isEmpty : function(elements) { 3721 var self = this, node = self.firstChild, i, name; 3722 3723 if (node) { 3724 do { 3725 if (node.type === 1) { 3726 // Ignore bogus elements 3727 if (node.attributes.map['data-mce-bogus']) 3728 continue; 3729 3730 // Keep empty elements like <img /> 3731 if (elements[node.name]) 3732 return false; 3733 3734 // Keep elements with data attributes or name attribute like <a name="1"></a> 3735 i = node.attributes.length; 3736 while (i--) { 3737 name = node.attributes[i].name; 3738 if (name === "name" || name.indexOf('data-mce-') === 0) 3739 return false; 3740 } 3741 } 3742 3743 // Keep comments 3744 if (node.type === 8) 3745 return false; 3746 3747 // Keep non whitespace text nodes 3748 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3749 return false; 3750 } while (node = walk(node, self)); 3751 } 3752 3753 return true; 3754 }, 3755 3756 walk : function(prev) { 3757 return walk(this, null, prev); 3758 } 3759 }); 3760 3761 tinymce.extend(Node, { 3762 create : function(name, attrs) { 3763 var node, attrName; 3764 3765 // Create node 3766 node = new Node(name, typeLookup[name] || 1); 3767 3768 // Add attributes if needed 3769 if (attrs) { 3770 for (attrName in attrs) 3771 node.attr(attrName, attrs[attrName]); 3772 } 3773 3774 return node; 3775 } 3776 }); 3777 3778 tinymce.html.Node = Node; 3779 })(tinymce); 3780 (function(tinymce) { 3781 var Node = tinymce.html.Node; 3782 3783 tinymce.html.DomParser = function(settings, schema) { 3784 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3785 3786 settings = settings || {}; 3787 settings.validate = "validate" in settings ? settings.validate : true; 3788 settings.root_name = settings.root_name || 'body'; 3789 self.schema = schema = schema || new tinymce.html.Schema(); 3790 3791 function fixInvalidChildren(nodes) { 3792 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3793 childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; 3794 3795 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3796 nonEmptyElements = schema.getNonEmptyElements(); 3797 textBlockElements = schema.getTextBlockElements(); 3798 3799 for (ni = 0; ni < nodes.length; ni++) { 3800 node = nodes[ni]; 3801 3802 // Already removed or fixed 3803 if (!node.parent || node.fixed) 3804 continue; 3805 3806 // If the invalid element is a text block and the text block is within a parent LI element 3807 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 3808 if (textBlockElements[node.name] && node.parent.name == 'li') { 3809 // Move sibling text blocks after LI element 3810 sibling = node.next; 3811 while (sibling) { 3812 if (textBlockElements[sibling.name]) { 3813 sibling.name = 'li'; 3814 sibling.fixed = true; 3815 node.parent.insert(sibling, node.parent); 3816 } else { 3817 break; 3818 } 3819 3820 sibling = sibling.next; 3821 } 3822 3823 // Unwrap current text block 3824 node.unwrap(node); 3825 continue; 3826 } 3827 3828 // Get list of all parent nodes until we find a valid parent to stick the child into 3829 parents = [node]; 3830 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3831 parents.push(parent); 3832 3833 // Found a suitable parent 3834 if (parent && parents.length > 1) { 3835 // Reverse the array since it makes looping easier 3836 parents.reverse(); 3837 3838 // Clone the related parent and insert that after the moved node 3839 newParent = currentNode = self.filterNode(parents[0].clone()); 3840 3841 // Start cloning and moving children on the left side of the target node 3842 for (i = 0; i < parents.length - 1; i++) { 3843 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3844 tempNode = self.filterNode(parents[i].clone()); 3845 currentNode.append(tempNode); 3846 } else 3847 tempNode = currentNode; 3848 3849 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3850 nextNode = childNode.next; 3851 tempNode.append(childNode); 3852 childNode = nextNode; 3853 } 3854 3855 currentNode = tempNode; 3856 } 3857 3858 if (!newParent.isEmpty(nonEmptyElements)) { 3859 parent.insert(newParent, parents[0], true); 3860 parent.insert(node, newParent); 3861 } else { 3862 parent.insert(node, parents[0], true); 3863 } 3864 3865 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3866 parent = parents[0]; 3867 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3868 parent.empty().remove(); 3869 } 3870 } else if (node.parent) { 3871 // If it's an LI try to find a UL/OL for it or wrap it 3872 if (node.name === 'li') { 3873 sibling = node.prev; 3874 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3875 sibling.append(node); 3876 continue; 3877 } 3878 3879 sibling = node.next; 3880 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3881 sibling.insert(node, sibling.firstChild, true); 3882 continue; 3883 } 3884 3885 node.wrap(self.filterNode(new Node('ul', 1))); 3886 continue; 3887 } 3888 3889 // Try wrapping the element in a DIV 3890 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3891 node.wrap(self.filterNode(new Node('div', 1))); 3892 } else { 3893 // We failed wrapping it, then remove or unwrap it 3894 if (node.name === 'style' || node.name === 'script') 3895 node.empty().remove(); 3896 else 3897 node.unwrap(); 3898 } 3899 } 3900 } 3901 }; 3902 3903 self.filterNode = function(node) { 3904 var i, name, list; 3905 3906 // Run element filters 3907 if (name in nodeFilters) { 3908 list = matchedNodes[name]; 3909 3910 if (list) 3911 list.push(node); 3912 else 3913 matchedNodes[name] = [node]; 3914 } 3915 3916 // Run attribute filters 3917 i = attributeFilters.length; 3918 while (i--) { 3919 name = attributeFilters[i].name; 3920 3921 if (name in node.attributes.map) { 3922 list = matchedAttributes[name]; 3923 3924 if (list) 3925 list.push(node); 3926 else 3927 matchedAttributes[name] = [node]; 3928 } 3929 } 3930 3931 return node; 3932 }; 3933 3934 self.addNodeFilter = function(name, callback) { 3935 tinymce.each(tinymce.explode(name), function(name) { 3936 var list = nodeFilters[name]; 3937 3938 if (!list) 3939 nodeFilters[name] = list = []; 3940 3941 list.push(callback); 3942 }); 3943 }; 3944 3945 self.addAttributeFilter = function(name, callback) { 3946 tinymce.each(tinymce.explode(name), function(name) { 3947 var i; 3948 3949 for (i = 0; i < attributeFilters.length; i++) { 3950 if (attributeFilters[i].name === name) { 3951 attributeFilters[i].callbacks.push(callback); 3952 return; 3953 } 3954 } 3955 3956 attributeFilters.push({name: name, callbacks: [callback]}); 3957 }); 3958 }; 3959 3960 self.parse = function(html, args) { 3961 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3962 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3963 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3964 3965 args = args || {}; 3966 matchedNodes = {}; 3967 matchedAttributes = {}; 3968 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3969 nonEmptyElements = schema.getNonEmptyElements(); 3970 children = schema.children; 3971 validate = settings.validate; 3972 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3973 3974 whiteSpaceElements = schema.getWhiteSpaceElements(); 3975 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3976 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3977 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3978 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3979 3980 function addRootBlocks() { 3981 var node = rootNode.firstChild, next, rootBlockNode; 3982 3983 while (node) { 3984 next = node.next; 3985 3986 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3987 if (!rootBlockNode) { 3988 // Create a new root block element 3989 rootBlockNode = createNode(rootBlockName, 1); 3990 rootNode.insert(rootBlockNode, node); 3991 rootBlockNode.append(node); 3992 } else 3993 rootBlockNode.append(node); 3994 } else { 3995 rootBlockNode = null; 3996 } 3997 3998 node = next; 3999 }; 4000 }; 4001 4002 function createNode(name, type) { 4003 var node = new Node(name, type), list; 4004 4005 if (name in nodeFilters) { 4006 list = matchedNodes[name]; 4007 4008 if (list) 4009 list.push(node); 4010 else 4011 matchedNodes[name] = [node]; 4012 } 4013 4014 return node; 4015 }; 4016 4017 function removeWhitespaceBefore(node) { 4018 var textNode, textVal, sibling; 4019 4020 for (textNode = node.prev; textNode && textNode.type === 3; ) { 4021 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 4022 4023 if (textVal.length > 0) { 4024 textNode.value = textVal; 4025 textNode = textNode.prev; 4026 } else { 4027 sibling = textNode.prev; 4028 textNode.remove(); 4029 textNode = sibling; 4030 } 4031 } 4032 }; 4033 4034 function cloneAndExcludeBlocks(input) { 4035 var name, output = {}; 4036 4037 for (name in input) { 4038 if (name !== 'li' && name != 'p') { 4039 output[name] = input[name]; 4040 } 4041 } 4042 4043 return output; 4044 }; 4045 4046 parser = new tinymce.html.SaxParser({ 4047 validate : validate, 4048 4049 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 4050 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 4051 4052 cdata: function(text) { 4053 node.append(createNode('#cdata', 4)).value = text; 4054 }, 4055 4056 text: function(text, raw) { 4057 var textNode; 4058 4059 // Trim all redundant whitespace on non white space elements 4060 if (!isInWhiteSpacePreservedElement) { 4061 text = text.replace(allWhiteSpaceRegExp, ' '); 4062 4063 if (node.lastChild && blockElements[node.lastChild.name]) 4064 text = text.replace(startWhiteSpaceRegExp, ''); 4065 } 4066 4067 // Do we need to create the node 4068 if (text.length !== 0) { 4069 textNode = createNode('#text', 3); 4070 textNode.raw = !!raw; 4071 node.append(textNode).value = text; 4072 } 4073 }, 4074 4075 comment: function(text) { 4076 node.append(createNode('#comment', 8)).value = text; 4077 }, 4078 4079 pi: function(name, text) { 4080 node.append(createNode(name, 7)).value = text; 4081 removeWhitespaceBefore(node); 4082 }, 4083 4084 doctype: function(text) { 4085 var newNode; 4086 4087 newNode = node.append(createNode('#doctype', 10)); 4088 newNode.value = text; 4089 removeWhitespaceBefore(node); 4090 }, 4091 4092 start: function(name, attrs, empty) { 4093 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 4094 4095 elementRule = validate ? schema.getElementRule(name) : {}; 4096 if (elementRule) { 4097 newNode = createNode(elementRule.outputName || name, 1); 4098 newNode.attributes = attrs; 4099 newNode.shortEnded = empty; 4100 4101 node.append(newNode); 4102 4103 // Check if node is valid child of the parent node is the child is 4104 // unknown we don't collect it since it's probably a custom element 4105 parent = children[node.name]; 4106 if (parent && children[newNode.name] && !parent[newNode.name]) 4107 invalidChildren.push(newNode); 4108 4109 attrFiltersLen = attributeFilters.length; 4110 while (attrFiltersLen--) { 4111 attrName = attributeFilters[attrFiltersLen].name; 4112 4113 if (attrName in attrs.map) { 4114 list = matchedAttributes[attrName]; 4115 4116 if (list) 4117 list.push(newNode); 4118 else 4119 matchedAttributes[attrName] = [newNode]; 4120 } 4121 } 4122 4123 // Trim whitespace before block 4124 if (blockElements[name]) 4125 removeWhitespaceBefore(newNode); 4126 4127 // Change current node if the element wasn't empty i.e not <br /> or <img /> 4128 if (!empty) 4129 node = newNode; 4130 4131 // Check if we are inside a whitespace preserved element 4132 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4133 isInWhiteSpacePreservedElement = true; 4134 } 4135 } 4136 }, 4137 4138 end: function(name) { 4139 var textNode, elementRule, text, sibling, tempNode; 4140 4141 elementRule = validate ? schema.getElementRule(name) : {}; 4142 if (elementRule) { 4143 if (blockElements[name]) { 4144 if (!isInWhiteSpacePreservedElement) { 4145 // Trim whitespace of the first node in a block 4146 textNode = node.firstChild; 4147 if (textNode && textNode.type === 3) { 4148 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4149 4150 // Any characters left after trim or should we remove it 4151 if (text.length > 0) { 4152 textNode.value = text; 4153 textNode = textNode.next; 4154 } else { 4155 sibling = textNode.next; 4156 textNode.remove(); 4157 textNode = sibling; 4158 } 4159 4160 // Remove any pure whitespace siblings 4161 while (textNode && textNode.type === 3) { 4162 text = textNode.value; 4163 sibling = textNode.next; 4164 4165 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4166 textNode.remove(); 4167 textNode = sibling; 4168 } 4169 4170 textNode = sibling; 4171 } 4172 } 4173 4174 // Trim whitespace of the last node in a block 4175 textNode = node.lastChild; 4176 if (textNode && textNode.type === 3) { 4177 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 4178 4179 // Any characters left after trim or should we remove it 4180 if (text.length > 0) { 4181 textNode.value = text; 4182 textNode = textNode.prev; 4183 } else { 4184 sibling = textNode.prev; 4185 textNode.remove(); 4186 textNode = sibling; 4187 } 4188 4189 // Remove any pure whitespace siblings 4190 while (textNode && textNode.type === 3) { 4191 text = textNode.value; 4192 sibling = textNode.prev; 4193 4194 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4195 textNode.remove(); 4196 textNode = sibling; 4197 } 4198 4199 textNode = sibling; 4200 } 4201 } 4202 } 4203 4204 // Trim start white space 4205 // Removed due to: #5424 4206 /*textNode = node.prev; 4207 if (textNode && textNode.type === 3) { 4208 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4209 4210 if (text.length > 0) 4211 textNode.value = text; 4212 else 4213 textNode.remove(); 4214 }*/ 4215 } 4216 4217 // Check if we exited a whitespace preserved element 4218 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4219 isInWhiteSpacePreservedElement = false; 4220 } 4221 4222 // Handle empty nodes 4223 if (elementRule.removeEmpty || elementRule.paddEmpty) { 4224 if (node.isEmpty(nonEmptyElements)) { 4225 if (elementRule.paddEmpty) 4226 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 4227 else { 4228 // Leave nodes that have a name like <a name="name"> 4229 if (!node.attributes.map.name && !node.attributes.map.id) { 4230 tempNode = node.parent; 4231 node.empty().remove(); 4232 node = tempNode; 4233 return; 4234 } 4235 } 4236 } 4237 } 4238 4239 node = node.parent; 4240 } 4241 } 4242 }, schema); 4243 4244 rootNode = node = new Node(args.context || settings.root_name, 11); 4245 4246 parser.parse(html); 4247 4248 // Fix invalid children or report invalid children in a contextual parsing 4249 if (validate && invalidChildren.length) { 4250 if (!args.context) 4251 fixInvalidChildren(invalidChildren); 4252 else 4253 args.invalid = true; 4254 } 4255 4256 // Wrap nodes in the root into block elements if the root is body 4257 if (rootBlockName && rootNode.name == 'body') 4258 addRootBlocks(); 4259 4260 // Run filters only when the contents is valid 4261 if (!args.invalid) { 4262 // Run node filters 4263 for (name in matchedNodes) { 4264 list = nodeFilters[name]; 4265 nodes = matchedNodes[name]; 4266 4267 // Remove already removed children 4268 fi = nodes.length; 4269 while (fi--) { 4270 if (!nodes[fi].parent) 4271 nodes.splice(fi, 1); 4272 } 4273 4274 for (i = 0, l = list.length; i < l; i++) 4275 list[i](nodes, name, args); 4276 } 4277 4278 // Run attribute filters 4279 for (i = 0, l = attributeFilters.length; i < l; i++) { 4280 list = attributeFilters[i]; 4281 4282 if (list.name in matchedAttributes) { 4283 nodes = matchedAttributes[list.name]; 4284 4285 // Remove already removed children 4286 fi = nodes.length; 4287 while (fi--) { 4288 if (!nodes[fi].parent) 4289 nodes.splice(fi, 1); 4290 } 4291 4292 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4293 list.callbacks[fi](nodes, list.name, args); 4294 } 4295 } 4296 } 4297 4298 return rootNode; 4299 }; 4300 4301 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4302 // make it possible to place the caret inside empty blocks. This logic tries to remove 4303 // these elements and keep br elements that where intended to be there intact 4304 if (settings.remove_trailing_brs) { 4305 self.addNodeFilter('br', function(nodes, name) { 4306 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4307 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4308 4309 // Remove brs from body element as well 4310 blockElements.body = 1; 4311 4312 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4313 for (i = 0; i < l; i++) { 4314 node = nodes[i]; 4315 parent = node.parent; 4316 4317 if (blockElements[node.parent.name] && node === parent.lastChild) { 4318 // Loop all nodes to the left of the current node and check for other BR elements 4319 // excluding bookmarks since they are invisible 4320 prev = node.prev; 4321 while (prev) { 4322 prevName = prev.name; 4323 4324 // Ignore bookmarks 4325 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4326 // Found a non BR element 4327 if (prevName !== "br") 4328 break; 4329 4330 // Found another br it's a <br><br> structure then don't remove anything 4331 if (prevName === 'br') { 4332 node = null; 4333 break; 4334 } 4335 } 4336 4337 prev = prev.prev; 4338 } 4339 4340 if (node) { 4341 node.remove(); 4342 4343 // Is the parent to be considered empty after we removed the BR 4344 if (parent.isEmpty(nonEmptyElements)) { 4345 elementRule = schema.getElementRule(parent.name); 4346 4347 // Remove or padd the element depending on schema rule 4348 if (elementRule) { 4349 if (elementRule.removeEmpty) 4350 parent.remove(); 4351 else if (elementRule.paddEmpty) 4352 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4353 } 4354 } 4355 } 4356 } else { 4357 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4358 lastParent = node; 4359 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4360 lastParent = parent; 4361 4362 if (blockElements[parent.name]) { 4363 break; 4364 } 4365 4366 parent = parent.parent; 4367 } 4368 4369 if (lastParent === parent) { 4370 textNode = new tinymce.html.Node('#text', 3); 4371 textNode.value = '\u00a0'; 4372 node.replace(textNode); 4373 } 4374 } 4375 } 4376 }); 4377 } 4378 4379 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4380 if (!settings.allow_html_in_named_anchor) { 4381 self.addAttributeFilter('id,name', function(nodes, name) { 4382 var i = nodes.length, sibling, prevSibling, parent, node; 4383 4384 while (i--) { 4385 node = nodes[i]; 4386 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4387 parent = node.parent; 4388 4389 // Move children after current node 4390 sibling = node.lastChild; 4391 do { 4392 prevSibling = sibling.prev; 4393 parent.insert(sibling, node); 4394 sibling = prevSibling; 4395 } while (sibling); 4396 } 4397 } 4398 }); 4399 } 4400 } 4401 })(tinymce); 4402 tinymce.html.Writer = function(settings) { 4403 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4404 4405 settings = settings || {}; 4406 indent = settings.indent; 4407 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4408 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4409 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4410 htmlOutput = settings.element_format == "html"; 4411 4412 return { 4413 start: function(name, attrs, empty) { 4414 var i, l, attr, value; 4415 4416 if (indent && indentBefore[name] && html.length > 0) { 4417 value = html[html.length - 1]; 4418 4419 if (value.length > 0 && value !== '\n') 4420 html.push('\n'); 4421 } 4422 4423 html.push('<', name); 4424 4425 if (attrs) { 4426 for (i = 0, l = attrs.length; i < l; i++) { 4427 attr = attrs[i]; 4428 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4429 } 4430 } 4431 4432 if (!empty || htmlOutput) 4433 html[html.length] = '>'; 4434 else 4435 html[html.length] = ' />'; 4436 4437 if (empty && indent && indentAfter[name] && html.length > 0) { 4438 value = html[html.length - 1]; 4439 4440 if (value.length > 0 && value !== '\n') 4441 html.push('\n'); 4442 } 4443 }, 4444 4445 end: function(name) { 4446 var value; 4447 4448 /*if (indent && indentBefore[name] && html.length > 0) { 4449 value = html[html.length - 1]; 4450 4451 if (value.length > 0 && value !== '\n') 4452 html.push('\n'); 4453 }*/ 4454 4455 html.push('</', name, '>'); 4456 4457 if (indent && indentAfter[name] && html.length > 0) { 4458 value = html[html.length - 1]; 4459 4460 if (value.length > 0 && value !== '\n') 4461 html.push('\n'); 4462 } 4463 }, 4464 4465 text: function(text, raw) { 4466 if (text.length > 0) 4467 html[html.length] = raw ? text : encode(text); 4468 }, 4469 4470 cdata: function(text) { 4471 html.push('<![CDATA[', text, ']]>'); 4472 }, 4473 4474 comment: function(text) { 4475 html.push('<!--', text, '-->'); 4476 }, 4477 4478 pi: function(name, text) { 4479 if (text) 4480 html.push('<?', name, ' ', text, '?>'); 4481 else 4482 html.push('<?', name, '?>'); 4483 4484 if (indent) 4485 html.push('\n'); 4486 }, 4487 4488 doctype: function(text) { 4489 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4490 }, 4491 4492 reset: function() { 4493 html.length = 0; 4494 }, 4495 4496 getContent: function() { 4497 return html.join('').replace(/\n$/, ''); 4498 } 4499 }; 4500 }; 4501 (function(tinymce) { 4502 tinymce.html.Serializer = function(settings, schema) { 4503 var self = this, writer = new tinymce.html.Writer(settings); 4504 4505 settings = settings || {}; 4506 settings.validate = "validate" in settings ? settings.validate : true; 4507 4508 self.schema = schema = schema || new tinymce.html.Schema(); 4509 self.writer = writer; 4510 4511 self.serialize = function(node) { 4512 var handlers, validate; 4513 4514 validate = settings.validate; 4515 4516 handlers = { 4517 // #text 4518 3: function(node, raw) { 4519 writer.text(node.value, node.raw); 4520 }, 4521 4522 // #comment 4523 8: function(node) { 4524 writer.comment(node.value); 4525 }, 4526 4527 // Processing instruction 4528 7: function(node) { 4529 writer.pi(node.name, node.value); 4530 }, 4531 4532 // Doctype 4533 10: function(node) { 4534 writer.doctype(node.value); 4535 }, 4536 4537 // CDATA 4538 4: function(node) { 4539 writer.cdata(node.value); 4540 }, 4541 4542 // Document fragment 4543 11: function(node) { 4544 if ((node = node.firstChild)) { 4545 do { 4546 walk(node); 4547 } while (node = node.next); 4548 } 4549 } 4550 }; 4551 4552 writer.reset(); 4553 4554 function walk(node) { 4555 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4556 4557 if (!handler) { 4558 name = node.name; 4559 isEmpty = node.shortEnded; 4560 attrs = node.attributes; 4561 4562 // Sort attributes 4563 if (validate && attrs && attrs.length > 1) { 4564 sortedAttrs = []; 4565 sortedAttrs.map = {}; 4566 4567 elementRule = schema.getElementRule(node.name); 4568 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4569 attrName = elementRule.attributesOrder[i]; 4570 4571 if (attrName in attrs.map) { 4572 attrValue = attrs.map[attrName]; 4573 sortedAttrs.map[attrName] = attrValue; 4574 sortedAttrs.push({name: attrName, value: attrValue}); 4575 } 4576 } 4577 4578 for (i = 0, l = attrs.length; i < l; i++) { 4579 attrName = attrs[i].name; 4580 4581 if (!(attrName in sortedAttrs.map)) { 4582 attrValue = attrs.map[attrName]; 4583 sortedAttrs.map[attrName] = attrValue; 4584 sortedAttrs.push({name: attrName, value: attrValue}); 4585 } 4586 } 4587 4588 attrs = sortedAttrs; 4589 } 4590 4591 writer.start(node.name, attrs, isEmpty); 4592 4593 if (!isEmpty) { 4594 if ((node = node.firstChild)) { 4595 do { 4596 walk(node); 4597 } while (node = node.next); 4598 } 4599 4600 writer.end(name); 4601 } 4602 } else 4603 handler(node); 4604 } 4605 4606 // Serialize element and treat all non elements as fragments 4607 if (node.type == 1 && !settings.inner) 4608 walk(node); 4609 else 4610 handlers[11](node); 4611 4612 return writer.getContent(); 4613 }; 4614 } 4615 })(tinymce); 4616 // JSLint defined globals 4617 /*global tinymce:false, window:false */ 4618 4619 tinymce.dom = {}; 4620 4621 (function(namespace, expando) { 4622 var w3cEventModel = !!document.addEventListener; 4623 4624 function addEvent(target, name, callback, capture) { 4625 if (target.addEventListener) { 4626 target.addEventListener(name, callback, capture || false); 4627 } else if (target.attachEvent) { 4628 target.attachEvent('on' + name, callback); 4629 } 4630 } 4631 4632 function removeEvent(target, name, callback, capture) { 4633 if (target.removeEventListener) { 4634 target.removeEventListener(name, callback, capture || false); 4635 } else if (target.detachEvent) { 4636 target.detachEvent('on' + name, callback); 4637 } 4638 } 4639 4640 function fix(original_event, data) { 4641 var name, event = data || {}; 4642 4643 // Dummy function that gets replaced on the delegation state functions 4644 function returnFalse() { 4645 return false; 4646 } 4647 4648 // Dummy function that gets replaced on the delegation state functions 4649 function returnTrue() { 4650 return true; 4651 } 4652 4653 // Copy all properties from the original event 4654 for (name in original_event) { 4655 // layerX/layerY is deprecated in Chrome and produces a warning 4656 if (name !== "layerX" && name !== "layerY") { 4657 event[name] = original_event[name]; 4658 } 4659 } 4660 4661 // Normalize target IE uses srcElement 4662 if (!event.target) { 4663 event.target = event.srcElement || document; 4664 } 4665 4666 // Add preventDefault method 4667 event.preventDefault = function() { 4668 event.isDefaultPrevented = returnTrue; 4669 4670 // Execute preventDefault on the original event object 4671 if (original_event) { 4672 if (original_event.preventDefault) { 4673 original_event.preventDefault(); 4674 } else { 4675 original_event.returnValue = false; // IE 4676 } 4677 } 4678 }; 4679 4680 // Add stopPropagation 4681 event.stopPropagation = function() { 4682 event.isPropagationStopped = returnTrue; 4683 4684 // Execute stopPropagation on the original event object 4685 if (original_event) { 4686 if (original_event.stopPropagation) { 4687 original_event.stopPropagation(); 4688 } else { 4689 original_event.cancelBubble = true; // IE 4690 } 4691 } 4692 }; 4693 4694 // Add stopImmediatePropagation 4695 event.stopImmediatePropagation = function() { 4696 event.isImmediatePropagationStopped = returnTrue; 4697 event.stopPropagation(); 4698 }; 4699 4700 // Add event delegation states 4701 if (!event.isDefaultPrevented) { 4702 event.isDefaultPrevented = returnFalse; 4703 event.isPropagationStopped = returnFalse; 4704 event.isImmediatePropagationStopped = returnFalse; 4705 } 4706 4707 return event; 4708 } 4709 4710 function bindOnReady(win, callback, event_utils) { 4711 var doc = win.document, event = {type: 'ready'}; 4712 4713 // Gets called when the DOM is ready 4714 function readyHandler() { 4715 if (!event_utils.domLoaded) { 4716 event_utils.domLoaded = true; 4717 callback(event); 4718 } 4719 } 4720 4721 // Page already loaded then fire it directly 4722 if (doc.readyState == "complete") { 4723 readyHandler(); 4724 return; 4725 } 4726 4727 // Use W3C method 4728 if (w3cEventModel) { 4729 addEvent(win, 'DOMContentLoaded', readyHandler); 4730 } else { 4731 // Use IE method 4732 addEvent(doc, "readystatechange", function() { 4733 if (doc.readyState === "complete") { 4734 removeEvent(doc, "readystatechange", arguments.callee); 4735 readyHandler(); 4736 } 4737 }); 4738 4739 // Wait until we can scroll, when we can the DOM is initialized 4740 if (doc.documentElement.doScroll && win === win.top) { 4741 (function() { 4742 try { 4743 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4744 // http://javascript.nwbox.com/IEContentLoaded/ 4745 doc.documentElement.doScroll("left"); 4746 } catch (ex) { 4747 setTimeout(arguments.callee, 0); 4748 return; 4749 } 4750 4751 readyHandler(); 4752 })(); 4753 } 4754 } 4755 4756 // Fallback if any of the above methods should fail for some odd reason 4757 addEvent(win, 'load', readyHandler); 4758 } 4759 4760 function EventUtils(proxy) { 4761 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4762 4763 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4764 hasFocusIn = "onfocusin" in document.documentElement; 4765 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4766 count = 1; 4767 4768 // State if the DOMContentLoaded was executed or not 4769 self.domLoaded = false; 4770 self.events = events; 4771 4772 function executeHandlers(evt, id) { 4773 var callbackList, i, l, callback; 4774 4775 callbackList = events[id][evt.type]; 4776 if (callbackList) { 4777 for (i = 0, l = callbackList.length; i < l; i++) { 4778 callback = callbackList[i]; 4779 4780 // Check if callback exists might be removed if a unbind is called inside the callback 4781 if (callback && callback.func.call(callback.scope, evt) === false) { 4782 evt.preventDefault(); 4783 } 4784 4785 // Should we stop propagation to immediate listeners 4786 if (evt.isImmediatePropagationStopped()) { 4787 return; 4788 } 4789 } 4790 } 4791 } 4792 4793 self.bind = function(target, names, callback, scope) { 4794 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4795 4796 // Native event handler function patches the event and executes the callbacks for the expando 4797 function defaultNativeHandler(evt) { 4798 executeHandlers(fix(evt || win.event), id); 4799 } 4800 4801 // Don't bind to text nodes or comments 4802 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4803 return; 4804 } 4805 4806 // Create or get events id for the target 4807 if (!target[expando]) { 4808 id = count++; 4809 target[expando] = id; 4810 events[id] = {}; 4811 } else { 4812 id = target[expando]; 4813 4814 if (!events[id]) { 4815 events[id] = {}; 4816 } 4817 } 4818 4819 // Setup the specified scope or use the target as a default 4820 scope = scope || target; 4821 4822 // Split names and bind each event, enables you to bind multiple events with one call 4823 names = names.split(' '); 4824 i = names.length; 4825 while (i--) { 4826 name = names[i]; 4827 nativeHandler = defaultNativeHandler; 4828 fakeName = capture = false; 4829 4830 // Use ready instead of DOMContentLoaded 4831 if (name === "DOMContentLoaded") { 4832 name = "ready"; 4833 } 4834 4835 // DOM is already ready 4836 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4837 self.domLoaded = true; 4838 callback.call(scope, fix({type: name})); 4839 continue; 4840 } 4841 4842 // Handle mouseenter/mouseleaver 4843 if (!hasMouseEnterLeave) { 4844 fakeName = mouseEnterLeave[name]; 4845 4846 if (fakeName) { 4847 nativeHandler = function(evt) { 4848 var current, related; 4849 4850 current = evt.currentTarget; 4851 related = evt.relatedTarget; 4852 4853 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4854 if (related && current.contains) { 4855 // Use contains for performance 4856 related = current.contains(related); 4857 } else { 4858 while (related && related !== current) { 4859 related = related.parentNode; 4860 } 4861 } 4862 4863 // Fire fake event 4864 if (!related) { 4865 evt = fix(evt || win.event); 4866 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4867 evt.target = current; 4868 executeHandlers(evt, id); 4869 } 4870 }; 4871 } 4872 } 4873 4874 // Fake bubbeling of focusin/focusout 4875 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4876 capture = true; 4877 fakeName = name === "focusin" ? "focus" : "blur"; 4878 nativeHandler = function(evt) { 4879 evt = fix(evt || win.event); 4880 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4881 executeHandlers(evt, id); 4882 }; 4883 } 4884 4885 // Setup callback list and bind native event 4886 callbackList = events[id][name]; 4887 if (!callbackList) { 4888 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4889 callbackList.fakeName = fakeName; 4890 callbackList.capture = capture; 4891 4892 // Add the nativeHandler to the callback list so that we can later unbind it 4893 callbackList.nativeHandler = nativeHandler; 4894 if (!w3cEventModel) { 4895 callbackList.proxyHandler = proxy(id); 4896 } 4897 4898 // Check if the target has native events support 4899 if (name === "ready") { 4900 bindOnReady(target, nativeHandler, self); 4901 } else { 4902 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4903 } 4904 } else { 4905 // If it already has an native handler then just push the callback 4906 callbackList.push({func: callback, scope: scope}); 4907 } 4908 } 4909 4910 target = callbackList = 0; // Clean memory for IE 4911 4912 return callback; 4913 }; 4914 4915 self.unbind = function(target, names, callback) { 4916 var id, callbackList, i, ci, name, eventMap; 4917 4918 // Don't bind to text nodes or comments 4919 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4920 return self; 4921 } 4922 4923 // Unbind event or events if the target has the expando 4924 id = target[expando]; 4925 if (id) { 4926 eventMap = events[id]; 4927 4928 // Specific callback 4929 if (names) { 4930 names = names.split(' '); 4931 i = names.length; 4932 while (i--) { 4933 name = names[i]; 4934 callbackList = eventMap[name]; 4935 4936 // Unbind the event if it exists in the map 4937 if (callbackList) { 4938 // Remove specified callback 4939 if (callback) { 4940 ci = callbackList.length; 4941 while (ci--) { 4942 if (callbackList[ci].func === callback) { 4943 callbackList.splice(ci, 1); 4944 } 4945 } 4946 } 4947 4948 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4949 if (!callback || callbackList.length === 0) { 4950 delete eventMap[name]; 4951 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4952 } 4953 } 4954 } 4955 } else { 4956 // All events for a specific element 4957 for (name in eventMap) { 4958 callbackList = eventMap[name]; 4959 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4960 } 4961 4962 eventMap = {}; 4963 } 4964 4965 // Check if object is empty, if it isn't then we won't remove the expando map 4966 for (name in eventMap) { 4967 return self; 4968 } 4969 4970 // Delete event object 4971 delete events[id]; 4972 4973 // Remove expando from target 4974 try { 4975 // IE will fail here since it can't delete properties from window 4976 delete target[expando]; 4977 } catch (ex) { 4978 // IE will set it to null 4979 target[expando] = null; 4980 } 4981 } 4982 4983 return self; 4984 }; 4985 4986 self.fire = function(target, name, args) { 4987 var id, event; 4988 4989 // Don't bind to text nodes or comments 4990 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4991 return self; 4992 } 4993 4994 // Build event object by patching the args 4995 event = fix(null, args); 4996 event.type = name; 4997 4998 do { 4999 // Found an expando that means there is listeners to execute 5000 id = target[expando]; 5001 if (id) { 5002 executeHandlers(event, id); 5003 } 5004 5005 // Walk up the DOM 5006 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 5007 } while (target && !event.isPropagationStopped()); 5008 5009 return self; 5010 }; 5011 5012 self.clean = function(target) { 5013 var i, children, unbind = self.unbind; 5014 5015 // Don't bind to text nodes or comments 5016 if (!target || target.nodeType === 3 || target.nodeType === 8) { 5017 return self; 5018 } 5019 5020 // Unbind any element on the specificed target 5021 if (target[expando]) { 5022 unbind(target); 5023 } 5024 5025 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 5026 if (!target.getElementsByTagName) { 5027 target = target.document; 5028 } 5029 5030 // Remove events from each child element 5031 if (target && target.getElementsByTagName) { 5032 unbind(target); 5033 5034 children = target.getElementsByTagName('*'); 5035 i = children.length; 5036 while (i--) { 5037 target = children[i]; 5038 5039 if (target[expando]) { 5040 unbind(target); 5041 } 5042 } 5043 } 5044 5045 return self; 5046 }; 5047 5048 self.callNativeHandler = function(id, evt) { 5049 if (events) { 5050 events[id][evt.type].nativeHandler(evt); 5051 } 5052 }; 5053 5054 self.destory = function() { 5055 events = {}; 5056 }; 5057 5058 // Legacy function calls 5059 5060 self.add = function(target, events, func, scope) { 5061 // Old API supported direct ID assignment 5062 if (typeof(target) === "string") { 5063 target = document.getElementById(target); 5064 } 5065 5066 // Old API supported multiple targets 5067 if (target && target instanceof Array) { 5068 var i = target.length; 5069 5070 while (i--) { 5071 self.add(target[i], events, func, scope); 5072 } 5073 5074 return; 5075 } 5076 5077 // Old API called ready init 5078 if (events === "init") { 5079 events = "ready"; 5080 } 5081 5082 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 5083 }; 5084 5085 self.remove = function(target, events, func, scope) { 5086 if (!target) { 5087 return self; 5088 } 5089 5090 // Old API supported direct ID assignment 5091 if (typeof(target) === "string") { 5092 target = document.getElementById(target); 5093 } 5094 5095 // Old API supported multiple targets 5096 if (target instanceof Array) { 5097 var i = target.length; 5098 5099 while (i--) { 5100 self.remove(target[i], events, func, scope); 5101 } 5102 5103 return self; 5104 } 5105 5106 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 5107 }; 5108 5109 self.clear = function(target) { 5110 // Old API supported direct ID assignment 5111 if (typeof(target) === "string") { 5112 target = document.getElementById(target); 5113 } 5114 5115 return self.clean(target); 5116 }; 5117 5118 self.cancel = function(e) { 5119 if (e) { 5120 self.prevent(e); 5121 self.stop(e); 5122 } 5123 5124 return false; 5125 }; 5126 5127 self.prevent = function(e) { 5128 if (!e.preventDefault) { 5129 e = fix(e); 5130 } 5131 5132 e.preventDefault(); 5133 5134 return false; 5135 }; 5136 5137 self.stop = function(e) { 5138 if (!e.stopPropagation) { 5139 e = fix(e); 5140 } 5141 5142 e.stopPropagation(); 5143 5144 return false; 5145 }; 5146 } 5147 5148 namespace.EventUtils = EventUtils; 5149 5150 namespace.Event = new EventUtils(function(id) { 5151 return function(evt) { 5152 tinymce.dom.Event.callNativeHandler(id, evt); 5153 }; 5154 }); 5155 5156 // Bind ready event when tinymce script is loaded 5157 namespace.Event.bind(window, 'ready', function() {}); 5158 5159 namespace = 0; 5160 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 5161 tinymce.dom.TreeWalker = function(start_node, root_node) { 5162 var node = start_node; 5163 5164 function findSibling(node, start_name, sibling_name, shallow) { 5165 var sibling, parent; 5166 5167 if (node) { 5168 // Walk into nodes if it has a start 5169 if (!shallow && node[start_name]) 5170 return node[start_name]; 5171 5172 // Return the sibling if it has one 5173 if (node != root_node) { 5174 sibling = node[sibling_name]; 5175 if (sibling) 5176 return sibling; 5177 5178 // Walk up the parents to look for siblings 5179 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 5180 sibling = parent[sibling_name]; 5181 if (sibling) 5182 return sibling; 5183 } 5184 } 5185 } 5186 }; 5187 5188 this.current = function() { 5189 return node; 5190 }; 5191 5192 this.next = function(shallow) { 5193 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 5194 }; 5195 5196 this.prev = function(shallow) { 5197 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 5198 }; 5199 }; 5200 (function(tinymce) { 5201 // Shorten names 5202 var each = tinymce.each, 5203 is = tinymce.is, 5204 isWebKit = tinymce.isWebKit, 5205 isIE = tinymce.isIE, 5206 Entities = tinymce.html.Entities, 5207 simpleSelectorRe = /^([a-z0-9],?)+$/i, 5208 whiteSpaceRegExp = /^[ \t\r\n]*$/; 5209 5210 tinymce.create('tinymce.dom.DOMUtils', { 5211 doc : null, 5212 root : null, 5213 files : null, 5214 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 5215 props : { 5216 "for" : "htmlFor", 5217 "class" : "className", 5218 className : "className", 5219 checked : "checked", 5220 disabled : "disabled", 5221 maxlength : "maxLength", 5222 readonly : "readOnly", 5223 selected : "selected", 5224 value : "value", 5225 id : "id", 5226 name : "name", 5227 type : "type" 5228 }, 5229 5230 DOMUtils : function(d, s) { 5231 var t = this, globalStyle, name, blockElementsMap; 5232 5233 t.doc = d; 5234 t.win = window; 5235 t.files = {}; 5236 t.cssFlicker = false; 5237 t.counter = 0; 5238 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 5239 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 5240 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 5241 5242 t.settings = s = tinymce.extend({ 5243 keep_values : false, 5244 hex_colors : 1 5245 }, s); 5246 5247 t.schema = s.schema; 5248 t.styles = new tinymce.html.Styles({ 5249 url_converter : s.url_converter, 5250 url_converter_scope : s.url_converter_scope 5251 }, s.schema); 5252 5253 // Fix IE6SP2 flicker and check it failed for pre SP2 5254 if (tinymce.isIE6) { 5255 try { 5256 d.execCommand('BackgroundImageCache', false, true); 5257 } catch (e) { 5258 t.cssFlicker = true; 5259 } 5260 } 5261 5262 t.fixDoc(d); 5263 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 5264 tinymce.addUnload(t.destroy, t); 5265 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 5266 5267 t.isBlock = function(node) { 5268 // Fix for #5446 5269 if (!node) { 5270 return false; 5271 } 5272 5273 // This function is called in module pattern style since it might be executed with the wrong this scope 5274 var type = node.nodeType; 5275 5276 // If it's a node then check the type and use the nodeName 5277 if (type) 5278 return !!(type === 1 && blockElementsMap[node.nodeName]); 5279 5280 return !!blockElementsMap[node]; 5281 }; 5282 }, 5283 5284 fixDoc: function(doc) { 5285 var settings = this.settings, name; 5286 5287 if (isIE && !tinymce.isIE11 && settings.schema) { 5288 // Add missing HTML 4/5 elements to IE 5289 ('abbr article aside audio canvas ' + 5290 'details figcaption figure footer ' + 5291 'header hgroup mark menu meter nav ' + 5292 'output progress section summary ' + 5293 'time video').replace(/\w+/g, function(name) { 5294 doc.createElement(name); 5295 }); 5296 5297 // Create all custom elements 5298 for (name in settings.schema.getCustomElements()) { 5299 doc.createElement(name); 5300 } 5301 } 5302 }, 5303 5304 clone: function(node, deep) { 5305 var self = this, clone, doc; 5306 5307 // TODO: Add feature detection here in the future 5308 if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) { 5309 return node.cloneNode(deep); 5310 } 5311 5312 doc = self.doc; 5313 5314 // Make a HTML5 safe shallow copy 5315 if (!deep) { 5316 clone = doc.createElement(node.nodeName); 5317 5318 // Copy attribs 5319 each(self.getAttribs(node), function(attr) { 5320 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5321 }); 5322 5323 return clone; 5324 } 5325 /* 5326 // Setup HTML5 patched document fragment 5327 if (!self.frag) { 5328 self.frag = doc.createDocumentFragment(); 5329 self.fixDoc(self.frag); 5330 } 5331 5332 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5333 clone = doc.createElement('div'); 5334 self.frag.appendChild(clone); 5335 clone.innerHTML = node.outerHTML; 5336 self.frag.removeChild(clone); 5337 */ 5338 return clone.firstChild; 5339 }, 5340 5341 getRoot : function() { 5342 var t = this, s = t.settings; 5343 5344 return (s && t.get(s.root_element)) || t.doc.body; 5345 }, 5346 5347 getViewPort : function(w) { 5348 var d, b; 5349 5350 w = !w ? this.win : w; 5351 d = w.document; 5352 b = this.boxModel ? d.documentElement : d.body; 5353 5354 // Returns viewport size excluding scrollbars 5355 return { 5356 x : w.pageXOffset || b.scrollLeft, 5357 y : w.pageYOffset || b.scrollTop, 5358 w : w.innerWidth || b.clientWidth, 5359 h : w.innerHeight || b.clientHeight 5360 }; 5361 }, 5362 5363 getRect : function(e) { 5364 var p, t = this, sr; 5365 5366 e = t.get(e); 5367 p = t.getPos(e); 5368 sr = t.getSize(e); 5369 5370 return { 5371 x : p.x, 5372 y : p.y, 5373 w : sr.w, 5374 h : sr.h 5375 }; 5376 }, 5377 5378 getSize : function(e) { 5379 var t = this, w, h; 5380 5381 e = t.get(e); 5382 w = t.getStyle(e, 'width'); 5383 h = t.getStyle(e, 'height'); 5384 5385 // Non pixel value, then force offset/clientWidth 5386 if (w.indexOf('px') === -1) 5387 w = 0; 5388 5389 // Non pixel value, then force offset/clientWidth 5390 if (h.indexOf('px') === -1) 5391 h = 0; 5392 5393 return { 5394 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5395 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5396 }; 5397 }, 5398 5399 getParent : function(n, f, r) { 5400 return this.getParents(n, f, r, false); 5401 }, 5402 5403 getParents : function(n, f, r, c) { 5404 var t = this, na, se = t.settings, o = []; 5405 5406 n = t.get(n); 5407 c = c === undefined; 5408 5409 if (se.strict_root) 5410 r = r || t.getRoot(); 5411 5412 // Wrap node name as func 5413 if (is(f, 'string')) { 5414 na = f; 5415 5416 if (f === '*') { 5417 f = function(n) {return n.nodeType == 1;}; 5418 } else { 5419 f = function(n) { 5420 return t.is(n, na); 5421 }; 5422 } 5423 } 5424 5425 while (n) { 5426 if (n == r || !n.nodeType || n.nodeType === 9) 5427 break; 5428 5429 if (!f || f(n)) { 5430 if (c) 5431 o.push(n); 5432 else 5433 return n; 5434 } 5435 5436 n = n.parentNode; 5437 } 5438 5439 return c ? o : null; 5440 }, 5441 5442 get : function(e) { 5443 var n; 5444 5445 if (e && this.doc && typeof(e) == 'string') { 5446 n = e; 5447 e = this.doc.getElementById(e); 5448 5449 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5450 if (e && e.id !== n) 5451 return this.doc.getElementsByName(n)[1]; 5452 } 5453 5454 return e; 5455 }, 5456 5457 getNext : function(node, selector) { 5458 return this._findSib(node, selector, 'nextSibling'); 5459 }, 5460 5461 getPrev : function(node, selector) { 5462 return this._findSib(node, selector, 'previousSibling'); 5463 }, 5464 5465 5466 select : function(pa, s) { 5467 var t = this; 5468 5469 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5470 }, 5471 5472 is : function(n, selector) { 5473 var i; 5474 5475 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5476 if (n.length === undefined) { 5477 // Simple all selector 5478 if (selector === '*') 5479 return n.nodeType == 1; 5480 5481 // Simple selector just elements 5482 if (simpleSelectorRe.test(selector)) { 5483 selector = selector.toLowerCase().split(/,/); 5484 n = n.nodeName.toLowerCase(); 5485 5486 for (i = selector.length - 1; i >= 0; i--) { 5487 if (selector[i] == n) 5488 return true; 5489 } 5490 5491 return false; 5492 } 5493 } 5494 5495 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5496 }, 5497 5498 5499 add : function(p, n, a, h, c) { 5500 var t = this; 5501 5502 return this.run(p, function(p) { 5503 var e, k; 5504 5505 e = is(n, 'string') ? t.doc.createElement(n) : n; 5506 t.setAttribs(e, a); 5507 5508 if (h) { 5509 if (h.nodeType) 5510 e.appendChild(h); 5511 else 5512 t.setHTML(e, h); 5513 } 5514 5515 return !c ? p.appendChild(e) : e; 5516 }); 5517 }, 5518 5519 create : function(n, a, h) { 5520 return this.add(this.doc.createElement(n), n, a, h, 1); 5521 }, 5522 5523 createHTML : function(n, a, h) { 5524 var o = '', t = this, k; 5525 5526 o += '<' + n; 5527 5528 for (k in a) { 5529 if (a.hasOwnProperty(k)) 5530 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5531 } 5532 5533 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5534 if (typeof(h) != "undefined") 5535 return o + '>' + h + '</' + n + '>'; 5536 5537 return o + ' />'; 5538 }, 5539 5540 remove : function(node, keep_children) { 5541 return this.run(node, function(node) { 5542 var child, parent = node.parentNode; 5543 5544 if (!parent) 5545 return null; 5546 5547 if (keep_children) { 5548 while (child = node.firstChild) { 5549 // IE 8 will crash if you don't remove completely empty text nodes 5550 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5551 parent.insertBefore(child, node); 5552 else 5553 node.removeChild(child); 5554 } 5555 } 5556 5557 return parent.removeChild(node); 5558 }); 5559 }, 5560 5561 setStyle : function(n, na, v) { 5562 var t = this; 5563 5564 return t.run(n, function(e) { 5565 var s, i; 5566 5567 s = e.style; 5568 5569 // Camelcase it, if needed 5570 na = na.replace(/-(\D)/g, function(a, b){ 5571 return b.toUpperCase(); 5572 }); 5573 5574 // Default px suffix on these 5575 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5576 v += 'px'; 5577 5578 switch (na) { 5579 case 'opacity': 5580 // IE specific opacity 5581 if (isIE && ! tinymce.isIE11) { 5582 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5583 5584 if (!n.currentStyle || !n.currentStyle.hasLayout) 5585 s.display = 'inline-block'; 5586 } 5587 5588 // Fix for older browsers 5589 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5590 break; 5591 5592 case 'float': 5593 (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v; 5594 break; 5595 5596 default: 5597 s[na] = v || ''; 5598 } 5599 5600 // Force update of the style data 5601 if (t.settings.update_styles) 5602 t.setAttrib(e, 'data-mce-style'); 5603 }); 5604 }, 5605 5606 getStyle : function(n, na, c) { 5607 n = this.get(n); 5608 5609 if (!n) 5610 return; 5611 5612 // Gecko 5613 if (this.doc.defaultView && c) { 5614 // Remove camelcase 5615 na = na.replace(/[A-Z]/g, function(a){ 5616 return '-' + a; 5617 }); 5618 5619 try { 5620 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5621 } catch (ex) { 5622 // Old safari might fail 5623 return null; 5624 } 5625 } 5626 5627 // Camelcase it, if needed 5628 na = na.replace(/-(\D)/g, function(a, b){ 5629 return b.toUpperCase(); 5630 }); 5631 5632 if (na == 'float') 5633 na = isIE ? 'styleFloat' : 'cssFloat'; 5634 5635 // IE & Opera 5636 if (n.currentStyle && c) 5637 return n.currentStyle[na]; 5638 5639 return n.style ? n.style[na] : undefined; 5640 }, 5641 5642 setStyles : function(e, o) { 5643 var t = this, s = t.settings, ol; 5644 5645 ol = s.update_styles; 5646 s.update_styles = 0; 5647 5648 each(o, function(v, n) { 5649 t.setStyle(e, n, v); 5650 }); 5651 5652 // Update style info 5653 s.update_styles = ol; 5654 if (s.update_styles) 5655 t.setAttrib(e, s.cssText); 5656 }, 5657 5658 removeAllAttribs: function(e) { 5659 return this.run(e, function(e) { 5660 var i, attrs = e.attributes; 5661 for (i = attrs.length - 1; i >= 0; i--) { 5662 e.removeAttributeNode(attrs.item(i)); 5663 } 5664 }); 5665 }, 5666 5667 setAttrib : function(e, n, v) { 5668 var t = this; 5669 5670 // Whats the point 5671 if (!e || !n) 5672 return; 5673 5674 // Strict XML mode 5675 if (t.settings.strict) 5676 n = n.toLowerCase(); 5677 5678 return this.run(e, function(e) { 5679 var s = t.settings; 5680 var originalValue = e.getAttribute(n); 5681 if (v !== null) { 5682 switch (n) { 5683 case "style": 5684 if (!is(v, 'string')) { 5685 each(v, function(v, n) { 5686 t.setStyle(e, n, v); 5687 }); 5688 5689 return; 5690 } 5691 5692 // No mce_style for elements with these since they might get resized by the user 5693 if (s.keep_values) { 5694 if (v && !t._isRes(v)) 5695 e.setAttribute('data-mce-style', v, 2); 5696 else 5697 e.removeAttribute('data-mce-style', 2); 5698 } 5699 5700 e.style.cssText = v; 5701 break; 5702 5703 case "class": 5704 e.className = v || ''; // Fix IE null bug 5705 break; 5706 5707 case "src": 5708 case "href": 5709 if (s.keep_values) { 5710 if (s.url_converter) 5711 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5712 5713 t.setAttrib(e, 'data-mce-' + n, v, 2); 5714 } 5715 5716 break; 5717 5718 case "shape": 5719 e.setAttribute('data-mce-style', v); 5720 break; 5721 } 5722 } 5723 if (is(v) && v !== null && v.length !== 0) 5724 e.setAttribute(n, '' + v, 2); 5725 else 5726 e.removeAttribute(n, 2); 5727 5728 // fire onChangeAttrib event for attributes that have changed 5729 if (tinyMCE.activeEditor && originalValue != v) { 5730 var ed = tinyMCE.activeEditor; 5731 ed.onSetAttrib.dispatch(ed, e, n, v); 5732 } 5733 }); 5734 }, 5735 5736 setAttribs : function(e, o) { 5737 var t = this; 5738 5739 return this.run(e, function(e) { 5740 each(o, function(v, n) { 5741 t.setAttrib(e, n, v); 5742 }); 5743 }); 5744 }, 5745 5746 getAttrib : function(e, n, dv) { 5747 var v, t = this, undef; 5748 5749 e = t.get(e); 5750 5751 if (!e || e.nodeType !== 1) 5752 return dv === undef ? false : dv; 5753 5754 if (!is(dv)) 5755 dv = ''; 5756 5757 // Try the mce variant for these 5758 if (/^(src|href|style|coords|shape)$/.test(n)) { 5759 v = e.getAttribute("data-mce-" + n); 5760 5761 if (v) 5762 return v; 5763 } 5764 5765 if (isIE && t.props[n]) { 5766 v = e[t.props[n]]; 5767 v = v && v.nodeValue ? v.nodeValue : v; 5768 } 5769 5770 if (!v) 5771 v = e.getAttribute(n, 2); 5772 5773 // Check boolean attribs 5774 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5775 if (e[t.props[n]] === true && v === '') 5776 return n; 5777 5778 return v ? n : ''; 5779 } 5780 5781 // Inner input elements will override attributes on form elements 5782 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5783 return e.getAttributeNode(n).nodeValue; 5784 5785 if (n === 'style') { 5786 v = v || e.style.cssText; 5787 5788 if (v) { 5789 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5790 5791 if (t.settings.keep_values && !t._isRes(v)) 5792 e.setAttribute('data-mce-style', v); 5793 } 5794 } 5795 5796 // Remove Apple and WebKit stuff 5797 if (isWebKit && n === "class" && v) 5798 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5799 5800 // Handle IE issues 5801 if (isIE) { 5802 switch (n) { 5803 case 'rowspan': 5804 case 'colspan': 5805 // IE returns 1 as default value 5806 if (v === 1) 5807 v = ''; 5808 5809 break; 5810 5811 case 'size': 5812 // IE returns +0 as default value for size 5813 if (v === '+0' || v === 20 || v === 0) 5814 v = ''; 5815 5816 break; 5817 5818 case 'width': 5819 case 'height': 5820 case 'vspace': 5821 case 'checked': 5822 case 'disabled': 5823 case 'readonly': 5824 if (v === 0) 5825 v = ''; 5826 5827 break; 5828 5829 case 'hspace': 5830 // IE returns -1 as default value 5831 if (v === -1) 5832 v = ''; 5833 5834 break; 5835 5836 case 'maxlength': 5837 case 'tabindex': 5838 // IE returns default value 5839 if (v === 32768 || v === 2147483647 || v === '32768') 5840 v = ''; 5841 5842 break; 5843 5844 case 'multiple': 5845 case 'compact': 5846 case 'noshade': 5847 case 'nowrap': 5848 if (v === 65535) 5849 return n; 5850 5851 return dv; 5852 5853 case 'shape': 5854 v = v.toLowerCase(); 5855 break; 5856 5857 default: 5858 // IE has odd anonymous function for event attributes 5859 if (n.indexOf('on') === 0 && v) 5860 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5861 } 5862 } 5863 5864 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5865 }, 5866 5867 getPos : function(n, ro) { 5868 var t = this, x = 0, y = 0, e, d = t.doc, r; 5869 5870 n = t.get(n); 5871 ro = ro || d.body; 5872 5873 if (n) { 5874 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5875 if (n.getBoundingClientRect) { 5876 n = n.getBoundingClientRect(); 5877 e = t.boxModel ? d.documentElement : d.body; 5878 5879 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5880 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5881 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5882 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5883 5884 return {x : x, y : y}; 5885 } 5886 5887 r = n; 5888 while (r && r != ro && r.nodeType) { 5889 x += r.offsetLeft || 0; 5890 y += r.offsetTop || 0; 5891 r = r.offsetParent; 5892 } 5893 5894 r = n.parentNode; 5895 while (r && r != ro && r.nodeType) { 5896 x -= r.scrollLeft || 0; 5897 y -= r.scrollTop || 0; 5898 r = r.parentNode; 5899 } 5900 } 5901 5902 return {x : x, y : y}; 5903 }, 5904 5905 parseStyle : function(st) { 5906 return this.styles.parse(st); 5907 }, 5908 5909 serializeStyle : function(o, name) { 5910 return this.styles.serialize(o, name); 5911 }, 5912 5913 addStyle: function(cssText) { 5914 var doc = this.doc, head; 5915 5916 // Create style element if needed 5917 styleElm = doc.getElementById('mceDefaultStyles'); 5918 if (!styleElm) { 5919 styleElm = doc.createElement('style'), 5920 styleElm.id = 'mceDefaultStyles'; 5921 styleElm.type = 'text/css'; 5922 5923 head = doc.getElementsByTagName('head')[0]; 5924 if (head.firstChild) { 5925 head.insertBefore(styleElm, head.firstChild); 5926 } else { 5927 head.appendChild(styleElm); 5928 } 5929 } 5930 5931 // Append style data to old or new style element 5932 if (styleElm.styleSheet) { 5933 styleElm.styleSheet.cssText += cssText; 5934 } else { 5935 styleElm.appendChild(doc.createTextNode(cssText)); 5936 } 5937 }, 5938 5939 loadCSS : function(u) { 5940 var t = this, d = t.doc, head; 5941 5942 if (!u) 5943 u = ''; 5944 5945 head = d.getElementsByTagName('head')[0]; 5946 5947 each(u.split(','), function(u) { 5948 var link; 5949 5950 if (t.files[u]) 5951 return; 5952 5953 t.files[u] = true; 5954 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5955 5956 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5957 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5958 // It's ugly but it seems to work fine. 5959 if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) { 5960 link.onload = function() { 5961 if (d.recalc) 5962 d.recalc(); 5963 5964 link.onload = null; 5965 }; 5966 } 5967 5968 head.appendChild(link); 5969 }); 5970 }, 5971 5972 addClass : function(e, c) { 5973 return this.run(e, function(e) { 5974 var o; 5975 5976 if (!c) 5977 return 0; 5978 5979 if (this.hasClass(e, c)) 5980 return e.className; 5981 5982 o = this.removeClass(e, c); 5983 5984 return e.className = (o != '' ? (o + ' ') : '') + c; 5985 }); 5986 }, 5987 5988 removeClass : function(e, c) { 5989 var t = this, re; 5990 5991 return t.run(e, function(e) { 5992 var v; 5993 5994 if (t.hasClass(e, c)) { 5995 if (!re) 5996 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5997 5998 v = e.className.replace(re, ' '); 5999 v = tinymce.trim(v != ' ' ? v : ''); 6000 6001 e.className = v; 6002 6003 // Empty class attr 6004 if (!v) { 6005 e.removeAttribute('class'); 6006 e.removeAttribute('className'); 6007 } 6008 6009 return v; 6010 } 6011 6012 return e.className; 6013 }); 6014 }, 6015 6016 hasClass : function(n, c) { 6017 n = this.get(n); 6018 6019 if (!n || !c) 6020 return false; 6021 6022 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 6023 }, 6024 6025 show : function(e) { 6026 return this.setStyle(e, 'display', 'block'); 6027 }, 6028 6029 hide : function(e) { 6030 return this.setStyle(e, 'display', 'none'); 6031 }, 6032 6033 isHidden : function(e) { 6034 e = this.get(e); 6035 6036 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 6037 }, 6038 6039 uniqueId : function(p) { 6040 return (!p ? 'mce_' : p) + (this.counter++); 6041 }, 6042 6043 setHTML : function(element, html) { 6044 var self = this; 6045 6046 return self.run(element, function(element) { 6047 if (isIE) { 6048 // Remove all child nodes, IE keeps empty text nodes in DOM 6049 while (element.firstChild) 6050 element.removeChild(element.firstChild); 6051 6052 try { 6053 // IE will remove comments from the beginning 6054 // unless you padd the contents with something 6055 element.innerHTML = '<br />' + html; 6056 element.removeChild(element.firstChild); 6057 } catch (ex) { 6058 // 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 6059 // This seems to fix this problem 6060 6061 // Create new div with HTML contents and a BR infront to keep comments 6062 var newElement = self.create('div'); 6063 newElement.innerHTML = '<br />' + html; 6064 6065 // Add all children from div to target 6066 each (tinymce.grep(newElement.childNodes), function(node, i) { 6067 // Skip br element 6068 if (i && element.canHaveHTML) 6069 element.appendChild(node); 6070 }); 6071 } 6072 } else 6073 element.innerHTML = html; 6074 6075 return html; 6076 }); 6077 }, 6078 6079 getOuterHTML : function(elm) { 6080 var doc, self = this; 6081 6082 elm = self.get(elm); 6083 6084 if (!elm) 6085 return null; 6086 6087 if (elm.nodeType === 1 && self.hasOuterHTML) 6088 return elm.outerHTML; 6089 6090 doc = (elm.ownerDocument || self.doc).createElement("body"); 6091 doc.appendChild(elm.cloneNode(true)); 6092 6093 return doc.innerHTML; 6094 }, 6095 6096 setOuterHTML : function(e, h, d) { 6097 var t = this; 6098 6099 function setHTML(e, h, d) { 6100 var n, tp; 6101 6102 tp = d.createElement("body"); 6103 tp.innerHTML = h; 6104 6105 n = tp.lastChild; 6106 while (n) { 6107 t.insertAfter(n.cloneNode(true), e); 6108 n = n.previousSibling; 6109 } 6110 6111 t.remove(e); 6112 }; 6113 6114 return this.run(e, function(e) { 6115 e = t.get(e); 6116 6117 // Only set HTML on elements 6118 if (e.nodeType == 1) { 6119 d = d || e.ownerDocument || t.doc; 6120 6121 if (isIE) { 6122 try { 6123 // Try outerHTML for IE it sometimes produces an unknown runtime error 6124 if (isIE && e.nodeType == 1) 6125 e.outerHTML = h; 6126 else 6127 setHTML(e, h, d); 6128 } catch (ex) { 6129 // Fix for unknown runtime error 6130 setHTML(e, h, d); 6131 } 6132 } else 6133 setHTML(e, h, d); 6134 } 6135 }); 6136 }, 6137 6138 decode : Entities.decode, 6139 6140 encode : Entities.encodeAllRaw, 6141 6142 insertAfter : function(node, reference_node) { 6143 reference_node = this.get(reference_node); 6144 6145 return this.run(node, function(node) { 6146 var parent, nextSibling; 6147 6148 parent = reference_node.parentNode; 6149 nextSibling = reference_node.nextSibling; 6150 6151 if (nextSibling) 6152 parent.insertBefore(node, nextSibling); 6153 else 6154 parent.appendChild(node); 6155 6156 return node; 6157 }); 6158 }, 6159 6160 replace : function(n, o, k) { 6161 var t = this; 6162 6163 if (is(o, 'array')) 6164 n = n.cloneNode(true); 6165 6166 return t.run(o, function(o) { 6167 if (k) { 6168 each(tinymce.grep(o.childNodes), function(c) { 6169 n.appendChild(c); 6170 }); 6171 } 6172 6173 return o.parentNode.replaceChild(n, o); 6174 }); 6175 }, 6176 6177 rename : function(elm, name) { 6178 var t = this, newElm; 6179 6180 if (elm.nodeName != name.toUpperCase()) { 6181 // Rename block element 6182 newElm = t.create(name); 6183 6184 // Copy attribs to new block 6185 each(t.getAttribs(elm), function(attr_node) { 6186 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 6187 }); 6188 6189 // Replace block 6190 t.replace(newElm, elm, 1); 6191 } 6192 6193 return newElm || elm; 6194 }, 6195 6196 findCommonAncestor : function(a, b) { 6197 var ps = a, pe; 6198 6199 while (ps) { 6200 pe = b; 6201 6202 while (pe && ps != pe) 6203 pe = pe.parentNode; 6204 6205 if (ps == pe) 6206 break; 6207 6208 ps = ps.parentNode; 6209 } 6210 6211 if (!ps && a.ownerDocument) 6212 return a.ownerDocument.documentElement; 6213 6214 return ps; 6215 }, 6216 6217 toHex : function(s) { 6218 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 6219 6220 function hex(s) { 6221 s = parseInt(s, 10).toString(16); 6222 6223 return s.length > 1 ? s : '0' + s; // 0 -> 00 6224 }; 6225 6226 if (c) { 6227 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 6228 6229 return s; 6230 } 6231 6232 return s; 6233 }, 6234 6235 getClasses : function() { 6236 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 6237 6238 if (t.classes) 6239 return t.classes; 6240 6241 function addClasses(s) { 6242 // IE style imports 6243 each(s.imports, function(r) { 6244 addClasses(r); 6245 }); 6246 6247 each(s.cssRules || s.rules, function(r) { 6248 // Real type or fake it on IE 6249 switch (r.type || 1) { 6250 // Rule 6251 case 1: 6252 if (r.selectorText) { 6253 each(r.selectorText.split(','), function(v) { 6254 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 6255 6256 // Is internal or it doesn't contain a class 6257 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 6258 return; 6259 6260 // Remove everything but class name 6261 ov = v; 6262 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 6263 6264 // Filter classes 6265 if (f && !(v = f(v, ov))) 6266 return; 6267 6268 if (!lo[v]) { 6269 cl.push({'class' : v}); 6270 lo[v] = 1; 6271 } 6272 }); 6273 } 6274 break; 6275 6276 // Import 6277 case 3: 6278 try { 6279 addClasses(r.styleSheet); 6280 } catch (ex) { 6281 // Ignore 6282 } 6283 6284 break; 6285 } 6286 }); 6287 }; 6288 6289 try { 6290 each(t.doc.styleSheets, addClasses); 6291 } catch (ex) { 6292 // Ignore 6293 } 6294 6295 if (cl.length > 0) 6296 t.classes = cl; 6297 6298 return cl; 6299 }, 6300 6301 run : function(e, f, s) { 6302 var t = this, o; 6303 6304 if (t.doc && typeof(e) === 'string') 6305 e = t.get(e); 6306 6307 if (!e) 6308 return false; 6309 6310 s = s || this; 6311 if (!e.nodeType && (e.length || e.length === 0)) { 6312 o = []; 6313 6314 each(e, function(e, i) { 6315 if (e) { 6316 if (typeof(e) == 'string') 6317 e = t.doc.getElementById(e); 6318 6319 o.push(f.call(s, e, i)); 6320 } 6321 }); 6322 6323 return o; 6324 } 6325 6326 return f.call(s, e); 6327 }, 6328 6329 getAttribs : function(n) { 6330 var o; 6331 6332 n = this.get(n); 6333 6334 if (!n) 6335 return []; 6336 6337 if (isIE) { 6338 o = []; 6339 6340 // Object will throw exception in IE 6341 if (n.nodeName == 'OBJECT') 6342 return n.attributes; 6343 6344 // IE doesn't keep the selected attribute if you clone option elements 6345 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6346 o.push({specified : 1, nodeName : 'selected'}); 6347 6348 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6349 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6350 o.push({specified : 1, nodeName : a}); 6351 }); 6352 6353 return o; 6354 } 6355 6356 return n.attributes; 6357 }, 6358 6359 isEmpty : function(node, elements) { 6360 var self = this, i, attributes, type, walker, name, brCount = 0; 6361 6362 node = node.firstChild; 6363 if (node) { 6364 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6365 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6366 6367 do { 6368 type = node.nodeType; 6369 6370 if (type === 1) { 6371 // Ignore bogus elements 6372 if (node.getAttribute('data-mce-bogus')) 6373 continue; 6374 6375 // Keep empty elements like <img /> 6376 name = node.nodeName.toLowerCase(); 6377 if (elements && elements[name]) { 6378 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6379 if (name === 'br') { 6380 brCount++; 6381 continue; 6382 } 6383 6384 return false; 6385 } 6386 6387 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6388 attributes = self.getAttribs(node); 6389 i = node.attributes.length; 6390 while (i--) { 6391 name = node.attributes[i].nodeName; 6392 if (name === "name" || name === 'data-mce-bookmark') 6393 return false; 6394 } 6395 } 6396 6397 // Keep comment nodes 6398 if (type == 8) 6399 return false; 6400 6401 // Keep non whitespace text nodes 6402 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6403 return false; 6404 } while (node = walker.next()); 6405 } 6406 6407 return brCount <= 1; 6408 }, 6409 6410 destroy : function(s) { 6411 var t = this; 6412 6413 t.win = t.doc = t.root = t.events = t.frag = null; 6414 6415 // Manual destroy then remove unload handler 6416 if (!s) 6417 tinymce.removeUnload(t.destroy); 6418 }, 6419 6420 createRng : function() { 6421 var d = this.doc; 6422 6423 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6424 }, 6425 6426 nodeIndex : function(node, normalized) { 6427 var idx = 0, lastNodeType, lastNode, nodeType; 6428 6429 if (node) { 6430 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6431 nodeType = node.nodeType; 6432 6433 // Normalize text nodes 6434 if (normalized && nodeType == 3) { 6435 if (nodeType == lastNodeType || !node.nodeValue.length) 6436 continue; 6437 } 6438 idx++; 6439 lastNodeType = nodeType; 6440 } 6441 } 6442 6443 return idx; 6444 }, 6445 6446 split : function(pe, e, re) { 6447 var t = this, r = t.createRng(), bef, aft, pa; 6448 6449 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6450 // but we don't want that in our code since it serves no purpose for the end user 6451 // For example if this is chopped: 6452 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6453 // would produce: 6454 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6455 // this function will then trim of empty edges and produce: 6456 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6457 function trim(node) { 6458 var i, children = node.childNodes, type = node.nodeType; 6459 6460 function surroundedBySpans(node) { 6461 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6462 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6463 return previousIsSpan && nextIsSpan; 6464 } 6465 6466 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6467 return; 6468 6469 for (i = children.length - 1; i >= 0; i--) 6470 trim(children[i]); 6471 6472 if (type != 9) { 6473 // Keep non whitespace text nodes 6474 if (type == 3 && node.nodeValue.length > 0) { 6475 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6476 // Also keep text nodes with only spaces if surrounded by spans. 6477 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6478 var trimmedLength = tinymce.trim(node.nodeValue).length; 6479 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6480 return; 6481 } else if (type == 1) { 6482 // If the only child is a bookmark then move it up 6483 children = node.childNodes; 6484 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6485 node.parentNode.insertBefore(children[0], node); 6486 6487 // Keep non empty elements or img, hr etc 6488 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6489 return; 6490 } 6491 6492 t.remove(node); 6493 } 6494 6495 return node; 6496 }; 6497 6498 if (pe && e) { 6499 // Get before chunk 6500 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6501 r.setEnd(e.parentNode, t.nodeIndex(e)); 6502 bef = r.extractContents(); 6503 6504 // Get after chunk 6505 r = t.createRng(); 6506 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6507 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6508 aft = r.extractContents(); 6509 6510 // Insert before chunk 6511 pa = pe.parentNode; 6512 pa.insertBefore(trim(bef), pe); 6513 6514 // Insert middle chunk 6515 if (re) 6516 pa.replaceChild(re, e); 6517 else 6518 pa.insertBefore(e, pe); 6519 6520 // Insert after chunk 6521 pa.insertBefore(trim(aft), pe); 6522 t.remove(pe); 6523 6524 return re || e; 6525 } 6526 }, 6527 6528 bind : function(target, name, func, scope) { 6529 return this.events.add(target, name, func, scope || this); 6530 }, 6531 6532 unbind : function(target, name, func) { 6533 return this.events.remove(target, name, func); 6534 }, 6535 6536 fire : function(target, name, evt) { 6537 return this.events.fire(target, name, evt); 6538 }, 6539 6540 // Returns the content editable state of a node 6541 getContentEditable: function(node) { 6542 var contentEditable; 6543 6544 // Check type 6545 if (node.nodeType != 1) { 6546 return null; 6547 } 6548 6549 // Check for fake content editable 6550 contentEditable = node.getAttribute("data-mce-contenteditable"); 6551 if (contentEditable && contentEditable !== "inherit") { 6552 return contentEditable; 6553 } 6554 6555 // Check for real content editable 6556 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6557 }, 6558 6559 6560 _findSib : function(node, selector, name) { 6561 var t = this, f = selector; 6562 6563 if (node) { 6564 // If expression make a function of it using is 6565 if (is(f, 'string')) { 6566 f = function(node) { 6567 return t.is(node, selector); 6568 }; 6569 } 6570 6571 // Loop all siblings 6572 for (node = node[name]; node; node = node[name]) { 6573 if (f(node)) 6574 return node; 6575 } 6576 } 6577 6578 return null; 6579 }, 6580 6581 _isRes : function(c) { 6582 // Is live resizble element 6583 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6584 } 6585 6586 /* 6587 walk : function(n, f, s) { 6588 var d = this.doc, w; 6589 6590 if (d.createTreeWalker) { 6591 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6592 6593 while ((n = w.nextNode()) != null) 6594 f.call(s || this, n); 6595 } else 6596 tinymce.walk(n, f, 'childNodes', s); 6597 } 6598 */ 6599 6600 /* 6601 toRGB : function(s) { 6602 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6603 6604 if (c) { 6605 // #FFF -> #FFFFFF 6606 if (!is(c[3])) 6607 c[3] = c[2] = c[1]; 6608 6609 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6610 } 6611 6612 return s; 6613 } 6614 */ 6615 }); 6616 6617 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6618 })(tinymce); 6619 (function(ns) { 6620 // Range constructor 6621 function Range(dom) { 6622 var t = this, 6623 doc = dom.doc, 6624 EXTRACT = 0, 6625 CLONE = 1, 6626 DELETE = 2, 6627 TRUE = true, 6628 FALSE = false, 6629 START_OFFSET = 'startOffset', 6630 START_CONTAINER = 'startContainer', 6631 END_CONTAINER = 'endContainer', 6632 END_OFFSET = 'endOffset', 6633 extend = tinymce.extend, 6634 nodeIndex = dom.nodeIndex; 6635 6636 extend(t, { 6637 // Inital states 6638 startContainer : doc, 6639 startOffset : 0, 6640 endContainer : doc, 6641 endOffset : 0, 6642 collapsed : TRUE, 6643 commonAncestorContainer : doc, 6644 6645 // Range constants 6646 START_TO_START : 0, 6647 START_TO_END : 1, 6648 END_TO_END : 2, 6649 END_TO_START : 3, 6650 6651 // Public methods 6652 setStart : setStart, 6653 setEnd : setEnd, 6654 setStartBefore : setStartBefore, 6655 setStartAfter : setStartAfter, 6656 setEndBefore : setEndBefore, 6657 setEndAfter : setEndAfter, 6658 collapse : collapse, 6659 selectNode : selectNode, 6660 selectNodeContents : selectNodeContents, 6661 compareBoundaryPoints : compareBoundaryPoints, 6662 deleteContents : deleteContents, 6663 extractContents : extractContents, 6664 cloneContents : cloneContents, 6665 insertNode : insertNode, 6666 surroundContents : surroundContents, 6667 cloneRange : cloneRange, 6668 toStringIE : toStringIE 6669 }); 6670 6671 function createDocumentFragment() { 6672 return doc.createDocumentFragment(); 6673 }; 6674 6675 function setStart(n, o) { 6676 _setEndPoint(TRUE, n, o); 6677 }; 6678 6679 function setEnd(n, o) { 6680 _setEndPoint(FALSE, n, o); 6681 }; 6682 6683 function setStartBefore(n) { 6684 setStart(n.parentNode, nodeIndex(n)); 6685 }; 6686 6687 function setStartAfter(n) { 6688 setStart(n.parentNode, nodeIndex(n) + 1); 6689 }; 6690 6691 function setEndBefore(n) { 6692 setEnd(n.parentNode, nodeIndex(n)); 6693 }; 6694 6695 function setEndAfter(n) { 6696 setEnd(n.parentNode, nodeIndex(n) + 1); 6697 }; 6698 6699 function collapse(ts) { 6700 if (ts) { 6701 t[END_CONTAINER] = t[START_CONTAINER]; 6702 t[END_OFFSET] = t[START_OFFSET]; 6703 } else { 6704 t[START_CONTAINER] = t[END_CONTAINER]; 6705 t[START_OFFSET] = t[END_OFFSET]; 6706 } 6707 6708 t.collapsed = TRUE; 6709 }; 6710 6711 function selectNode(n) { 6712 setStartBefore(n); 6713 setEndAfter(n); 6714 }; 6715 6716 function selectNodeContents(n) { 6717 setStart(n, 0); 6718 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6719 }; 6720 6721 function compareBoundaryPoints(h, r) { 6722 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6723 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6724 6725 // Check START_TO_START 6726 if (h === 0) 6727 return _compareBoundaryPoints(sc, so, rsc, rso); 6728 6729 // Check START_TO_END 6730 if (h === 1) 6731 return _compareBoundaryPoints(ec, eo, rsc, rso); 6732 6733 // Check END_TO_END 6734 if (h === 2) 6735 return _compareBoundaryPoints(ec, eo, rec, reo); 6736 6737 // Check END_TO_START 6738 if (h === 3) 6739 return _compareBoundaryPoints(sc, so, rec, reo); 6740 }; 6741 6742 function deleteContents() { 6743 _traverse(DELETE); 6744 }; 6745 6746 function extractContents() { 6747 return _traverse(EXTRACT); 6748 }; 6749 6750 function cloneContents() { 6751 return _traverse(CLONE); 6752 }; 6753 6754 function insertNode(n) { 6755 var startContainer = this[START_CONTAINER], 6756 startOffset = this[START_OFFSET], nn, o; 6757 6758 // Node is TEXT_NODE or CDATA 6759 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6760 if (!startOffset) { 6761 // At the start of text 6762 startContainer.parentNode.insertBefore(n, startContainer); 6763 } else if (startOffset >= startContainer.nodeValue.length) { 6764 // At the end of text 6765 dom.insertAfter(n, startContainer); 6766 } else { 6767 // Middle, need to split 6768 nn = startContainer.splitText(startOffset); 6769 startContainer.parentNode.insertBefore(n, nn); 6770 } 6771 } else { 6772 // Insert element node 6773 if (startContainer.childNodes.length > 0) 6774 o = startContainer.childNodes[startOffset]; 6775 6776 if (o) 6777 startContainer.insertBefore(n, o); 6778 else 6779 startContainer.appendChild(n); 6780 } 6781 }; 6782 6783 function surroundContents(n) { 6784 var f = t.extractContents(); 6785 6786 t.insertNode(n); 6787 n.appendChild(f); 6788 t.selectNode(n); 6789 }; 6790 6791 function cloneRange() { 6792 return extend(new Range(dom), { 6793 startContainer : t[START_CONTAINER], 6794 startOffset : t[START_OFFSET], 6795 endContainer : t[END_CONTAINER], 6796 endOffset : t[END_OFFSET], 6797 collapsed : t.collapsed, 6798 commonAncestorContainer : t.commonAncestorContainer 6799 }); 6800 }; 6801 6802 // Private methods 6803 6804 function _getSelectedNode(container, offset) { 6805 var child; 6806 6807 if (container.nodeType == 3 /* TEXT_NODE */) 6808 return container; 6809 6810 if (offset < 0) 6811 return container; 6812 6813 child = container.firstChild; 6814 while (child && offset > 0) { 6815 --offset; 6816 child = child.nextSibling; 6817 } 6818 6819 if (child) 6820 return child; 6821 6822 return container; 6823 }; 6824 6825 function _isCollapsed() { 6826 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6827 }; 6828 6829 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6830 var c, offsetC, n, cmnRoot, childA, childB; 6831 6832 // In the first case the boundary-points have the same container. A is before B 6833 // if its offset is less than the offset of B, A is equal to B if its offset is 6834 // equal to the offset of B, and A is after B if its offset is greater than the 6835 // offset of B. 6836 if (containerA == containerB) { 6837 if (offsetA == offsetB) 6838 return 0; // equal 6839 6840 if (offsetA < offsetB) 6841 return -1; // before 6842 6843 return 1; // after 6844 } 6845 6846 // In the second case a child node C of the container of A is an ancestor 6847 // container of B. In this case, A is before B if the offset of A is less than or 6848 // equal to the index of the child node C and A is after B otherwise. 6849 c = containerB; 6850 while (c && c.parentNode != containerA) 6851 c = c.parentNode; 6852 6853 if (c) { 6854 offsetC = 0; 6855 n = containerA.firstChild; 6856 6857 while (n != c && offsetC < offsetA) { 6858 offsetC++; 6859 n = n.nextSibling; 6860 } 6861 6862 if (offsetA <= offsetC) 6863 return -1; // before 6864 6865 return 1; // after 6866 } 6867 6868 // In the third case a child node C of the container of B is an ancestor container 6869 // of A. In this case, A is before B if the index of the child node C is less than 6870 // the offset of B and A is after B otherwise. 6871 c = containerA; 6872 while (c && c.parentNode != containerB) { 6873 c = c.parentNode; 6874 } 6875 6876 if (c) { 6877 offsetC = 0; 6878 n = containerB.firstChild; 6879 6880 while (n != c && offsetC < offsetB) { 6881 offsetC++; 6882 n = n.nextSibling; 6883 } 6884 6885 if (offsetC < offsetB) 6886 return -1; // before 6887 6888 return 1; // after 6889 } 6890 6891 // In the fourth case, none of three other cases hold: the containers of A and B 6892 // are siblings or descendants of sibling nodes. In this case, A is before B if 6893 // the container of A is before the container of B in a pre-order traversal of the 6894 // Ranges' context tree and A is after B otherwise. 6895 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6896 childA = containerA; 6897 6898 while (childA && childA.parentNode != cmnRoot) 6899 childA = childA.parentNode; 6900 6901 if (!childA) 6902 childA = cmnRoot; 6903 6904 childB = containerB; 6905 while (childB && childB.parentNode != cmnRoot) 6906 childB = childB.parentNode; 6907 6908 if (!childB) 6909 childB = cmnRoot; 6910 6911 if (childA == childB) 6912 return 0; // equal 6913 6914 n = cmnRoot.firstChild; 6915 while (n) { 6916 if (n == childA) 6917 return -1; // before 6918 6919 if (n == childB) 6920 return 1; // after 6921 6922 n = n.nextSibling; 6923 } 6924 }; 6925 6926 function _setEndPoint(st, n, o) { 6927 var ec, sc; 6928 6929 if (st) { 6930 t[START_CONTAINER] = n; 6931 t[START_OFFSET] = o; 6932 } else { 6933 t[END_CONTAINER] = n; 6934 t[END_OFFSET] = o; 6935 } 6936 6937 // If one boundary-point of a Range is set to have a root container 6938 // other than the current one for the Range, the Range is collapsed to 6939 // the new position. This enforces the restriction that both boundary- 6940 // points of a Range must have the same root container. 6941 ec = t[END_CONTAINER]; 6942 while (ec.parentNode) 6943 ec = ec.parentNode; 6944 6945 sc = t[START_CONTAINER]; 6946 while (sc.parentNode) 6947 sc = sc.parentNode; 6948 6949 if (sc == ec) { 6950 // The start position of a Range is guaranteed to never be after the 6951 // end position. To enforce this restriction, if the start is set to 6952 // be at a position after the end, the Range is collapsed to that 6953 // position. 6954 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6955 t.collapse(st); 6956 } else 6957 t.collapse(st); 6958 6959 t.collapsed = _isCollapsed(); 6960 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6961 }; 6962 6963 function _traverse(how) { 6964 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6965 6966 if (t[START_CONTAINER] == t[END_CONTAINER]) 6967 return _traverseSameContainer(how); 6968 6969 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6970 if (p == t[START_CONTAINER]) 6971 return _traverseCommonStartContainer(c, how); 6972 6973 ++endContainerDepth; 6974 } 6975 6976 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6977 if (p == t[END_CONTAINER]) 6978 return _traverseCommonEndContainer(c, how); 6979 6980 ++startContainerDepth; 6981 } 6982 6983 depthDiff = startContainerDepth - endContainerDepth; 6984 6985 startNode = t[START_CONTAINER]; 6986 while (depthDiff > 0) { 6987 startNode = startNode.parentNode; 6988 depthDiff--; 6989 } 6990 6991 endNode = t[END_CONTAINER]; 6992 while (depthDiff < 0) { 6993 endNode = endNode.parentNode; 6994 depthDiff++; 6995 } 6996 6997 // ascend the ancestor hierarchy until we have a common parent. 6998 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6999 startNode = sp; 7000 endNode = ep; 7001 } 7002 7003 return _traverseCommonAncestors(startNode, endNode, how); 7004 }; 7005 7006 function _traverseSameContainer(how) { 7007 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 7008 7009 if (how != DELETE) 7010 frag = createDocumentFragment(); 7011 7012 // If selection is empty, just return the fragment 7013 if (t[START_OFFSET] == t[END_OFFSET]) 7014 return frag; 7015 7016 // Text node needs special case handling 7017 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 7018 // get the substring 7019 s = t[START_CONTAINER].nodeValue; 7020 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 7021 7022 // set the original text node to its new value 7023 if (how != CLONE) { 7024 n = t[START_CONTAINER]; 7025 start = t[START_OFFSET]; 7026 len = t[END_OFFSET] - t[START_OFFSET]; 7027 7028 if (start === 0 && len >= n.nodeValue.length - 1) { 7029 n.parentNode.removeChild(n); 7030 } else { 7031 n.deleteData(start, len); 7032 } 7033 7034 // Nothing is partially selected, so collapse to start point 7035 t.collapse(TRUE); 7036 } 7037 7038 if (how == DELETE) 7039 return; 7040 7041 if (sub.length > 0) { 7042 frag.appendChild(doc.createTextNode(sub)); 7043 } 7044 7045 return frag; 7046 } 7047 7048 // Copy nodes between the start/end offsets. 7049 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 7050 cnt = t[END_OFFSET] - t[START_OFFSET]; 7051 7052 while (n && cnt > 0) { 7053 sibling = n.nextSibling; 7054 xferNode = _traverseFullySelected(n, how); 7055 7056 if (frag) 7057 frag.appendChild( xferNode ); 7058 7059 --cnt; 7060 n = sibling; 7061 } 7062 7063 // Nothing is partially selected, so collapse to start point 7064 if (how != CLONE) 7065 t.collapse(TRUE); 7066 7067 return frag; 7068 }; 7069 7070 function _traverseCommonStartContainer(endAncestor, how) { 7071 var frag, n, endIdx, cnt, sibling, xferNode; 7072 7073 if (how != DELETE) 7074 frag = createDocumentFragment(); 7075 7076 n = _traverseRightBoundary(endAncestor, how); 7077 7078 if (frag) 7079 frag.appendChild(n); 7080 7081 endIdx = nodeIndex(endAncestor); 7082 cnt = endIdx - t[START_OFFSET]; 7083 7084 if (cnt <= 0) { 7085 // Collapse to just before the endAncestor, which 7086 // is partially selected. 7087 if (how != CLONE) { 7088 t.setEndBefore(endAncestor); 7089 t.collapse(FALSE); 7090 } 7091 7092 return frag; 7093 } 7094 7095 n = endAncestor.previousSibling; 7096 while (cnt > 0) { 7097 sibling = n.previousSibling; 7098 xferNode = _traverseFullySelected(n, how); 7099 7100 if (frag) 7101 frag.insertBefore(xferNode, frag.firstChild); 7102 7103 --cnt; 7104 n = sibling; 7105 } 7106 7107 // Collapse to just before the endAncestor, which 7108 // is partially selected. 7109 if (how != CLONE) { 7110 t.setEndBefore(endAncestor); 7111 t.collapse(FALSE); 7112 } 7113 7114 return frag; 7115 }; 7116 7117 function _traverseCommonEndContainer(startAncestor, how) { 7118 var frag, startIdx, n, cnt, sibling, xferNode; 7119 7120 if (how != DELETE) 7121 frag = createDocumentFragment(); 7122 7123 n = _traverseLeftBoundary(startAncestor, how); 7124 if (frag) 7125 frag.appendChild(n); 7126 7127 startIdx = nodeIndex(startAncestor); 7128 ++startIdx; // Because we already traversed it 7129 7130 cnt = t[END_OFFSET] - startIdx; 7131 n = startAncestor.nextSibling; 7132 while (n && cnt > 0) { 7133 sibling = n.nextSibling; 7134 xferNode = _traverseFullySelected(n, how); 7135 7136 if (frag) 7137 frag.appendChild(xferNode); 7138 7139 --cnt; 7140 n = sibling; 7141 } 7142 7143 if (how != CLONE) { 7144 t.setStartAfter(startAncestor); 7145 t.collapse(TRUE); 7146 } 7147 7148 return frag; 7149 }; 7150 7151 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 7152 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 7153 7154 if (how != DELETE) 7155 frag = createDocumentFragment(); 7156 7157 n = _traverseLeftBoundary(startAncestor, how); 7158 if (frag) 7159 frag.appendChild(n); 7160 7161 commonParent = startAncestor.parentNode; 7162 startOffset = nodeIndex(startAncestor); 7163 endOffset = nodeIndex(endAncestor); 7164 ++startOffset; 7165 7166 cnt = endOffset - startOffset; 7167 sibling = startAncestor.nextSibling; 7168 7169 while (cnt > 0) { 7170 nextSibling = sibling.nextSibling; 7171 n = _traverseFullySelected(sibling, how); 7172 7173 if (frag) 7174 frag.appendChild(n); 7175 7176 sibling = nextSibling; 7177 --cnt; 7178 } 7179 7180 n = _traverseRightBoundary(endAncestor, how); 7181 7182 if (frag) 7183 frag.appendChild(n); 7184 7185 if (how != CLONE) { 7186 t.setStartAfter(startAncestor); 7187 t.collapse(TRUE); 7188 } 7189 7190 return frag; 7191 }; 7192 7193 function _traverseRightBoundary(root, how) { 7194 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 7195 7196 if (next == root) 7197 return _traverseNode(next, isFullySelected, FALSE, how); 7198 7199 parent = next.parentNode; 7200 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 7201 7202 while (parent) { 7203 while (next) { 7204 prevSibling = next.previousSibling; 7205 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 7206 7207 if (how != DELETE) 7208 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 7209 7210 isFullySelected = TRUE; 7211 next = prevSibling; 7212 } 7213 7214 if (parent == root) 7215 return clonedParent; 7216 7217 next = parent.previousSibling; 7218 parent = parent.parentNode; 7219 7220 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 7221 7222 if (how != DELETE) 7223 clonedGrandParent.appendChild(clonedParent); 7224 7225 clonedParent = clonedGrandParent; 7226 } 7227 }; 7228 7229 function _traverseLeftBoundary(root, how) { 7230 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 7231 7232 if (next == root) 7233 return _traverseNode(next, isFullySelected, TRUE, how); 7234 7235 parent = next.parentNode; 7236 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 7237 7238 while (parent) { 7239 while (next) { 7240 nextSibling = next.nextSibling; 7241 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 7242 7243 if (how != DELETE) 7244 clonedParent.appendChild(clonedChild); 7245 7246 isFullySelected = TRUE; 7247 next = nextSibling; 7248 } 7249 7250 if (parent == root) 7251 return clonedParent; 7252 7253 next = parent.nextSibling; 7254 parent = parent.parentNode; 7255 7256 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 7257 7258 if (how != DELETE) 7259 clonedGrandParent.appendChild(clonedParent); 7260 7261 clonedParent = clonedGrandParent; 7262 } 7263 }; 7264 7265 function _traverseNode(n, isFullySelected, isLeft, how) { 7266 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 7267 7268 if (isFullySelected) 7269 return _traverseFullySelected(n, how); 7270 7271 if (n.nodeType == 3 /* TEXT_NODE */) { 7272 txtValue = n.nodeValue; 7273 7274 if (isLeft) { 7275 offset = t[START_OFFSET]; 7276 newNodeValue = txtValue.substring(offset); 7277 oldNodeValue = txtValue.substring(0, offset); 7278 } else { 7279 offset = t[END_OFFSET]; 7280 newNodeValue = txtValue.substring(0, offset); 7281 oldNodeValue = txtValue.substring(offset); 7282 } 7283 7284 if (how != CLONE) 7285 n.nodeValue = oldNodeValue; 7286 7287 if (how == DELETE) 7288 return; 7289 7290 newNode = dom.clone(n, FALSE); 7291 newNode.nodeValue = newNodeValue; 7292 7293 return newNode; 7294 } 7295 7296 if (how == DELETE) 7297 return; 7298 7299 return dom.clone(n, FALSE); 7300 }; 7301 7302 function _traverseFullySelected(n, how) { 7303 if (how != DELETE) 7304 return how == CLONE ? dom.clone(n, TRUE) : n; 7305 7306 n.parentNode.removeChild(n); 7307 }; 7308 7309 function toStringIE() { 7310 return dom.create('body', null, cloneContents()).outerText; 7311 } 7312 7313 return t; 7314 }; 7315 7316 ns.Range = Range; 7317 7318 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7319 Range.prototype.toString = function() { 7320 return this.toStringIE(); 7321 }; 7322 })(tinymce.dom); 7323 (function() { 7324 function Selection(selection) { 7325 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7326 7327 function getPosition(rng, start) { 7328 var checkRng, startIndex = 0, endIndex, inside, 7329 children, child, offset, index, position = -1, parent; 7330 7331 // Setup test range, collapse it and get the parent 7332 checkRng = rng.duplicate(); 7333 checkRng.collapse(start); 7334 parent = checkRng.parentElement(); 7335 7336 // Check if the selection is within the right document 7337 if (parent.ownerDocument !== selection.dom.doc) 7338 return; 7339 7340 // IE will report non editable elements as it's parent so look for an editable one 7341 while (parent.contentEditable === "false") { 7342 parent = parent.parentNode; 7343 } 7344 7345 // If parent doesn't have any children then return that we are inside the element 7346 if (!parent.hasChildNodes()) { 7347 return {node : parent, inside : 1}; 7348 } 7349 7350 // Setup node list and endIndex 7351 children = parent.children; 7352 endIndex = children.length - 1; 7353 7354 // Perform a binary search for the position 7355 while (startIndex <= endIndex) { 7356 index = Math.floor((startIndex + endIndex) / 2); 7357 7358 // Move selection to node and compare the ranges 7359 child = children[index]; 7360 checkRng.moveToElementText(child); 7361 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7362 7363 // Before/after or an exact match 7364 if (position > 0) { 7365 endIndex = index - 1; 7366 } else if (position < 0) { 7367 startIndex = index + 1; 7368 } else { 7369 return {node : child}; 7370 } 7371 } 7372 7373 // Check if child position is before or we didn't find a position 7374 if (position < 0) { 7375 // No element child was found use the parent element and the offset inside that 7376 if (!child) { 7377 checkRng.moveToElementText(parent); 7378 checkRng.collapse(true); 7379 child = parent; 7380 inside = true; 7381 } else 7382 checkRng.collapse(false); 7383 7384 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7385 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7386 offset = 0; 7387 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7388 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7389 break; 7390 } 7391 7392 offset++; 7393 } 7394 } else { 7395 // Child position is after the selection endpoint 7396 checkRng.collapse(true); 7397 7398 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7399 offset = 0; 7400 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7401 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7402 break; 7403 } 7404 7405 offset++; 7406 } 7407 } 7408 7409 return {node : child, position : position, offset : offset, inside : inside}; 7410 }; 7411 7412 // Returns a W3C DOM compatible range object by using the IE Range API 7413 function getRange() { 7414 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7415 7416 // If selection is outside the current document just return an empty range 7417 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7418 if (element.ownerDocument != dom.doc) 7419 return domRange; 7420 7421 collapsed = selection.isCollapsed(); 7422 7423 // Handle control selection 7424 if (ieRange.item) { 7425 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7426 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7427 7428 return domRange; 7429 } 7430 7431 function findEndPoint(start) { 7432 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7433 7434 container = endPoint.node; 7435 offset = endPoint.offset; 7436 7437 if (endPoint.inside && !container.hasChildNodes()) { 7438 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7439 return; 7440 } 7441 7442 if (offset === undef) { 7443 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7444 return; 7445 } 7446 7447 if (endPoint.position < 0) { 7448 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7449 7450 if (!sibling) { 7451 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7452 return; 7453 } 7454 7455 if (!offset) { 7456 if (sibling.nodeType == 3) 7457 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7458 else 7459 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7460 7461 return; 7462 } 7463 7464 // Find the text node and offset 7465 while (sibling) { 7466 nodeValue = sibling.nodeValue; 7467 textNodeOffset += nodeValue.length; 7468 7469 // We are at or passed the position we where looking for 7470 if (textNodeOffset >= offset) { 7471 container = sibling; 7472 textNodeOffset -= offset; 7473 textNodeOffset = nodeValue.length - textNodeOffset; 7474 break; 7475 } 7476 7477 sibling = sibling.nextSibling; 7478 } 7479 } else { 7480 // Find the text node and offset 7481 sibling = container.previousSibling; 7482 7483 if (!sibling) 7484 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7485 7486 // If there isn't any text to loop then use the first position 7487 if (!offset) { 7488 if (container.nodeType == 3) 7489 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7490 else 7491 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7492 7493 return; 7494 } 7495 7496 while (sibling) { 7497 textNodeOffset += sibling.nodeValue.length; 7498 7499 // We are at or passed the position we where looking for 7500 if (textNodeOffset >= offset) { 7501 container = sibling; 7502 textNodeOffset -= offset; 7503 break; 7504 } 7505 7506 sibling = sibling.previousSibling; 7507 } 7508 } 7509 7510 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7511 }; 7512 7513 try { 7514 // Find start point 7515 findEndPoint(true); 7516 7517 // Find end point if needed 7518 if (!collapsed) 7519 findEndPoint(); 7520 } catch (ex) { 7521 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7522 // access the nodeValue or other properties of text nodes. This seems to happend when 7523 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7524 if (ex.number == -2147024809) { 7525 // Get the current selection 7526 bookmark = self.getBookmark(2); 7527 7528 // Get start element 7529 tmpRange = ieRange.duplicate(); 7530 tmpRange.collapse(true); 7531 element = tmpRange.parentElement(); 7532 7533 // Get end element 7534 if (!collapsed) { 7535 tmpRange = ieRange.duplicate(); 7536 tmpRange.collapse(false); 7537 element2 = tmpRange.parentElement(); 7538 element2.innerHTML = element2.innerHTML; 7539 } 7540 7541 // Remove the broken elements 7542 element.innerHTML = element.innerHTML; 7543 7544 // Restore the selection 7545 self.moveToBookmark(bookmark); 7546 7547 // Since the range has moved we need to re-get it 7548 ieRange = selection.getRng(); 7549 7550 // Find start point 7551 findEndPoint(true); 7552 7553 // Find end point if needed 7554 if (!collapsed) 7555 findEndPoint(); 7556 } else 7557 throw ex; // Throw other errors 7558 } 7559 7560 return domRange; 7561 }; 7562 7563 this.getBookmark = function(type) { 7564 var rng = selection.getRng(), start, end, bookmark = {}; 7565 7566 function getIndexes(node) { 7567 var parent, root, children, i, indexes = []; 7568 7569 parent = node.parentNode; 7570 root = dom.getRoot().parentNode; 7571 7572 while (parent != root && parent.nodeType !== 9) { 7573 children = parent.children; 7574 7575 i = children.length; 7576 while (i--) { 7577 if (node === children[i]) { 7578 indexes.push(i); 7579 break; 7580 } 7581 } 7582 7583 node = parent; 7584 parent = parent.parentNode; 7585 } 7586 7587 return indexes; 7588 }; 7589 7590 function getBookmarkEndPoint(start) { 7591 var position; 7592 7593 position = getPosition(rng, start); 7594 if (position) { 7595 return { 7596 position : position.position, 7597 offset : position.offset, 7598 indexes : getIndexes(position.node), 7599 inside : position.inside 7600 }; 7601 } 7602 }; 7603 7604 // Non ubstructive bookmark 7605 if (type === 2) { 7606 // Handle text selection 7607 if (!rng.item) { 7608 bookmark.start = getBookmarkEndPoint(true); 7609 7610 if (!selection.isCollapsed()) 7611 bookmark.end = getBookmarkEndPoint(); 7612 } else 7613 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7614 } 7615 7616 return bookmark; 7617 }; 7618 7619 this.moveToBookmark = function(bookmark) { 7620 var rng, body = dom.doc.body; 7621 7622 function resolveIndexes(indexes) { 7623 var node, i, idx, children; 7624 7625 node = dom.getRoot(); 7626 for (i = indexes.length - 1; i >= 0; i--) { 7627 children = node.children; 7628 idx = indexes[i]; 7629 7630 if (idx <= children.length - 1) { 7631 node = children[idx]; 7632 } 7633 } 7634 7635 return node; 7636 }; 7637 7638 function setBookmarkEndPoint(start) { 7639 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7640 7641 if (endPoint) { 7642 moveLeft = endPoint.position > 0; 7643 7644 moveRng = body.createTextRange(); 7645 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7646 7647 offset = endPoint.offset; 7648 if (offset !== undef) { 7649 moveRng.collapse(endPoint.inside || moveLeft); 7650 moveRng.moveStart('character', moveLeft ? -offset : offset); 7651 } else 7652 moveRng.collapse(start); 7653 7654 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7655 7656 if (start) 7657 rng.collapse(true); 7658 } 7659 }; 7660 7661 if (bookmark.start) { 7662 if (bookmark.start.ctrl) { 7663 rng = body.createControlRange(); 7664 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7665 rng.select(); 7666 } else { 7667 rng = body.createTextRange(); 7668 setBookmarkEndPoint(true); 7669 setBookmarkEndPoint(); 7670 rng.select(); 7671 } 7672 } 7673 }; 7674 7675 this.addRange = function(rng) { 7676 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 7677 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 7678 7679 function setEndPoint(start) { 7680 var container, offset, marker, tmpRng, nodes; 7681 7682 marker = dom.create('a'); 7683 container = start ? startContainer : endContainer; 7684 offset = start ? startOffset : endOffset; 7685 tmpRng = ieRng.duplicate(); 7686 7687 if (container == doc || container == doc.documentElement) { 7688 container = body; 7689 offset = 0; 7690 } 7691 7692 if (container.nodeType == 3) { 7693 container.parentNode.insertBefore(marker, container); 7694 tmpRng.moveToElementText(marker); 7695 tmpRng.moveStart('character', offset); 7696 dom.remove(marker); 7697 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7698 } else { 7699 nodes = container.childNodes; 7700 7701 if (nodes.length) { 7702 if (offset >= nodes.length) { 7703 dom.insertAfter(marker, nodes[nodes.length - 1]); 7704 } else { 7705 container.insertBefore(marker, nodes[offset]); 7706 } 7707 7708 tmpRng.moveToElementText(marker); 7709 } else if (container.canHaveHTML) { 7710 // Empty node selection for example <div>|</div> 7711 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7712 container.innerHTML = '<span>\uFEFF</span>'; 7713 marker = container.firstChild; 7714 tmpRng.moveToElementText(marker); 7715 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7716 } 7717 7718 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7719 dom.remove(marker); 7720 } 7721 } 7722 7723 // Setup some shorter versions 7724 startContainer = rng.startContainer; 7725 startOffset = rng.startOffset; 7726 endContainer = rng.endContainer; 7727 endOffset = rng.endOffset; 7728 ieRng = body.createTextRange(); 7729 7730 // If single element selection then try making a control selection out of it 7731 if (startContainer == endContainer && startContainer.nodeType == 1) { 7732 // Trick to place the caret inside an empty block element like <p></p> 7733 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7734 if (startContainer.canHaveHTML) { 7735 // Check if previous sibling is an empty block if it is then we need to render it 7736 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7737 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7738 sibling = startContainer.previousSibling; 7739 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7740 sibling.innerHTML = '\uFEFF'; 7741 } else { 7742 sibling = null; 7743 } 7744 7745 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7746 ieRng.moveToElementText(startContainer.lastChild); 7747 ieRng.select(); 7748 dom.doc.selection.clear(); 7749 startContainer.innerHTML = ''; 7750 7751 if (sibling) { 7752 sibling.innerHTML = ''; 7753 } 7754 return; 7755 } else { 7756 startOffset = dom.nodeIndex(startContainer); 7757 startContainer = startContainer.parentNode; 7758 } 7759 } 7760 7761 if (startOffset == endOffset - 1) { 7762 try { 7763 ctrlElm = startContainer.childNodes[startOffset]; 7764 ctrlRng = body.createControlRange(); 7765 ctrlRng.addElement(ctrlElm); 7766 ctrlRng.select(); 7767 7768 // Check if the range produced is on the correct element and is a control range 7769 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 7770 nativeRng = selection.getRng(); 7771 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 7772 return; 7773 } 7774 } catch (ex) { 7775 // Ignore 7776 } 7777 } 7778 } 7779 7780 // Set start/end point of selection 7781 setEndPoint(true); 7782 setEndPoint(); 7783 7784 // Select the new range and scroll it into view 7785 ieRng.select(); 7786 }; 7787 7788 // Expose range method 7789 this.getRangeAt = getRange; 7790 }; 7791 7792 // Expose the selection object 7793 tinymce.dom.TridentSelection = Selection; 7794 })(); 7795 7796 /* 7797 * Sizzle CSS Selector Engine 7798 * Copyright, The Dojo Foundation 7799 * Released under the MIT, BSD, and GPL Licenses. 7800 * More information: http://sizzlejs.com/ 7801 */ 7802 (function(){ 7803 7804 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7805 expando = "sizcache", 7806 done = 0, 7807 toString = Object.prototype.toString, 7808 hasDuplicate = false, 7809 baseHasDuplicate = true, 7810 rBackslash = /\\/g, 7811 rReturn = /\r\n/g, 7812 rNonWord = /\W/; 7813 7814 // Here we check if the JavaScript engine is using some sort of 7815 // optimization where it does not always call our comparision 7816 // function. If that is the case, discard the hasDuplicate value. 7817 // Thus far that includes Google Chrome. 7818 [0, 0].sort(function() { 7819 baseHasDuplicate = false; 7820 return 0; 7821 }); 7822 7823 var Sizzle = function( selector, context, results, seed ) { 7824 results = results || []; 7825 context = context || document; 7826 7827 var origContext = context; 7828 7829 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7830 return []; 7831 } 7832 7833 if ( !selector || typeof selector !== "string" ) { 7834 return results; 7835 } 7836 7837 var m, set, checkSet, extra, ret, cur, pop, i, 7838 prune = true, 7839 contextXML = Sizzle.isXML( context ), 7840 parts = [], 7841 soFar = selector; 7842 7843 // Reset the position of the chunker regexp (start from head) 7844 do { 7845 chunker.exec( "" ); 7846 m = chunker.exec( soFar ); 7847 7848 if ( m ) { 7849 soFar = m[3]; 7850 7851 parts.push( m[1] ); 7852 7853 if ( m[2] ) { 7854 extra = m[3]; 7855 break; 7856 } 7857 } 7858 } while ( m ); 7859 7860 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7861 7862 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7863 set = posProcess( parts[0] + parts[1], context, seed ); 7864 7865 } else { 7866 set = Expr.relative[ parts[0] ] ? 7867 [ context ] : 7868 Sizzle( parts.shift(), context ); 7869 7870 while ( parts.length ) { 7871 selector = parts.shift(); 7872 7873 if ( Expr.relative[ selector ] ) { 7874 selector += parts.shift(); 7875 } 7876 7877 set = posProcess( selector, set, seed ); 7878 } 7879 } 7880 7881 } else { 7882 // Take a shortcut and set the context if the root selector is an ID 7883 // (but not if it'll be faster if the inner selector is an ID) 7884 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7885 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7886 7887 ret = Sizzle.find( parts.shift(), context, contextXML ); 7888 context = ret.expr ? 7889 Sizzle.filter( ret.expr, ret.set )[0] : 7890 ret.set[0]; 7891 } 7892 7893 if ( context ) { 7894 ret = seed ? 7895 { expr: parts.pop(), set: makeArray(seed) } : 7896 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7897 7898 set = ret.expr ? 7899 Sizzle.filter( ret.expr, ret.set ) : 7900 ret.set; 7901 7902 if ( parts.length > 0 ) { 7903 checkSet = makeArray( set ); 7904 7905 } else { 7906 prune = false; 7907 } 7908 7909 while ( parts.length ) { 7910 cur = parts.pop(); 7911 pop = cur; 7912 7913 if ( !Expr.relative[ cur ] ) { 7914 cur = ""; 7915 } else { 7916 pop = parts.pop(); 7917 } 7918 7919 if ( pop == null ) { 7920 pop = context; 7921 } 7922 7923 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7924 } 7925 7926 } else { 7927 checkSet = parts = []; 7928 } 7929 } 7930 7931 if ( !checkSet ) { 7932 checkSet = set; 7933 } 7934 7935 if ( !checkSet ) { 7936 Sizzle.error( cur || selector ); 7937 } 7938 7939 if ( toString.call(checkSet) === "[object Array]" ) { 7940 if ( !prune ) { 7941 results.push.apply( results, checkSet ); 7942 7943 } else if ( context && context.nodeType === 1 ) { 7944 for ( i = 0; checkSet[i] != null; i++ ) { 7945 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7946 results.push( set[i] ); 7947 } 7948 } 7949 7950 } else { 7951 for ( i = 0; checkSet[i] != null; i++ ) { 7952 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7953 results.push( set[i] ); 7954 } 7955 } 7956 } 7957 7958 } else { 7959 makeArray( checkSet, results ); 7960 } 7961 7962 if ( extra ) { 7963 Sizzle( extra, origContext, results, seed ); 7964 Sizzle.uniqueSort( results ); 7965 } 7966 7967 return results; 7968 }; 7969 7970 Sizzle.uniqueSort = function( results ) { 7971 if ( sortOrder ) { 7972 hasDuplicate = baseHasDuplicate; 7973 results.sort( sortOrder ); 7974 7975 if ( hasDuplicate ) { 7976 for ( var i = 1; i < results.length; i++ ) { 7977 if ( results[i] === results[ i - 1 ] ) { 7978 results.splice( i--, 1 ); 7979 } 7980 } 7981 } 7982 } 7983 7984 return results; 7985 }; 7986 7987 Sizzle.matches = function( expr, set ) { 7988 return Sizzle( expr, null, null, set ); 7989 }; 7990 7991 Sizzle.matchesSelector = function( node, expr ) { 7992 return Sizzle( expr, null, null, [node] ).length > 0; 7993 }; 7994 7995 Sizzle.find = function( expr, context, isXML ) { 7996 var set, i, len, match, type, left; 7997 7998 if ( !expr ) { 7999 return []; 8000 } 8001 8002 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 8003 type = Expr.order[i]; 8004 8005 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 8006 left = match[1]; 8007 match.splice( 1, 1 ); 8008 8009 if ( left.substr( left.length - 1 ) !== "\\" ) { 8010 match[1] = (match[1] || "").replace( rBackslash, "" ); 8011 set = Expr.find[ type ]( match, context, isXML ); 8012 8013 if ( set != null ) { 8014 expr = expr.replace( Expr.match[ type ], "" ); 8015 break; 8016 } 8017 } 8018 } 8019 } 8020 8021 if ( !set ) { 8022 set = typeof context.getElementsByTagName !== "undefined" ? 8023 context.getElementsByTagName( "*" ) : 8024 []; 8025 } 8026 8027 return { set: set, expr: expr }; 8028 }; 8029 8030 Sizzle.filter = function( expr, set, inplace, not ) { 8031 var match, anyFound, 8032 type, found, item, filter, left, 8033 i, pass, 8034 old = expr, 8035 result = [], 8036 curLoop = set, 8037 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 8038 8039 while ( expr && set.length ) { 8040 for ( type in Expr.filter ) { 8041 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 8042 filter = Expr.filter[ type ]; 8043 left = match[1]; 8044 8045 anyFound = false; 8046 8047 match.splice(1,1); 8048 8049 if ( left.substr( left.length - 1 ) === "\\" ) { 8050 continue; 8051 } 8052 8053 if ( curLoop === result ) { 8054 result = []; 8055 } 8056 8057 if ( Expr.preFilter[ type ] ) { 8058 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 8059 8060 if ( !match ) { 8061 anyFound = found = true; 8062 8063 } else if ( match === true ) { 8064 continue; 8065 } 8066 } 8067 8068 if ( match ) { 8069 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 8070 if ( item ) { 8071 found = filter( item, match, i, curLoop ); 8072 pass = not ^ found; 8073 8074 if ( inplace && found != null ) { 8075 if ( pass ) { 8076 anyFound = true; 8077 8078 } else { 8079 curLoop[i] = false; 8080 } 8081 8082 } else if ( pass ) { 8083 result.push( item ); 8084 anyFound = true; 8085 } 8086 } 8087 } 8088 } 8089 8090 if ( found !== undefined ) { 8091 if ( !inplace ) { 8092 curLoop = result; 8093 } 8094 8095 expr = expr.replace( Expr.match[ type ], "" ); 8096 8097 if ( !anyFound ) { 8098 return []; 8099 } 8100 8101 break; 8102 } 8103 } 8104 } 8105 8106 // Improper expression 8107 if ( expr === old ) { 8108 if ( anyFound == null ) { 8109 Sizzle.error( expr ); 8110 8111 } else { 8112 break; 8113 } 8114 } 8115 8116 old = expr; 8117 } 8118 8119 return curLoop; 8120 }; 8121 8122 Sizzle.error = function( msg ) { 8123 throw new Error( "Syntax error, unrecognized expression: " + msg ); 8124 }; 8125 8126 var getText = Sizzle.getText = function( elem ) { 8127 var i, node, 8128 nodeType = elem.nodeType, 8129 ret = ""; 8130 8131 if ( nodeType ) { 8132 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 8133 // Use textContent || innerText for elements 8134 if ( typeof elem.textContent === 'string' ) { 8135 return elem.textContent; 8136 } else if ( typeof elem.innerText === 'string' ) { 8137 // Replace IE's carriage returns 8138 return elem.innerText.replace( rReturn, '' ); 8139 } else { 8140 // Traverse it's children 8141 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 8142 ret += getText( elem ); 8143 } 8144 } 8145 } else if ( nodeType === 3 || nodeType === 4 ) { 8146 return elem.nodeValue; 8147 } 8148 } else { 8149 8150 // If no nodeType, this is expected to be an array 8151 for ( i = 0; (node = elem[i]); i++ ) { 8152 // Do not traverse comment nodes 8153 if ( node.nodeType !== 8 ) { 8154 ret += getText( node ); 8155 } 8156 } 8157 } 8158 return ret; 8159 }; 8160 8161 var Expr = Sizzle.selectors = { 8162 order: [ "ID", "NAME", "TAG" ], 8163 8164 match: { 8165 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8166 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8167 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 8168 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 8169 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 8170 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 8171 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 8172 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 8173 }, 8174 8175 leftMatch: {}, 8176 8177 attrMap: { 8178 "class": "className", 8179 "for": "htmlFor" 8180 }, 8181 8182 attrHandle: { 8183 href: function( elem ) { 8184 return elem.getAttribute( "href" ); 8185 }, 8186 type: function( elem ) { 8187 return elem.getAttribute( "type" ); 8188 } 8189 }, 8190 8191 relative: { 8192 "+": function(checkSet, part){ 8193 var isPartStr = typeof part === "string", 8194 isTag = isPartStr && !rNonWord.test( part ), 8195 isPartStrNotTag = isPartStr && !isTag; 8196 8197 if ( isTag ) { 8198 part = part.toLowerCase(); 8199 } 8200 8201 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 8202 if ( (elem = checkSet[i]) ) { 8203 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 8204 8205 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 8206 elem || false : 8207 elem === part; 8208 } 8209 } 8210 8211 if ( isPartStrNotTag ) { 8212 Sizzle.filter( part, checkSet, true ); 8213 } 8214 }, 8215 8216 ">": function( checkSet, part ) { 8217 var elem, 8218 isPartStr = typeof part === "string", 8219 i = 0, 8220 l = checkSet.length; 8221 8222 if ( isPartStr && !rNonWord.test( part ) ) { 8223 part = part.toLowerCase(); 8224 8225 for ( ; i < l; i++ ) { 8226 elem = checkSet[i]; 8227 8228 if ( elem ) { 8229 var parent = elem.parentNode; 8230 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 8231 } 8232 } 8233 8234 } else { 8235 for ( ; i < l; i++ ) { 8236 elem = checkSet[i]; 8237 8238 if ( elem ) { 8239 checkSet[i] = isPartStr ? 8240 elem.parentNode : 8241 elem.parentNode === part; 8242 } 8243 } 8244 8245 if ( isPartStr ) { 8246 Sizzle.filter( part, checkSet, true ); 8247 } 8248 } 8249 }, 8250 8251 "": function(checkSet, part, isXML){ 8252 var nodeCheck, 8253 doneName = done++, 8254 checkFn = dirCheck; 8255 8256 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8257 part = part.toLowerCase(); 8258 nodeCheck = part; 8259 checkFn = dirNodeCheck; 8260 } 8261 8262 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 8263 }, 8264 8265 "~": function( checkSet, part, isXML ) { 8266 var nodeCheck, 8267 doneName = done++, 8268 checkFn = dirCheck; 8269 8270 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8271 part = part.toLowerCase(); 8272 nodeCheck = part; 8273 checkFn = dirNodeCheck; 8274 } 8275 8276 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 8277 } 8278 }, 8279 8280 find: { 8281 ID: function( match, context, isXML ) { 8282 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8283 var m = context.getElementById(match[1]); 8284 // Check parentNode to catch when Blackberry 4.6 returns 8285 // nodes that are no longer in the document #6963 8286 return m && m.parentNode ? [m] : []; 8287 } 8288 }, 8289 8290 NAME: function( match, context ) { 8291 if ( typeof context.getElementsByName !== "undefined" ) { 8292 var ret = [], 8293 results = context.getElementsByName( match[1] ); 8294 8295 for ( var i = 0, l = results.length; i < l; i++ ) { 8296 if ( results[i].getAttribute("name") === match[1] ) { 8297 ret.push( results[i] ); 8298 } 8299 } 8300 8301 return ret.length === 0 ? null : ret; 8302 } 8303 }, 8304 8305 TAG: function( match, context ) { 8306 if ( typeof context.getElementsByTagName !== "undefined" ) { 8307 return context.getElementsByTagName( match[1] ); 8308 } 8309 } 8310 }, 8311 preFilter: { 8312 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8313 match = " " + match[1].replace( rBackslash, "" ) + " "; 8314 8315 if ( isXML ) { 8316 return match; 8317 } 8318 8319 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8320 if ( elem ) { 8321 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8322 if ( !inplace ) { 8323 result.push( elem ); 8324 } 8325 8326 } else if ( inplace ) { 8327 curLoop[i] = false; 8328 } 8329 } 8330 } 8331 8332 return false; 8333 }, 8334 8335 ID: function( match ) { 8336 return match[1].replace( rBackslash, "" ); 8337 }, 8338 8339 TAG: function( match, curLoop ) { 8340 return match[1].replace( rBackslash, "" ).toLowerCase(); 8341 }, 8342 8343 CHILD: function( match ) { 8344 if ( match[1] === "nth" ) { 8345 if ( !match[2] ) { 8346 Sizzle.error( match[0] ); 8347 } 8348 8349 match[2] = match[2].replace(/^\+|\s*/g, ''); 8350 8351 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8352 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8353 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8354 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8355 8356 // calculate the numbers (first)n+(last) including if they are negative 8357 match[2] = (test[1] + (test[2] || 1)) - 0; 8358 match[3] = test[3] - 0; 8359 } 8360 else if ( match[2] ) { 8361 Sizzle.error( match[0] ); 8362 } 8363 8364 // TODO: Move to normal caching system 8365 match[0] = done++; 8366 8367 return match; 8368 }, 8369 8370 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8371 var name = match[1] = match[1].replace( rBackslash, "" ); 8372 8373 if ( !isXML && Expr.attrMap[name] ) { 8374 match[1] = Expr.attrMap[name]; 8375 } 8376 8377 // Handle if an un-quoted value was used 8378 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8379 8380 if ( match[2] === "~=" ) { 8381 match[4] = " " + match[4] + " "; 8382 } 8383 8384 return match; 8385 }, 8386 8387 PSEUDO: function( match, curLoop, inplace, result, not ) { 8388 if ( match[1] === "not" ) { 8389 // If we're dealing with a complex expression, or a simple one 8390 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8391 match[3] = Sizzle(match[3], null, null, curLoop); 8392 8393 } else { 8394 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8395 8396 if ( !inplace ) { 8397 result.push.apply( result, ret ); 8398 } 8399 8400 return false; 8401 } 8402 8403 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8404 return true; 8405 } 8406 8407 return match; 8408 }, 8409 8410 POS: function( match ) { 8411 match.unshift( true ); 8412 8413 return match; 8414 } 8415 }, 8416 8417 filters: { 8418 enabled: function( elem ) { 8419 return elem.disabled === false && elem.type !== "hidden"; 8420 }, 8421 8422 disabled: function( elem ) { 8423 return elem.disabled === true; 8424 }, 8425 8426 checked: function( elem ) { 8427 return elem.checked === true; 8428 }, 8429 8430 selected: function( elem ) { 8431 // Accessing this property makes selected-by-default 8432 // options in Safari work properly 8433 if ( elem.parentNode ) { 8434 elem.parentNode.selectedIndex; 8435 } 8436 8437 return elem.selected === true; 8438 }, 8439 8440 parent: function( elem ) { 8441 return !!elem.firstChild; 8442 }, 8443 8444 empty: function( elem ) { 8445 return !elem.firstChild; 8446 }, 8447 8448 has: function( elem, i, match ) { 8449 return !!Sizzle( match[3], elem ).length; 8450 }, 8451 8452 header: function( elem ) { 8453 return (/h\d/i).test( elem.nodeName ); 8454 }, 8455 8456 text: function( elem ) { 8457 var attr = elem.getAttribute( "type" ), type = elem.type; 8458 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8459 // use getAttribute instead to test this case 8460 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8461 }, 8462 8463 radio: function( elem ) { 8464 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8465 }, 8466 8467 checkbox: function( elem ) { 8468 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8469 }, 8470 8471 file: function( elem ) { 8472 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8473 }, 8474 8475 password: function( elem ) { 8476 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8477 }, 8478 8479 submit: function( elem ) { 8480 var name = elem.nodeName.toLowerCase(); 8481 return (name === "input" || name === "button") && "submit" === elem.type; 8482 }, 8483 8484 image: function( elem ) { 8485 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8486 }, 8487 8488 reset: function( elem ) { 8489 var name = elem.nodeName.toLowerCase(); 8490 return (name === "input" || name === "button") && "reset" === elem.type; 8491 }, 8492 8493 button: function( elem ) { 8494 var name = elem.nodeName.toLowerCase(); 8495 return name === "input" && "button" === elem.type || name === "button"; 8496 }, 8497 8498 input: function( elem ) { 8499 return (/input|select|textarea|button/i).test( elem.nodeName ); 8500 }, 8501 8502 focus: function( elem ) { 8503 return elem === elem.ownerDocument.activeElement; 8504 } 8505 }, 8506 setFilters: { 8507 first: function( elem, i ) { 8508 return i === 0; 8509 }, 8510 8511 last: function( elem, i, match, array ) { 8512 return i === array.length - 1; 8513 }, 8514 8515 even: function( elem, i ) { 8516 return i % 2 === 0; 8517 }, 8518 8519 odd: function( elem, i ) { 8520 return i % 2 === 1; 8521 }, 8522 8523 lt: function( elem, i, match ) { 8524 return i < match[3] - 0; 8525 }, 8526 8527 gt: function( elem, i, match ) { 8528 return i > match[3] - 0; 8529 }, 8530 8531 nth: function( elem, i, match ) { 8532 return match[3] - 0 === i; 8533 }, 8534 8535 eq: function( elem, i, match ) { 8536 return match[3] - 0 === i; 8537 } 8538 }, 8539 filter: { 8540 PSEUDO: function( elem, match, i, array ) { 8541 var name = match[1], 8542 filter = Expr.filters[ name ]; 8543 8544 if ( filter ) { 8545 return filter( elem, i, match, array ); 8546 8547 } else if ( name === "contains" ) { 8548 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8549 8550 } else if ( name === "not" ) { 8551 var not = match[3]; 8552 8553 for ( var j = 0, l = not.length; j < l; j++ ) { 8554 if ( not[j] === elem ) { 8555 return false; 8556 } 8557 } 8558 8559 return true; 8560 8561 } else { 8562 Sizzle.error( name ); 8563 } 8564 }, 8565 8566 CHILD: function( elem, match ) { 8567 var first, last, 8568 doneName, parent, cache, 8569 count, diff, 8570 type = match[1], 8571 node = elem; 8572 8573 switch ( type ) { 8574 case "only": 8575 case "first": 8576 while ( (node = node.previousSibling) ) { 8577 if ( node.nodeType === 1 ) { 8578 return false; 8579 } 8580 } 8581 8582 if ( type === "first" ) { 8583 return true; 8584 } 8585 8586 node = elem; 8587 8588 /* falls through */ 8589 case "last": 8590 while ( (node = node.nextSibling) ) { 8591 if ( node.nodeType === 1 ) { 8592 return false; 8593 } 8594 } 8595 8596 return true; 8597 8598 case "nth": 8599 first = match[2]; 8600 last = match[3]; 8601 8602 if ( first === 1 && last === 0 ) { 8603 return true; 8604 } 8605 8606 doneName = match[0]; 8607 parent = elem.parentNode; 8608 8609 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8610 count = 0; 8611 8612 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8613 if ( node.nodeType === 1 ) { 8614 node.nodeIndex = ++count; 8615 } 8616 } 8617 8618 parent[ expando ] = doneName; 8619 } 8620 8621 diff = elem.nodeIndex - last; 8622 8623 if ( first === 0 ) { 8624 return diff === 0; 8625 8626 } else { 8627 return ( diff % first === 0 && diff / first >= 0 ); 8628 } 8629 } 8630 }, 8631 8632 ID: function( elem, match ) { 8633 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8634 }, 8635 8636 TAG: function( elem, match ) { 8637 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8638 }, 8639 8640 CLASS: function( elem, match ) { 8641 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8642 .indexOf( match ) > -1; 8643 }, 8644 8645 ATTR: function( elem, match ) { 8646 var name = match[1], 8647 result = Sizzle.attr ? 8648 Sizzle.attr( elem, name ) : 8649 Expr.attrHandle[ name ] ? 8650 Expr.attrHandle[ name ]( elem ) : 8651 elem[ name ] != null ? 8652 elem[ name ] : 8653 elem.getAttribute( name ), 8654 value = result + "", 8655 type = match[2], 8656 check = match[4]; 8657 8658 return result == null ? 8659 type === "!=" : 8660 !type && Sizzle.attr ? 8661 result != null : 8662 type === "=" ? 8663 value === check : 8664 type === "*=" ? 8665 value.indexOf(check) >= 0 : 8666 type === "~=" ? 8667 (" " + value + " ").indexOf(check) >= 0 : 8668 !check ? 8669 value && result !== false : 8670 type === "!=" ? 8671 value !== check : 8672 type === "^=" ? 8673 value.indexOf(check) === 0 : 8674 type === "$=" ? 8675 value.substr(value.length - check.length) === check : 8676 type === "|=" ? 8677 value === check || value.substr(0, check.length + 1) === check + "-" : 8678 false; 8679 }, 8680 8681 POS: function( elem, match, i, array ) { 8682 var name = match[2], 8683 filter = Expr.setFilters[ name ]; 8684 8685 if ( filter ) { 8686 return filter( elem, i, match, array ); 8687 } 8688 } 8689 } 8690 }; 8691 8692 var origPOS = Expr.match.POS, 8693 fescape = function(all, num){ 8694 return "\\" + (num - 0 + 1); 8695 }; 8696 8697 for ( var type in Expr.match ) { 8698 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8699 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8700 } 8701 // Expose origPOS 8702 // "global" as in regardless of relation to brackets/parens 8703 Expr.match.globalPOS = origPOS; 8704 8705 var makeArray = function( array, results ) { 8706 array = Array.prototype.slice.call( array, 0 ); 8707 8708 if ( results ) { 8709 results.push.apply( results, array ); 8710 return results; 8711 } 8712 8713 return array; 8714 }; 8715 8716 // Perform a simple check to determine if the browser is capable of 8717 // converting a NodeList to an array using builtin methods. 8718 // Also verifies that the returned array holds DOM nodes 8719 // (which is not the case in the Blackberry browser) 8720 try { 8721 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8722 8723 // Provide a fallback method if it does not work 8724 } catch( e ) { 8725 makeArray = function( array, results ) { 8726 var i = 0, 8727 ret = results || []; 8728 8729 if ( toString.call(array) === "[object Array]" ) { 8730 Array.prototype.push.apply( ret, array ); 8731 8732 } else { 8733 if ( typeof array.length === "number" ) { 8734 for ( var l = array.length; i < l; i++ ) { 8735 ret.push( array[i] ); 8736 } 8737 8738 } else { 8739 for ( ; array[i]; i++ ) { 8740 ret.push( array[i] ); 8741 } 8742 } 8743 } 8744 8745 return ret; 8746 }; 8747 } 8748 8749 var sortOrder, siblingCheck; 8750 8751 if ( document.documentElement.compareDocumentPosition ) { 8752 sortOrder = function( a, b ) { 8753 if ( a === b ) { 8754 hasDuplicate = true; 8755 return 0; 8756 } 8757 8758 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8759 return a.compareDocumentPosition ? -1 : 1; 8760 } 8761 8762 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8763 }; 8764 8765 } else { 8766 sortOrder = function( a, b ) { 8767 // The nodes are identical, we can exit early 8768 if ( a === b ) { 8769 hasDuplicate = true; 8770 return 0; 8771 8772 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8773 } else if ( a.sourceIndex && b.sourceIndex ) { 8774 return a.sourceIndex - b.sourceIndex; 8775 } 8776 8777 var al, bl, 8778 ap = [], 8779 bp = [], 8780 aup = a.parentNode, 8781 bup = b.parentNode, 8782 cur = aup; 8783 8784 // If the nodes are siblings (or identical) we can do a quick check 8785 if ( aup === bup ) { 8786 return siblingCheck( a, b ); 8787 8788 // If no parents were found then the nodes are disconnected 8789 } else if ( !aup ) { 8790 return -1; 8791 8792 } else if ( !bup ) { 8793 return 1; 8794 } 8795 8796 // Otherwise they're somewhere else in the tree so we need 8797 // to build up a full list of the parentNodes for comparison 8798 while ( cur ) { 8799 ap.unshift( cur ); 8800 cur = cur.parentNode; 8801 } 8802 8803 cur = bup; 8804 8805 while ( cur ) { 8806 bp.unshift( cur ); 8807 cur = cur.parentNode; 8808 } 8809 8810 al = ap.length; 8811 bl = bp.length; 8812 8813 // Start walking down the tree looking for a discrepancy 8814 for ( var i = 0; i < al && i < bl; i++ ) { 8815 if ( ap[i] !== bp[i] ) { 8816 return siblingCheck( ap[i], bp[i] ); 8817 } 8818 } 8819 8820 // We ended someplace up the tree so do a sibling check 8821 return i === al ? 8822 siblingCheck( a, bp[i], -1 ) : 8823 siblingCheck( ap[i], b, 1 ); 8824 }; 8825 8826 siblingCheck = function( a, b, ret ) { 8827 if ( a === b ) { 8828 return ret; 8829 } 8830 8831 var cur = a.nextSibling; 8832 8833 while ( cur ) { 8834 if ( cur === b ) { 8835 return -1; 8836 } 8837 8838 cur = cur.nextSibling; 8839 } 8840 8841 return 1; 8842 }; 8843 } 8844 8845 // Check to see if the browser returns elements by name when 8846 // querying by getElementById (and provide a workaround) 8847 (function(){ 8848 // We're going to inject a fake input element with a specified name 8849 var form = document.createElement("div"), 8850 id = "script" + (new Date()).getTime(), 8851 root = document.documentElement; 8852 8853 form.innerHTML = "<a name='" + id + "'/>"; 8854 8855 // Inject it into the root element, check its status, and remove it quickly 8856 root.insertBefore( form, root.firstChild ); 8857 8858 // The workaround has to do additional checks after a getElementById 8859 // Which slows things down for other browsers (hence the branching) 8860 if ( document.getElementById( id ) ) { 8861 Expr.find.ID = function( match, context, isXML ) { 8862 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8863 var m = context.getElementById(match[1]); 8864 8865 return m ? 8866 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8867 [m] : 8868 undefined : 8869 []; 8870 } 8871 }; 8872 8873 Expr.filter.ID = function( elem, match ) { 8874 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8875 8876 return elem.nodeType === 1 && node && node.nodeValue === match; 8877 }; 8878 } 8879 8880 root.removeChild( form ); 8881 8882 // release memory in IE 8883 root = form = null; 8884 })(); 8885 8886 (function(){ 8887 // Check to see if the browser returns only elements 8888 // when doing getElementsByTagName("*") 8889 8890 // Create a fake element 8891 var div = document.createElement("div"); 8892 div.appendChild( document.createComment("") ); 8893 8894 // Make sure no comments are found 8895 if ( div.getElementsByTagName("*").length > 0 ) { 8896 Expr.find.TAG = function( match, context ) { 8897 var results = context.getElementsByTagName( match[1] ); 8898 8899 // Filter out possible comments 8900 if ( match[1] === "*" ) { 8901 var tmp = []; 8902 8903 for ( var i = 0; results[i]; i++ ) { 8904 if ( results[i].nodeType === 1 ) { 8905 tmp.push( results[i] ); 8906 } 8907 } 8908 8909 results = tmp; 8910 } 8911 8912 return results; 8913 }; 8914 } 8915 8916 // Check to see if an attribute returns normalized href attributes 8917 div.innerHTML = "<a href='#'></a>"; 8918 8919 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8920 div.firstChild.getAttribute("href") !== "#" ) { 8921 8922 Expr.attrHandle.href = function( elem ) { 8923 return elem.getAttribute( "href", 2 ); 8924 }; 8925 } 8926 8927 // release memory in IE 8928 div = null; 8929 })(); 8930 8931 if ( document.querySelectorAll ) { 8932 (function(){ 8933 var oldSizzle = Sizzle, 8934 div = document.createElement("div"), 8935 id = "__sizzle__"; 8936 8937 div.innerHTML = "<p class='TEST'></p>"; 8938 8939 // Safari can't handle uppercase or unicode characters when 8940 // in quirks mode. 8941 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8942 return; 8943 } 8944 8945 Sizzle = function( query, context, extra, seed ) { 8946 context = context || document; 8947 8948 // Only use querySelectorAll on non-XML documents 8949 // (ID selectors don't work in non-HTML documents) 8950 if ( !seed && !Sizzle.isXML(context) ) { 8951 // See if we find a selector to speed up 8952 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8953 8954 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8955 // Speed-up: Sizzle("TAG") 8956 if ( match[1] ) { 8957 return makeArray( context.getElementsByTagName( query ), extra ); 8958 8959 // Speed-up: Sizzle(".CLASS") 8960 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8961 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8962 } 8963 } 8964 8965 if ( context.nodeType === 9 ) { 8966 // Speed-up: Sizzle("body") 8967 // The body element only exists once, optimize finding it 8968 if ( query === "body" && context.body ) { 8969 return makeArray( [ context.body ], extra ); 8970 8971 // Speed-up: Sizzle("#ID") 8972 } else if ( match && match[3] ) { 8973 var elem = context.getElementById( match[3] ); 8974 8975 // Check parentNode to catch when Blackberry 4.6 returns 8976 // nodes that are no longer in the document #6963 8977 if ( elem && elem.parentNode ) { 8978 // Handle the case where IE and Opera return items 8979 // by name instead of ID 8980 if ( elem.id === match[3] ) { 8981 return makeArray( [ elem ], extra ); 8982 } 8983 8984 } else { 8985 return makeArray( [], extra ); 8986 } 8987 } 8988 8989 try { 8990 return makeArray( context.querySelectorAll(query), extra ); 8991 } catch(qsaError) {} 8992 8993 // qSA works strangely on Element-rooted queries 8994 // We can work around this by specifying an extra ID on the root 8995 // and working up from there (Thanks to Andrew Dupont for the technique) 8996 // IE 8 doesn't work on object elements 8997 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8998 var oldContext = context, 8999 old = context.getAttribute( "id" ), 9000 nid = old || id, 9001 hasParent = context.parentNode, 9002 relativeHierarchySelector = /^\s*[+~]/.test( query ); 9003 9004 if ( !old ) { 9005 context.setAttribute( "id", nid ); 9006 } else { 9007 nid = nid.replace( /'/g, "\\$&" ); 9008 } 9009 if ( relativeHierarchySelector && hasParent ) { 9010 context = context.parentNode; 9011 } 9012 9013 try { 9014 if ( !relativeHierarchySelector || hasParent ) { 9015 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 9016 } 9017 9018 } catch(pseudoError) { 9019 } finally { 9020 if ( !old ) { 9021 oldContext.removeAttribute( "id" ); 9022 } 9023 } 9024 } 9025 } 9026 9027 return oldSizzle(query, context, extra, seed); 9028 }; 9029 9030 for ( var prop in oldSizzle ) { 9031 Sizzle[ prop ] = oldSizzle[ prop ]; 9032 } 9033 9034 // release memory in IE 9035 div = null; 9036 })(); 9037 } 9038 9039 (function(){ 9040 var html = document.documentElement, 9041 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 9042 9043 if ( matches ) { 9044 // Check to see if it's possible to do matchesSelector 9045 // on a disconnected node (IE 9 fails this) 9046 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 9047 pseudoWorks = false; 9048 9049 try { 9050 // This should fail with an exception 9051 // Gecko does not error, returns false instead 9052 matches.call( document.documentElement, "[test!='']:sizzle" ); 9053 9054 } catch( pseudoError ) { 9055 pseudoWorks = true; 9056 } 9057 9058 Sizzle.matchesSelector = function( node, expr ) { 9059 // Make sure that attribute selectors are quoted 9060 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 9061 9062 if ( !Sizzle.isXML( node ) ) { 9063 try { 9064 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 9065 var ret = matches.call( node, expr ); 9066 9067 // IE 9's matchesSelector returns false on disconnected nodes 9068 if ( ret || !disconnectedMatch || 9069 // As well, disconnected nodes are said to be in a document 9070 // fragment in IE 9, so check for that 9071 node.document && node.document.nodeType !== 11 ) { 9072 return ret; 9073 } 9074 } 9075 } catch(e) {} 9076 } 9077 9078 return Sizzle(expr, null, null, [node]).length > 0; 9079 }; 9080 } 9081 })(); 9082 9083 (function(){ 9084 var div = document.createElement("div"); 9085 9086 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 9087 9088 // Opera can't find a second classname (in 9.6) 9089 // Also, make sure that getElementsByClassName actually exists 9090 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 9091 return; 9092 } 9093 9094 // Safari caches class attributes, doesn't catch changes (in 3.2) 9095 div.lastChild.className = "e"; 9096 9097 if ( div.getElementsByClassName("e").length === 1 ) { 9098 return; 9099 } 9100 9101 Expr.order.splice(1, 0, "CLASS"); 9102 Expr.find.CLASS = function( match, context, isXML ) { 9103 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 9104 return context.getElementsByClassName(match[1]); 9105 } 9106 }; 9107 9108 // release memory in IE 9109 div = null; 9110 })(); 9111 9112 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9113 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9114 var elem = checkSet[i]; 9115 9116 if ( elem ) { 9117 var match = false; 9118 9119 elem = elem[dir]; 9120 9121 while ( elem ) { 9122 if ( elem[ expando ] === doneName ) { 9123 match = checkSet[elem.sizset]; 9124 break; 9125 } 9126 9127 if ( elem.nodeType === 1 && !isXML ){ 9128 elem[ expando ] = doneName; 9129 elem.sizset = i; 9130 } 9131 9132 if ( elem.nodeName.toLowerCase() === cur ) { 9133 match = elem; 9134 break; 9135 } 9136 9137 elem = elem[dir]; 9138 } 9139 9140 checkSet[i] = match; 9141 } 9142 } 9143 } 9144 9145 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9146 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9147 var elem = checkSet[i]; 9148 9149 if ( elem ) { 9150 var match = false; 9151 9152 elem = elem[dir]; 9153 9154 while ( elem ) { 9155 if ( elem[ expando ] === doneName ) { 9156 match = checkSet[elem.sizset]; 9157 break; 9158 } 9159 9160 if ( elem.nodeType === 1 ) { 9161 if ( !isXML ) { 9162 elem[ expando ] = doneName; 9163 elem.sizset = i; 9164 } 9165 9166 if ( typeof cur !== "string" ) { 9167 if ( elem === cur ) { 9168 match = true; 9169 break; 9170 } 9171 9172 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 9173 match = elem; 9174 break; 9175 } 9176 } 9177 9178 elem = elem[dir]; 9179 } 9180 9181 checkSet[i] = match; 9182 } 9183 } 9184 } 9185 9186 if ( document.documentElement.contains ) { 9187 Sizzle.contains = function( a, b ) { 9188 return a !== b && (a.contains ? a.contains(b) : true); 9189 }; 9190 9191 } else if ( document.documentElement.compareDocumentPosition ) { 9192 Sizzle.contains = function( a, b ) { 9193 return !!(a.compareDocumentPosition(b) & 16); 9194 }; 9195 9196 } else { 9197 Sizzle.contains = function() { 9198 return false; 9199 }; 9200 } 9201 9202 Sizzle.isXML = function( elem ) { 9203 // documentElement is verified for cases where it doesn't yet exist 9204 // (such as loading iframes in IE - #4833) 9205 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 9206 9207 return documentElement ? documentElement.nodeName !== "HTML" : false; 9208 }; 9209 9210 var posProcess = function( selector, context, seed ) { 9211 var match, 9212 tmpSet = [], 9213 later = "", 9214 root = context.nodeType ? [context] : context; 9215 9216 // Position selectors must be done after the filter 9217 // And so must :not(positional) so we move all PSEUDOs to the end 9218 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 9219 later += match[0]; 9220 selector = selector.replace( Expr.match.PSEUDO, "" ); 9221 } 9222 9223 selector = Expr.relative[selector] ? selector + "*" : selector; 9224 9225 for ( var i = 0, l = root.length; i < l; i++ ) { 9226 Sizzle( selector, root[i], tmpSet, seed ); 9227 } 9228 9229 return Sizzle.filter( later, tmpSet ); 9230 }; 9231 9232 // EXPOSE 9233 9234 window.tinymce.dom.Sizzle = Sizzle; 9235 9236 })(); 9237 9238 (function(tinymce) { 9239 tinymce.dom.Element = function(id, settings) { 9240 var t = this, dom, el; 9241 9242 t.settings = settings = settings || {}; 9243 t.id = id; 9244 t.dom = dom = settings.dom || tinymce.DOM; 9245 9246 // Only IE leaks DOM references, this is a lot faster 9247 if (!tinymce.isIE) 9248 el = dom.get(t.id); 9249 9250 tinymce.each( 9251 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 9252 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 9253 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 9254 'isHidden,setHTML,get').split(/,/), function(k) { 9255 t[k] = function() { 9256 var a = [id], i; 9257 9258 for (i = 0; i < arguments.length; i++) 9259 a.push(arguments[i]); 9260 9261 a = dom[k].apply(dom, a); 9262 t.update(k); 9263 9264 return a; 9265 }; 9266 } 9267 ); 9268 9269 tinymce.extend(t, { 9270 on : function(n, f, s) { 9271 return tinymce.dom.Event.add(t.id, n, f, s); 9272 }, 9273 9274 getXY : function() { 9275 return { 9276 x : parseInt(t.getStyle('left')), 9277 y : parseInt(t.getStyle('top')) 9278 }; 9279 }, 9280 9281 getSize : function() { 9282 var n = dom.get(t.id); 9283 9284 return { 9285 w : parseInt(t.getStyle('width') || n.clientWidth), 9286 h : parseInt(t.getStyle('height') || n.clientHeight) 9287 }; 9288 }, 9289 9290 moveTo : function(x, y) { 9291 t.setStyles({left : x, top : y}); 9292 }, 9293 9294 moveBy : function(x, y) { 9295 var p = t.getXY(); 9296 9297 t.moveTo(p.x + x, p.y + y); 9298 }, 9299 9300 resizeTo : function(w, h) { 9301 t.setStyles({width : w, height : h}); 9302 }, 9303 9304 resizeBy : function(w, h) { 9305 var s = t.getSize(); 9306 9307 t.resizeTo(s.w + w, s.h + h); 9308 }, 9309 9310 update : function(k) { 9311 var b; 9312 9313 if (tinymce.isIE6 && settings.blocker) { 9314 k = k || ''; 9315 9316 // Ignore getters 9317 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9318 return; 9319 9320 // Remove blocker on remove 9321 if (k == 'remove') { 9322 dom.remove(t.blocker); 9323 return; 9324 } 9325 9326 if (!t.blocker) { 9327 t.blocker = dom.uniqueId(); 9328 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9329 dom.setStyle(b, 'opacity', 0); 9330 } else 9331 b = dom.get(t.blocker); 9332 9333 dom.setStyles(b, { 9334 left : t.getStyle('left', 1), 9335 top : t.getStyle('top', 1), 9336 width : t.getStyle('width', 1), 9337 height : t.getStyle('height', 1), 9338 display : t.getStyle('display', 1), 9339 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9340 }); 9341 } 9342 } 9343 }); 9344 }; 9345 })(tinymce); 9346 (function(tinymce) { 9347 function trimNl(s) { 9348 return s.replace(/[\n\r]+/g, ''); 9349 }; 9350 9351 // Shorten names 9352 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9353 9354 tinymce.create('tinymce.dom.Selection', { 9355 Selection : function(dom, win, serializer, editor) { 9356 var t = this; 9357 9358 t.dom = dom; 9359 t.win = win; 9360 t.serializer = serializer; 9361 t.editor = editor; 9362 9363 // Add events 9364 each([ 9365 'onBeforeSetContent', 9366 9367 'onBeforeGetContent', 9368 9369 'onSetContent', 9370 9371 'onGetContent' 9372 ], function(e) { 9373 t[e] = new tinymce.util.Dispatcher(t); 9374 }); 9375 9376 // No W3C Range support 9377 if (!t.win.getSelection) 9378 t.tridentSel = new tinymce.dom.TridentSelection(t); 9379 9380 if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel) 9381 this._fixIESelection(); 9382 9383 // Prevent leaks 9384 tinymce.addUnload(t.destroy, t); 9385 }, 9386 9387 setCursorLocation: function(node, offset) { 9388 var t = this; var r = t.dom.createRng(); 9389 r.setStart(node, offset); 9390 r.setEnd(node, offset); 9391 t.setRng(r); 9392 t.collapse(false); 9393 }, 9394 getContent : function(s) { 9395 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9396 9397 s = s || {}; 9398 wb = wa = ''; 9399 s.get = true; 9400 s.format = s.format || 'html'; 9401 s.forced_root_block = ''; 9402 t.onBeforeGetContent.dispatch(t, s); 9403 9404 if (s.format == 'text') 9405 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9406 9407 if (r.cloneContents) { 9408 n = r.cloneContents(); 9409 9410 if (n) 9411 e.appendChild(n); 9412 } else if (is(r.item) || is(r.htmlText)) { 9413 // IE will produce invalid markup if elements are present that 9414 // it doesn't understand like custom elements or HTML5 elements. 9415 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9416 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9417 e.removeChild(e.firstChild); 9418 } else 9419 e.innerHTML = r.toString(); 9420 9421 // Keep whitespace before and after 9422 if (/^\s/.test(e.innerHTML)) 9423 wb = ' '; 9424 9425 if (/\s+$/.test(e.innerHTML)) 9426 wa = ' '; 9427 9428 s.getInner = true; 9429 9430 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9431 t.onGetContent.dispatch(t, s); 9432 9433 return s.content; 9434 }, 9435 9436 setContent : function(content, args) { 9437 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9438 9439 args = args || {format : 'html'}; 9440 args.set = true; 9441 content = args.content = content; 9442 9443 // Dispatch before set content event 9444 if (!args.no_events) 9445 self.onBeforeSetContent.dispatch(self, args); 9446 9447 content = args.content; 9448 9449 if (rng.insertNode) { 9450 // Make caret marker since insertNode places the caret in the beginning of text after insert 9451 content += '<span id="__caret">_</span>'; 9452 9453 // Delete and insert new node 9454 if (rng.startContainer == doc && rng.endContainer == doc) { 9455 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9456 doc.body.innerHTML = content; 9457 } else { 9458 rng.deleteContents(); 9459 9460 if (doc.body.childNodes.length === 0) { 9461 doc.body.innerHTML = content; 9462 } else { 9463 // createContextualFragment doesn't exists in IE 9 DOMRanges 9464 if (rng.createContextualFragment) { 9465 rng.insertNode(rng.createContextualFragment(content)); 9466 } else { 9467 // Fake createContextualFragment call in IE 9 9468 frag = doc.createDocumentFragment(); 9469 temp = doc.createElement('div'); 9470 9471 frag.appendChild(temp); 9472 temp.outerHTML = content; 9473 9474 rng.insertNode(frag); 9475 } 9476 } 9477 } 9478 9479 // Move to caret marker 9480 caretNode = self.dom.get('__caret'); 9481 9482 // Make sure we wrap it compleatly, Opera fails with a simple select call 9483 rng = doc.createRange(); 9484 rng.setStartBefore(caretNode); 9485 rng.setEndBefore(caretNode); 9486 self.setRng(rng); 9487 9488 // Remove the caret position 9489 self.dom.remove('__caret'); 9490 9491 try { 9492 self.setRng(rng); 9493 } catch (ex) { 9494 // Might fail on Opera for some odd reason 9495 } 9496 } else { 9497 if (rng.item) { 9498 // Delete content and get caret text selection 9499 doc.execCommand('Delete', false, null); 9500 rng = self.getRng(); 9501 } 9502 9503 // Explorer removes spaces from the beginning of pasted contents 9504 if (/^\s+/.test(content)) { 9505 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9506 self.dom.remove('__mce_tmp'); 9507 } else 9508 rng.pasteHTML(content); 9509 } 9510 9511 // Dispatch set content event 9512 if (!args.no_events) 9513 self.onSetContent.dispatch(self, args); 9514 }, 9515 9516 getStart : function() { 9517 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9518 9519 if (rng.duplicate || rng.item) { 9520 // Control selection, return first item 9521 if (rng.item) 9522 return rng.item(0); 9523 9524 // Get start element 9525 checkRng = rng.duplicate(); 9526 checkRng.collapse(1); 9527 startElement = checkRng.parentElement(); 9528 if (startElement.ownerDocument !== self.dom.doc) { 9529 startElement = self.dom.getRoot(); 9530 } 9531 9532 // Check if range parent is inside the start element, then return the inner parent element 9533 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9534 parentElement = node = rng.parentElement(); 9535 while (node = node.parentNode) { 9536 if (node == startElement) { 9537 startElement = parentElement; 9538 break; 9539 } 9540 } 9541 9542 return startElement; 9543 } else { 9544 startElement = rng.startContainer; 9545 9546 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9547 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9548 9549 if (startElement && startElement.nodeType == 3) 9550 return startElement.parentNode; 9551 9552 return startElement; 9553 } 9554 }, 9555 9556 getEnd : function() { 9557 var self = this, rng = self.getRng(), endElement, endOffset; 9558 9559 if (rng.duplicate || rng.item) { 9560 if (rng.item) 9561 return rng.item(0); 9562 9563 rng = rng.duplicate(); 9564 rng.collapse(0); 9565 endElement = rng.parentElement(); 9566 if (endElement.ownerDocument !== self.dom.doc) { 9567 endElement = self.dom.getRoot(); 9568 } 9569 9570 if (endElement && endElement.nodeName == 'BODY') 9571 return endElement.lastChild || endElement; 9572 9573 return endElement; 9574 } else { 9575 endElement = rng.endContainer; 9576 endOffset = rng.endOffset; 9577 9578 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9579 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9580 9581 if (endElement && endElement.nodeType == 3) 9582 return endElement.parentNode; 9583 9584 return endElement; 9585 } 9586 }, 9587 9588 getBookmark : function(type, normalized) { 9589 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9590 9591 function findIndex(name, element) { 9592 var index = 0; 9593 9594 each(dom.select(name), function(node, i) { 9595 if (node == element) 9596 index = i; 9597 }); 9598 9599 return index; 9600 }; 9601 9602 function normalizeTableCellSelection(rng) { 9603 function moveEndPoint(start) { 9604 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9605 9606 container = rng[prefix + 'Container']; 9607 offset = rng[prefix + 'Offset']; 9608 9609 if (container.nodeType == 1 && container.nodeName == "TR") { 9610 childNodes = container.childNodes; 9611 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9612 if (container) { 9613 offset = start ? 0 : container.childNodes.length; 9614 rng['set' + (start ? 'Start' : 'End')](container, offset); 9615 } 9616 } 9617 }; 9618 9619 moveEndPoint(true); 9620 moveEndPoint(); 9621 9622 return rng; 9623 }; 9624 9625 function getLocation() { 9626 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9627 9628 function getPoint(rng, start) { 9629 var container = rng[start ? 'startContainer' : 'endContainer'], 9630 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9631 9632 if (container.nodeType == 3) { 9633 if (normalized) { 9634 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9635 offset += node.nodeValue.length; 9636 } 9637 9638 point.push(offset); 9639 } else { 9640 childNodes = container.childNodes; 9641 9642 if (offset >= childNodes.length && childNodes.length) { 9643 after = 1; 9644 offset = Math.max(0, childNodes.length - 1); 9645 } 9646 9647 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9648 } 9649 9650 for (; container && container != root; container = container.parentNode) 9651 point.push(t.dom.nodeIndex(container, normalized)); 9652 9653 return point; 9654 }; 9655 9656 bookmark.start = getPoint(rng, true); 9657 9658 if (!t.isCollapsed()) 9659 bookmark.end = getPoint(rng); 9660 9661 return bookmark; 9662 }; 9663 9664 if (type == 2) { 9665 if (t.tridentSel) 9666 return t.tridentSel.getBookmark(type); 9667 9668 return getLocation(); 9669 } 9670 9671 // Handle simple range 9672 if (type) { 9673 rng = t.getRng(); 9674 9675 if (rng.setStart) { 9676 rng = { 9677 startContainer: rng.startContainer, 9678 startOffset: rng.startOffset, 9679 endContainer: rng.endContainer, 9680 endOffset: rng.endOffset 9681 }; 9682 } 9683 9684 return {rng : rng}; 9685 } 9686 9687 rng = t.getRng(); 9688 id = dom.uniqueId(); 9689 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9690 styles = 'overflow:hidden;line-height:0px'; 9691 9692 // Explorer method 9693 if (rng.duplicate || rng.item) { 9694 // Text selection 9695 if (!rng.item) { 9696 rng2 = rng.duplicate(); 9697 9698 try { 9699 // Insert start marker 9700 rng.collapse(); 9701 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9702 9703 // Insert end marker 9704 if (!collapsed) { 9705 rng2.collapse(false); 9706 9707 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9708 rng.moveToElementText(rng2.parentElement()); 9709 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9710 rng2.move('character', -1); 9711 9712 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9713 } 9714 } catch (ex) { 9715 // IE might throw unspecified error so lets ignore it 9716 return null; 9717 } 9718 } else { 9719 // Control selection 9720 element = rng.item(0); 9721 name = element.nodeName; 9722 9723 return {name : name, index : findIndex(name, element)}; 9724 } 9725 } else { 9726 element = t.getNode(); 9727 name = element.nodeName; 9728 if (name == 'IMG') 9729 return {name : name, index : findIndex(name, element)}; 9730 9731 // W3C method 9732 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9733 9734 // Insert end marker 9735 if (!collapsed) { 9736 rng2.collapse(false); 9737 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9738 } 9739 9740 rng = normalizeTableCellSelection(rng); 9741 rng.collapse(true); 9742 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9743 } 9744 9745 t.moveToBookmark({id : id, keep : 1}); 9746 9747 return {id : id}; 9748 }, 9749 9750 moveToBookmark : function(bookmark) { 9751 var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset; 9752 9753 function setEndPoint(start) { 9754 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9755 9756 if (point) { 9757 offset = point[0]; 9758 9759 // Find container node 9760 for (node = root, i = point.length - 1; i >= 1; i--) { 9761 children = node.childNodes; 9762 9763 if (point[i] > children.length - 1) 9764 return; 9765 9766 node = children[point[i]]; 9767 } 9768 9769 // Move text offset to best suitable location 9770 if (node.nodeType === 3) 9771 offset = Math.min(point[0], node.nodeValue.length); 9772 9773 // Move element offset to best suitable location 9774 if (node.nodeType === 1) 9775 offset = Math.min(point[0], node.childNodes.length); 9776 9777 // Set offset within container node 9778 if (start) 9779 rng.setStart(node, offset); 9780 else 9781 rng.setEnd(node, offset); 9782 } 9783 9784 return true; 9785 }; 9786 9787 function restoreEndPoint(suffix) { 9788 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9789 9790 if (marker) { 9791 node = marker.parentNode; 9792 9793 if (suffix == 'start') { 9794 if (!keep) { 9795 idx = dom.nodeIndex(marker); 9796 } else { 9797 node = marker.firstChild; 9798 idx = 1; 9799 } 9800 9801 startContainer = endContainer = node; 9802 startOffset = endOffset = idx; 9803 } else { 9804 if (!keep) { 9805 idx = dom.nodeIndex(marker); 9806 } else { 9807 node = marker.firstChild; 9808 idx = 1; 9809 } 9810 9811 endContainer = node; 9812 endOffset = idx; 9813 } 9814 9815 if (!keep) { 9816 prev = marker.previousSibling; 9817 next = marker.nextSibling; 9818 9819 // Remove all marker text nodes 9820 each(tinymce.grep(marker.childNodes), function(node) { 9821 if (node.nodeType == 3) 9822 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9823 }); 9824 9825 // Remove marker but keep children if for example contents where inserted into the marker 9826 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9827 while (marker = dom.get(bookmark.id + '_' + suffix)) 9828 dom.remove(marker, 1); 9829 9830 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9831 // 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 9832 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9833 idx = prev.nodeValue.length; 9834 prev.appendData(next.nodeValue); 9835 dom.remove(next); 9836 9837 if (suffix == 'start') { 9838 startContainer = endContainer = prev; 9839 startOffset = endOffset = idx; 9840 } else { 9841 endContainer = prev; 9842 endOffset = idx; 9843 } 9844 } 9845 } 9846 } 9847 }; 9848 9849 function addBogus(node) { 9850 // Adds a bogus BR element for empty block elements 9851 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9852 node.innerHTML = '<br data-mce-bogus="1" />'; 9853 9854 return node; 9855 }; 9856 9857 if (bookmark) { 9858 if (bookmark.start) { 9859 rng = dom.createRng(); 9860 root = dom.getRoot(); 9861 9862 if (t.tridentSel) 9863 return t.tridentSel.moveToBookmark(bookmark); 9864 9865 if (setEndPoint(true) && setEndPoint()) { 9866 t.setRng(rng); 9867 } 9868 } else if (bookmark.id) { 9869 // Restore start/end points 9870 restoreEndPoint('start'); 9871 restoreEndPoint('end'); 9872 9873 if (startContainer) { 9874 rng = dom.createRng(); 9875 rng.setStart(addBogus(startContainer), startOffset); 9876 rng.setEnd(addBogus(endContainer), endOffset); 9877 t.setRng(rng); 9878 } 9879 } else if (bookmark.name) { 9880 t.select(dom.select(bookmark.name)[bookmark.index]); 9881 } else if (bookmark.rng) { 9882 rng = bookmark.rng; 9883 9884 if (rng.startContainer) { 9885 rng2 = t.dom.createRng(); 9886 9887 try { 9888 rng2.setStart(rng.startContainer, rng.startOffset); 9889 rng2.setEnd(rng.endContainer, rng.endOffset); 9890 } catch (e) { 9891 // Might fail with index error 9892 } 9893 9894 rng = rng2; 9895 } 9896 9897 t.setRng(rng); 9898 } 9899 } 9900 }, 9901 9902 select : function(node, content) { 9903 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9904 9905 function setPoint(node, start) { 9906 var walker = new TreeWalker(node, node); 9907 9908 do { 9909 // Text node 9910 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9911 if (start) 9912 rng.setStart(node, 0); 9913 else 9914 rng.setEnd(node, node.nodeValue.length); 9915 9916 return; 9917 } 9918 9919 // BR element 9920 if (node.nodeName == 'BR') { 9921 if (start) 9922 rng.setStartBefore(node); 9923 else 9924 rng.setEndBefore(node); 9925 9926 return; 9927 } 9928 } while (node = (start ? walker.next() : walker.prev())); 9929 }; 9930 9931 if (node) { 9932 idx = dom.nodeIndex(node); 9933 rng.setStart(node.parentNode, idx); 9934 rng.setEnd(node.parentNode, idx + 1); 9935 9936 // Find first/last text node or BR element 9937 if (content) { 9938 setPoint(node, 1); 9939 setPoint(node); 9940 } 9941 9942 t.setRng(rng); 9943 } 9944 9945 return node; 9946 }, 9947 9948 isCollapsed : function() { 9949 var t = this, r = t.getRng(), s = t.getSel(); 9950 9951 if (!r || r.item) 9952 return false; 9953 9954 if (r.compareEndPoints) 9955 return r.compareEndPoints('StartToEnd', r) === 0; 9956 9957 return !s || r.collapsed; 9958 }, 9959 9960 collapse : function(to_start) { 9961 var self = this, rng = self.getRng(), node; 9962 9963 // Control range on IE 9964 if (rng.item) { 9965 node = rng.item(0); 9966 rng = self.win.document.body.createTextRange(); 9967 rng.moveToElementText(node); 9968 } 9969 9970 rng.collapse(!!to_start); 9971 self.setRng(rng); 9972 }, 9973 9974 getSel : function() { 9975 var t = this, w = this.win; 9976 9977 return w.getSelection ? w.getSelection() : w.document.selection; 9978 }, 9979 9980 getRng : function(w3c) { 9981 var self = this, selection, rng, elm, doc = self.win.document; 9982 9983 // Workaround for IE 11 not being able to select images properly see #6613 see quirk fix 9984 if (self.fakeRng) { 9985 return self.fakeRng; 9986 } 9987 9988 // Found tridentSel object then we need to use that one 9989 if (w3c && self.tridentSel) { 9990 return self.tridentSel.getRangeAt(0); 9991 } 9992 9993 try { 9994 if (selection = self.getSel()) { 9995 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9996 } 9997 } catch (ex) { 9998 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9999 } 10000 10001 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 10002 if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) { 10003 elm = doc.selection.createRange().item(0); 10004 rng = doc.createRange(); 10005 rng.setStartBefore(elm); 10006 rng.setEndAfter(elm); 10007 } 10008 10009 // No range found then create an empty one 10010 // This can occur when the editor is placed in a hidden container element on Gecko 10011 // Or on IE when there was an exception 10012 if (!rng) { 10013 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 10014 } 10015 10016 // If range is at start of document then move it to start of body 10017 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 10018 elm = self.dom.getRoot(); 10019 rng.setStart(elm, 0); 10020 rng.setEnd(elm, 0); 10021 } 10022 10023 if (self.selectedRange && self.explicitRange) { 10024 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 10025 // Safari, Opera and Chrome only ever select text which causes the range to change. 10026 // This lets us use the originally set range if the selection hasn't been changed by the user. 10027 rng = self.explicitRange; 10028 } else { 10029 self.selectedRange = null; 10030 self.explicitRange = null; 10031 } 10032 } 10033 10034 return rng; 10035 }, 10036 10037 setRng : function(r, forward) { 10038 var s, t = this; 10039 10040 if (!t.tridentSel) { 10041 s = t.getSel(); 10042 10043 if (s) { 10044 t.explicitRange = r; 10045 10046 try { 10047 s.removeAllRanges(); 10048 } catch (ex) { 10049 // IE9 might throw errors here don't know why 10050 } 10051 10052 s.addRange(r); 10053 10054 // Forward is set to false and we have an extend function 10055 if (forward === false && s.extend) { 10056 s.collapse(r.endContainer, r.endOffset); 10057 s.extend(r.startContainer, r.startOffset); 10058 } 10059 10060 // adding range isn't always successful so we need to check range count otherwise an exception can occur 10061 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 10062 } 10063 } else { 10064 // Is W3C Range 10065 if (r.cloneRange) { 10066 try { 10067 t.tridentSel.addRange(r); 10068 return; 10069 } catch (ex) { 10070 //IE9 throws an error here if called before selection is placed in the editor 10071 } 10072 } 10073 10074 // Is IE specific range 10075 try { 10076 r.select(); 10077 } catch (ex) { 10078 // Needed for some odd IE bug #1843306 10079 } 10080 } 10081 }, 10082 10083 setNode : function(n) { 10084 var t = this; 10085 10086 t.setContent(t.dom.getOuterHTML(n)); 10087 10088 return n; 10089 }, 10090 10091 getNode : function() { 10092 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 10093 10094 function skipEmptyTextNodes(n, forwards) { 10095 var orig = n; 10096 while (n && n.nodeType === 3 && n.length === 0) { 10097 n = forwards ? n.nextSibling : n.previousSibling; 10098 } 10099 return n || orig; 10100 }; 10101 10102 // Range maybe lost after the editor is made visible again 10103 if (!rng) 10104 return t.dom.getRoot(); 10105 10106 if (rng.setStart) { 10107 elm = rng.commonAncestorContainer; 10108 10109 // Handle selection a image or other control like element such as anchors 10110 if (!rng.collapsed) { 10111 if (rng.startContainer == rng.endContainer) { 10112 if (rng.endOffset - rng.startOffset < 2) { 10113 if (rng.startContainer.hasChildNodes()) 10114 elm = rng.startContainer.childNodes[rng.startOffset]; 10115 } 10116 } 10117 10118 // If the anchor node is a element instead of a text node then return this element 10119 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 10120 // return sel.anchorNode.childNodes[sel.anchorOffset]; 10121 10122 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 10123 // This happens when you double click an underlined word in FireFox. 10124 if (start.nodeType === 3 && end.nodeType === 3) { 10125 if (start.length === rng.startOffset) { 10126 start = skipEmptyTextNodes(start.nextSibling, true); 10127 } else { 10128 start = start.parentNode; 10129 } 10130 if (rng.endOffset === 0) { 10131 end = skipEmptyTextNodes(end.previousSibling, false); 10132 } else { 10133 end = end.parentNode; 10134 } 10135 10136 if (start && start === end) 10137 return start; 10138 } 10139 } 10140 10141 if (elm && elm.nodeType == 3) 10142 return elm.parentNode; 10143 10144 return elm; 10145 } 10146 10147 return rng.item ? rng.item(0) : rng.parentElement(); 10148 }, 10149 10150 getSelectedBlocks : function(st, en) { 10151 var t = this, dom = t.dom, sb, eb, n, bl = []; 10152 10153 sb = dom.getParent(st || t.getStart(), dom.isBlock); 10154 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 10155 10156 if (sb) 10157 bl.push(sb); 10158 10159 if (sb && eb && sb != eb) { 10160 n = sb; 10161 10162 var walker = new TreeWalker(sb, dom.getRoot()); 10163 while ((n = walker.next()) && n != eb) { 10164 if (dom.isBlock(n)) 10165 bl.push(n); 10166 } 10167 } 10168 10169 if (eb && sb != eb) 10170 bl.push(eb); 10171 10172 return bl; 10173 }, 10174 10175 isForward: function(){ 10176 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 10177 10178 // No support for selection direction then always return true 10179 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 10180 return true; 10181 } 10182 10183 anchorRange = dom.createRng(); 10184 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 10185 anchorRange.collapse(true); 10186 10187 focusRange = dom.createRng(); 10188 focusRange.setStart(sel.focusNode, sel.focusOffset); 10189 focusRange.collapse(true); 10190 10191 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 10192 }, 10193 10194 normalize : function() { 10195 var self = this, rng, normalized, collapsed, node, sibling; 10196 10197 function normalizeEndPoint(start) { 10198 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 10199 10200 function hasBrBeforeAfter(node, left) { 10201 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 10202 10203 while (node = walker[left ? 'prev' : 'next']()) { 10204 if (node.nodeName === "BR") { 10205 return true; 10206 } 10207 } 10208 }; 10209 10210 // Walks the dom left/right to find a suitable text node to move the endpoint into 10211 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 10212 function findTextNodeRelative(left, startNode) { 10213 var walker, lastInlineElement; 10214 10215 startNode = startNode || container; 10216 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 10217 10218 // Walk left until we hit a text node we can move to or a block/br/img 10219 while (node = walker[left ? 'prev' : 'next']()) { 10220 // Found text node that has a length 10221 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10222 container = node; 10223 offset = left ? node.nodeValue.length : 0; 10224 normalized = true; 10225 return; 10226 } 10227 10228 // Break if we find a block or a BR/IMG/INPUT etc 10229 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10230 return; 10231 } 10232 10233 lastInlineElement = node; 10234 } 10235 10236 // Only fetch the last inline element when in caret mode for now 10237 if (collapsed && lastInlineElement) { 10238 container = lastInlineElement; 10239 normalized = true; 10240 offset = 0; 10241 } 10242 }; 10243 10244 container = rng[(start ? 'start' : 'end') + 'Container']; 10245 offset = rng[(start ? 'start' : 'end') + 'Offset']; 10246 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 10247 10248 // If the container is a document move it to the body element 10249 if (container.nodeType === 9) { 10250 container = dom.getRoot(); 10251 offset = 0; 10252 } 10253 10254 // If the container is body try move it into the closest text node or position 10255 if (container === body) { 10256 // If start is before/after a image, table etc 10257 if (start) { 10258 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 10259 if (node) { 10260 nodeName = node.nodeName.toLowerCase(); 10261 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 10262 return; 10263 } 10264 } 10265 } 10266 10267 // Resolve the index 10268 if (container.hasChildNodes()) { 10269 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 10270 offset = 0; 10271 10272 // Don't walk into elements that doesn't have any child nodes like a IMG 10273 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 10274 // Walk the DOM to find a text node to place the caret at or a BR 10275 node = container; 10276 walker = new TreeWalker(container, body); 10277 10278 do { 10279 // Found a text node use that position 10280 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10281 offset = start ? 0 : node.nodeValue.length; 10282 container = node; 10283 normalized = true; 10284 break; 10285 } 10286 10287 // Found a BR/IMG element that we can place the caret before 10288 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10289 offset = dom.nodeIndex(node); 10290 container = node.parentNode; 10291 10292 // Put caret after image when moving the end point 10293 if (node.nodeName == "IMG" && !start) { 10294 offset++; 10295 } 10296 10297 normalized = true; 10298 break; 10299 } 10300 } while (node = (start ? walker.next() : walker.prev())); 10301 } 10302 } 10303 } 10304 10305 // Lean the caret to the left if possible 10306 if (collapsed) { 10307 // So this: <b>x</b><i>|x</i> 10308 // Becomes: <b>x|</b><i>x</i> 10309 // Seems that only gecko has issues with this 10310 if (container.nodeType === 3 && offset === 0) { 10311 findTextNodeRelative(true); 10312 } 10313 10314 // Lean left into empty inline elements when the caret is before a BR 10315 // So this: <i><b></b><i>|<br></i> 10316 // Becomes: <i><b>|</b><i><br></i> 10317 // Seems that only gecko has issues with this 10318 if (container.nodeType === 1) { 10319 node = container.childNodes[offset]; 10320 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 10321 findTextNodeRelative(true, container.childNodes[offset]); 10322 } 10323 } 10324 } 10325 10326 // Lean the start of the selection right if possible 10327 // So this: x[<b>x]</b> 10328 // Becomes: x<b>[x]</b> 10329 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 10330 findTextNodeRelative(false); 10331 } 10332 10333 // Set endpoint if it was normalized 10334 if (normalized) 10335 rng['set' + (start ? 'Start' : 'End')](container, offset); 10336 }; 10337 10338 // Normalize only on non IE browsers for now 10339 if (tinymce.isIE) 10340 return; 10341 10342 rng = self.getRng(); 10343 collapsed = rng.collapsed; 10344 10345 // Normalize the end points 10346 normalizeEndPoint(true); 10347 10348 if (!collapsed) 10349 normalizeEndPoint(); 10350 10351 // Set the selection if it was normalized 10352 if (normalized) { 10353 // If it was collapsed then make sure it still is 10354 if (collapsed) { 10355 rng.collapse(true); 10356 } 10357 10358 //console.log(self.dom.dumpRng(rng)); 10359 self.setRng(rng, self.isForward()); 10360 } 10361 }, 10362 10363 selectorChanged: function(selector, callback) { 10364 var self = this, currentSelectors; 10365 10366 if (!self.selectorChangedData) { 10367 self.selectorChangedData = {}; 10368 currentSelectors = {}; 10369 10370 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10371 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10372 10373 // Check for new matching selectors 10374 each(self.selectorChangedData, function(callbacks, selector) { 10375 each(parents, function(node) { 10376 if (dom.is(node, selector)) { 10377 if (!currentSelectors[selector]) { 10378 // Execute callbacks 10379 each(callbacks, function(callback) { 10380 callback(true, {node: node, selector: selector, parents: parents}); 10381 }); 10382 10383 currentSelectors[selector] = callbacks; 10384 } 10385 10386 matchedSelectors[selector] = callbacks; 10387 return false; 10388 } 10389 }); 10390 }); 10391 10392 // Check if current selectors still match 10393 each(currentSelectors, function(callbacks, selector) { 10394 if (!matchedSelectors[selector]) { 10395 delete currentSelectors[selector]; 10396 10397 each(callbacks, function(callback) { 10398 callback(false, {node: node, selector: selector, parents: parents}); 10399 }); 10400 } 10401 }); 10402 }); 10403 } 10404 10405 // Add selector listeners 10406 if (!self.selectorChangedData[selector]) { 10407 self.selectorChangedData[selector] = []; 10408 } 10409 10410 self.selectorChangedData[selector].push(callback); 10411 10412 return self; 10413 }, 10414 10415 scrollIntoView: function(elm) { 10416 var y, viewPort, self = this, dom = self.dom; 10417 10418 viewPort = dom.getViewPort(self.editor.getWin()); 10419 y = dom.getPos(elm).y; 10420 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 10421 self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); 10422 } 10423 }, 10424 10425 destroy : function(manual) { 10426 var self = this; 10427 10428 self.win = null; 10429 10430 // Manual destroy then remove unload handler 10431 if (!manual) 10432 tinymce.removeUnload(self.destroy); 10433 }, 10434 10435 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10436 _fixIESelection : function() { 10437 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10438 10439 // Return range from point or null if it failed 10440 function rngFromPoint(x, y) { 10441 var rng = body.createTextRange(); 10442 10443 try { 10444 rng.moveToPoint(x, y); 10445 } catch (ex) { 10446 // IE sometimes throws and exception, so lets just ignore it 10447 rng = null; 10448 } 10449 10450 return rng; 10451 }; 10452 10453 // Fires while the selection is changing 10454 function selectionChange(e) { 10455 var pointRng; 10456 10457 // Check if the button is down or not 10458 if (e.button) { 10459 // Create range from mouse position 10460 pointRng = rngFromPoint(e.x, e.y); 10461 10462 if (pointRng) { 10463 // Check if pointRange is before/after selection then change the endPoint 10464 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10465 pointRng.setEndPoint('StartToStart', startRng); 10466 else 10467 pointRng.setEndPoint('EndToEnd', startRng); 10468 10469 pointRng.select(); 10470 } 10471 } else 10472 endSelection(); 10473 } 10474 10475 // Removes listeners 10476 function endSelection() { 10477 var rng = doc.selection.createRange(); 10478 10479 // If the range is collapsed then use the last start range 10480 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10481 startRng.select(); 10482 10483 dom.unbind(doc, 'mouseup', endSelection); 10484 dom.unbind(doc, 'mousemove', selectionChange); 10485 startRng = started = 0; 10486 }; 10487 10488 // Make HTML element unselectable since we are going to handle selection by hand 10489 doc.documentElement.unselectable = true; 10490 10491 // Detect when user selects outside BODY 10492 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10493 if (e.target.nodeName === 'HTML') { 10494 if (started) 10495 endSelection(); 10496 10497 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10498 htmlElm = doc.documentElement; 10499 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10500 return; 10501 10502 started = 1; 10503 // Setup start position 10504 startRng = rngFromPoint(e.x, e.y); 10505 if (startRng) { 10506 // Listen for selection change events 10507 dom.bind(doc, 'mouseup', endSelection); 10508 dom.bind(doc, 'mousemove', selectionChange); 10509 10510 dom.win.focus(); 10511 startRng.select(); 10512 } 10513 } 10514 }); 10515 } 10516 }); 10517 })(tinymce); 10518 (function(tinymce) { 10519 tinymce.dom.Serializer = function(settings, dom, schema) { 10520 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10521 10522 // Support the old apply_source_formatting option 10523 if (!settings.apply_source_formatting) 10524 settings.indent = false; 10525 10526 // Default DOM and Schema if they are undefined 10527 dom = dom || tinymce.DOM; 10528 schema = schema || new tinymce.html.Schema(settings); 10529 settings.entity_encoding = settings.entity_encoding || 'named'; 10530 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10531 10532 onPreProcess = new tinymce.util.Dispatcher(self); 10533 10534 onPostProcess = new tinymce.util.Dispatcher(self); 10535 10536 htmlParser = new tinymce.html.DomParser(settings, schema); 10537 10538 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10539 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10540 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10541 10542 while (i--) { 10543 node = nodes[i]; 10544 10545 value = node.attributes.map[internalName]; 10546 if (value !== undef) { 10547 // Set external name to internal value and remove internal 10548 node.attr(name, value.length > 0 ? value : null); 10549 node.attr(internalName, null); 10550 } else { 10551 // No internal attribute found then convert the value we have in the DOM 10552 value = node.attributes.map[name]; 10553 10554 if (name === "style") 10555 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10556 else if (urlConverter) 10557 value = urlConverter.call(urlConverterScope, value, name, node.name); 10558 10559 node.attr(name, value.length > 0 ? value : null); 10560 } 10561 } 10562 }); 10563 10564 // Remove internal classes mceItem<..> or mceSelected 10565 htmlParser.addAttributeFilter('class', function(nodes, name) { 10566 var i = nodes.length, node, value; 10567 10568 while (i--) { 10569 node = nodes[i]; 10570 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10571 node.attr('class', value.length > 0 ? value : null); 10572 } 10573 }); 10574 10575 // Remove bookmark elements 10576 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10577 var i = nodes.length, node; 10578 10579 while (i--) { 10580 node = nodes[i]; 10581 10582 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10583 node.remove(); 10584 } 10585 }); 10586 10587 // Remove expando attributes 10588 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10589 var i = nodes.length; 10590 10591 while (i--) { 10592 nodes[i].attr(name, null); 10593 } 10594 }); 10595 10596 htmlParser.addNodeFilter('noscript', function(nodes) { 10597 var i = nodes.length, node; 10598 10599 while (i--) { 10600 node = nodes[i].firstChild; 10601 10602 if (node) { 10603 node.value = tinymce.html.Entities.decode(node.value); 10604 } 10605 } 10606 }); 10607 10608 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10609 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10610 var i = nodes.length, node, value; 10611 10612 function trim(value) { 10613 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10614 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10615 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10616 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10617 }; 10618 10619 while (i--) { 10620 node = nodes[i]; 10621 value = node.firstChild ? node.firstChild.value : ''; 10622 10623 if (name === "script") { 10624 // Remove mce- prefix from script elements 10625 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10626 10627 if (value.length > 0) 10628 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10629 } else { 10630 if (value.length > 0) 10631 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10632 } 10633 } 10634 }); 10635 10636 // Convert comments to cdata and handle protected comments 10637 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10638 var i = nodes.length, node; 10639 10640 while (i--) { 10641 node = nodes[i]; 10642 10643 if (node.value.indexOf('[CDATA[') === 0) { 10644 node.name = '#cdata'; 10645 node.type = 4; 10646 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10647 } else if (node.value.indexOf('mce:protected ') === 0) { 10648 node.name = "#text"; 10649 node.type = 3; 10650 node.raw = true; 10651 node.value = unescape(node.value).substr(14); 10652 } 10653 } 10654 }); 10655 10656 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10657 var i = nodes.length, node; 10658 10659 while (i--) { 10660 node = nodes[i]; 10661 if (node.type === 7) 10662 node.remove(); 10663 else if (node.type === 1) { 10664 if (name === "input" && !("type" in node.attributes.map)) 10665 node.attr('type', 'text'); 10666 } 10667 } 10668 }); 10669 10670 // Fix list elements, TODO: Replace this later 10671 if (settings.fix_list_elements) { 10672 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10673 var i = nodes.length, node, parentNode; 10674 10675 while (i--) { 10676 node = nodes[i]; 10677 parentNode = node.parent; 10678 10679 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10680 if (node.prev && node.prev.name === 'li') { 10681 node.prev.append(node); 10682 } 10683 } 10684 } 10685 }); 10686 } 10687 10688 // Remove internal data attributes 10689 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10690 var i = nodes.length; 10691 10692 while (i--) { 10693 nodes[i].attr(name, null); 10694 } 10695 }); 10696 10697 // Return public methods 10698 return { 10699 schema : schema, 10700 10701 addNodeFilter : htmlParser.addNodeFilter, 10702 10703 addAttributeFilter : htmlParser.addAttributeFilter, 10704 10705 onPreProcess : onPreProcess, 10706 10707 onPostProcess : onPostProcess, 10708 10709 serialize : function(node, args) { 10710 var impl, doc, oldDoc, htmlSerializer, content; 10711 10712 // Explorer won't clone contents of script and style and the 10713 // selected index of select elements are cleared on a clone operation. 10714 if (isIE && dom.select('script,style,select,map').length > 0) { 10715 content = node.innerHTML; 10716 node = node.cloneNode(false); 10717 dom.setHTML(node, content); 10718 } else 10719 node = node.cloneNode(true); 10720 10721 // Nodes needs to be attached to something in WebKit/Opera 10722 // Older builds of Opera crashes if you attach the node to an document created dynamically 10723 // and since we can't feature detect a crash we need to sniff the acutal build number 10724 // This fix will make DOM ranges and make Sizzle happy! 10725 impl = node.ownerDocument.implementation; 10726 if (impl.createHTMLDocument) { 10727 // Create an empty HTML document 10728 doc = impl.createHTMLDocument(""); 10729 10730 // Add the element or it's children if it's a body element to the new document 10731 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10732 doc.body.appendChild(doc.importNode(node, true)); 10733 }); 10734 10735 // Grab first child or body element for serialization 10736 if (node.nodeName != 'BODY') 10737 node = doc.body.firstChild; 10738 else 10739 node = doc.body; 10740 10741 // set the new document in DOMUtils so createElement etc works 10742 oldDoc = dom.doc; 10743 dom.doc = doc; 10744 } 10745 10746 args = args || {}; 10747 args.format = args.format || 'html'; 10748 10749 // Pre process 10750 if (!args.no_events) { 10751 args.node = node; 10752 onPreProcess.dispatch(self, args); 10753 } 10754 10755 // Setup serializer 10756 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10757 10758 // Parse and serialize HTML 10759 args.content = htmlSerializer.serialize( 10760 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10761 ); 10762 10763 // Replace all BOM characters for now until we can find a better solution 10764 if (!args.cleanup) 10765 args.content = args.content.replace(/\uFEFF/g, ''); 10766 10767 // Post process 10768 if (!args.no_events) 10769 onPostProcess.dispatch(self, args); 10770 10771 // Restore the old document if it was changed 10772 if (oldDoc) 10773 dom.doc = oldDoc; 10774 10775 args.node = null; 10776 10777 return args.content; 10778 }, 10779 10780 addRules : function(rules) { 10781 schema.addValidElements(rules); 10782 }, 10783 10784 setRules : function(rules) { 10785 schema.setValidElements(rules); 10786 } 10787 }; 10788 }; 10789 })(tinymce); 10790 (function(tinymce) { 10791 tinymce.dom.ScriptLoader = function(settings) { 10792 var QUEUED = 0, 10793 LOADING = 1, 10794 LOADED = 2, 10795 states = {}, 10796 queue = [], 10797 scriptLoadedCallbacks = {}, 10798 queueLoadedCallbacks = [], 10799 loading = 0, 10800 undef; 10801 10802 function loadScript(url, callback) { 10803 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10804 10805 // Execute callback when script is loaded 10806 function done() { 10807 dom.remove(id); 10808 10809 if (elm) 10810 elm.onreadystatechange = elm.onload = elm = null; 10811 10812 callback(); 10813 }; 10814 10815 function error() { 10816 // Report the error so it's easier for people to spot loading errors 10817 if (typeof(console) !== "undefined" && console.log) 10818 console.log("Failed to load: " + url); 10819 10820 // We can't mark it as done if there is a load error since 10821 // A) We don't want to produce 404 errors on the server and 10822 // B) the onerror event won't fire on all browsers. 10823 // done(); 10824 }; 10825 10826 id = dom.uniqueId(); 10827 10828 if (tinymce.isIE6) { 10829 uri = new tinymce.util.URI(url); 10830 loc = location; 10831 10832 // If script is from same domain and we 10833 // use IE 6 then use XHR since it's more reliable 10834 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10835 tinymce.util.XHR.send({ 10836 url : tinymce._addVer(uri.getURI()), 10837 success : function(content) { 10838 // Create new temp script element 10839 var script = dom.create('script', { 10840 type : 'text/javascript' 10841 }); 10842 10843 // Evaluate script in global scope 10844 script.text = content; 10845 document.getElementsByTagName('head')[0].appendChild(script); 10846 dom.remove(script); 10847 10848 done(); 10849 }, 10850 10851 error : error 10852 }); 10853 10854 return; 10855 } 10856 } 10857 10858 // Create new script element 10859 elm = document.createElement('script'); 10860 elm.id = id; 10861 elm.type = 'text/javascript'; 10862 elm.src = tinymce._addVer(url); 10863 10864 // Add onload listener for non IE browsers since IE9 10865 // fires onload event before the script is parsed and executed 10866 if (!tinymce.isIE || tinymce.isIE11) 10867 elm.onload = done; 10868 10869 // Add onerror event will get fired on some browsers but not all of them 10870 elm.onerror = error; 10871 10872 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10873 if (!tinymce.isOpera) { 10874 elm.onreadystatechange = function() { 10875 var state = elm.readyState; 10876 10877 // Loaded state is passed on IE 6 however there 10878 // are known issues with this method but we can't use 10879 // XHR in a cross domain loading 10880 if (state == 'complete' || state == 'loaded') 10881 done(); 10882 }; 10883 } 10884 10885 // Most browsers support this feature so we report errors 10886 // for those at least to help users track their missing plugins etc 10887 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10888 /*elm.onerror = function() { 10889 alert('Failed to load: ' + url); 10890 };*/ 10891 10892 // Add script to document 10893 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10894 }; 10895 10896 this.isDone = function(url) { 10897 return states[url] == LOADED; 10898 }; 10899 10900 this.markDone = function(url) { 10901 states[url] = LOADED; 10902 }; 10903 10904 this.add = this.load = function(url, callback, scope) { 10905 var item, state = states[url]; 10906 10907 // Add url to load queue 10908 if (state == undef) { 10909 queue.push(url); 10910 states[url] = QUEUED; 10911 } 10912 10913 if (callback) { 10914 // Store away callback for later execution 10915 if (!scriptLoadedCallbacks[url]) 10916 scriptLoadedCallbacks[url] = []; 10917 10918 scriptLoadedCallbacks[url].push({ 10919 func : callback, 10920 scope : scope || this 10921 }); 10922 } 10923 }; 10924 10925 this.loadQueue = function(callback, scope) { 10926 this.loadScripts(queue, callback, scope); 10927 }; 10928 10929 this.loadScripts = function(scripts, callback, scope) { 10930 var loadScripts; 10931 10932 function execScriptLoadedCallbacks(url) { 10933 // Execute URL callback functions 10934 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10935 callback.func.call(callback.scope); 10936 }); 10937 10938 scriptLoadedCallbacks[url] = undef; 10939 }; 10940 10941 queueLoadedCallbacks.push({ 10942 func : callback, 10943 scope : scope || this 10944 }); 10945 10946 loadScripts = function() { 10947 var loadingScripts = tinymce.grep(scripts); 10948 10949 // Current scripts has been handled 10950 scripts.length = 0; 10951 10952 // Load scripts that needs to be loaded 10953 tinymce.each(loadingScripts, function(url) { 10954 // Script is already loaded then execute script callbacks directly 10955 if (states[url] == LOADED) { 10956 execScriptLoadedCallbacks(url); 10957 return; 10958 } 10959 10960 // Is script not loading then start loading it 10961 if (states[url] != LOADING) { 10962 states[url] = LOADING; 10963 loading++; 10964 10965 loadScript(url, function() { 10966 states[url] = LOADED; 10967 loading--; 10968 10969 execScriptLoadedCallbacks(url); 10970 10971 // Load more scripts if they where added by the recently loaded script 10972 loadScripts(); 10973 }); 10974 } 10975 }); 10976 10977 // No scripts are currently loading then execute all pending queue loaded callbacks 10978 if (!loading) { 10979 tinymce.each(queueLoadedCallbacks, function(callback) { 10980 callback.func.call(callback.scope); 10981 }); 10982 10983 queueLoadedCallbacks.length = 0; 10984 } 10985 }; 10986 10987 loadScripts(); 10988 }; 10989 }; 10990 10991 // Global script loader 10992 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10993 })(tinymce); 10994 (function(tinymce) { 10995 tinymce.dom.RangeUtils = function(dom) { 10996 var INVISIBLE_CHAR = '\uFEFF'; 10997 10998 this.walk = function(rng, callback) { 10999 var startContainer = rng.startContainer, 11000 startOffset = rng.startOffset, 11001 endContainer = rng.endContainer, 11002 endOffset = rng.endOffset, 11003 ancestor, startPoint, 11004 endPoint, node, parent, siblings, nodes; 11005 11006 // Handle table cell selection the table plugin enables 11007 // you to fake select table cells and perform formatting actions on them 11008 nodes = dom.select('td.mceSelected,th.mceSelected'); 11009 if (nodes.length > 0) { 11010 tinymce.each(nodes, function(node) { 11011 callback([node]); 11012 }); 11013 11014 return; 11015 } 11016 11017 function exclude(nodes) { 11018 var node; 11019 11020 // First node is excluded 11021 node = nodes[0]; 11022 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 11023 nodes.splice(0, 1); 11024 } 11025 11026 // Last node is excluded 11027 node = nodes[nodes.length - 1]; 11028 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 11029 nodes.splice(nodes.length - 1, 1); 11030 } 11031 11032 return nodes; 11033 }; 11034 11035 function collectSiblings(node, name, end_node) { 11036 var siblings = []; 11037 11038 for (; node && node != end_node; node = node[name]) 11039 siblings.push(node); 11040 11041 return siblings; 11042 }; 11043 11044 function findEndPoint(node, root) { 11045 do { 11046 if (node.parentNode == root) 11047 return node; 11048 11049 node = node.parentNode; 11050 } while(node); 11051 }; 11052 11053 function walkBoundary(start_node, end_node, next) { 11054 var siblingName = next ? 'nextSibling' : 'previousSibling'; 11055 11056 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 11057 parent = node.parentNode; 11058 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 11059 11060 if (siblings.length) { 11061 if (!next) 11062 siblings.reverse(); 11063 11064 callback(exclude(siblings)); 11065 } 11066 } 11067 }; 11068 11069 // If index based start position then resolve it 11070 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 11071 startContainer = startContainer.childNodes[startOffset]; 11072 11073 // If index based end position then resolve it 11074 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 11075 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 11076 11077 // Same container 11078 if (startContainer == endContainer) 11079 return callback(exclude([startContainer])); 11080 11081 // Find common ancestor and end points 11082 ancestor = dom.findCommonAncestor(startContainer, endContainer); 11083 11084 // Process left side 11085 for (node = startContainer; node; node = node.parentNode) { 11086 if (node === endContainer) 11087 return walkBoundary(startContainer, ancestor, true); 11088 11089 if (node === ancestor) 11090 break; 11091 } 11092 11093 // Process right side 11094 for (node = endContainer; node; node = node.parentNode) { 11095 if (node === startContainer) 11096 return walkBoundary(endContainer, ancestor); 11097 11098 if (node === ancestor) 11099 break; 11100 } 11101 11102 // Find start/end point 11103 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 11104 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 11105 11106 // Walk left leaf 11107 walkBoundary(startContainer, startPoint, true); 11108 11109 // Walk the middle from start to end point 11110 siblings = collectSiblings( 11111 startPoint == startContainer ? startPoint : startPoint.nextSibling, 11112 'nextSibling', 11113 endPoint == endContainer ? endPoint.nextSibling : endPoint 11114 ); 11115 11116 if (siblings.length) 11117 callback(exclude(siblings)); 11118 11119 // Walk right leaf 11120 walkBoundary(endContainer, endPoint); 11121 }; 11122 11123 this.split = function(rng) { 11124 var startContainer = rng.startContainer, 11125 startOffset = rng.startOffset, 11126 endContainer = rng.endContainer, 11127 endOffset = rng.endOffset; 11128 11129 function splitText(node, offset) { 11130 return node.splitText(offset); 11131 }; 11132 11133 // Handle single text node 11134 if (startContainer == endContainer && startContainer.nodeType == 3) { 11135 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11136 endContainer = splitText(startContainer, startOffset); 11137 startContainer = endContainer.previousSibling; 11138 11139 if (endOffset > startOffset) { 11140 endOffset = endOffset - startOffset; 11141 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 11142 endOffset = endContainer.nodeValue.length; 11143 startOffset = 0; 11144 } else { 11145 endOffset = 0; 11146 } 11147 } 11148 } else { 11149 // Split startContainer text node if needed 11150 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11151 startContainer = splitText(startContainer, startOffset); 11152 startOffset = 0; 11153 } 11154 11155 // Split endContainer text node if needed 11156 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 11157 endContainer = splitText(endContainer, endOffset).previousSibling; 11158 endOffset = endContainer.nodeValue.length; 11159 } 11160 } 11161 11162 return { 11163 startContainer : startContainer, 11164 startOffset : startOffset, 11165 endContainer : endContainer, 11166 endOffset : endOffset 11167 }; 11168 }; 11169 11170 }; 11171 11172 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 11173 if (rng1 && rng2) { 11174 // Compare native IE ranges 11175 if (rng1.item || rng1.duplicate) { 11176 // Both are control ranges and the selected element matches 11177 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 11178 return true; 11179 11180 // Both are text ranges and the range matches 11181 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 11182 return true; 11183 } else { 11184 // Compare w3c ranges 11185 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 11186 } 11187 } 11188 11189 return false; 11190 }; 11191 })(tinymce); 11192 (function(tinymce) { 11193 var Event = tinymce.dom.Event, each = tinymce.each; 11194 11195 tinymce.create('tinymce.ui.KeyboardNavigation', { 11196 KeyboardNavigation: function(settings, dom) { 11197 var t = this, root = settings.root, items = settings.items, 11198 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 11199 excludeFromTabOrder = settings.excludeFromTabOrder, 11200 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 11201 11202 dom = dom || tinymce.DOM; 11203 11204 itemFocussed = function(evt) { 11205 focussedId = evt.target.id; 11206 }; 11207 11208 itemBlurred = function(evt) { 11209 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 11210 }; 11211 11212 rootFocussed = function(evt) { 11213 var item = dom.get(focussedId); 11214 dom.setAttrib(item, 'tabindex', '0'); 11215 item.focus(); 11216 }; 11217 11218 t.focus = function() { 11219 dom.get(focussedId).focus(); 11220 }; 11221 11222 t.destroy = function() { 11223 each(items, function(item) { 11224 var elm = dom.get(item.id); 11225 11226 dom.unbind(elm, 'focus', itemFocussed); 11227 dom.unbind(elm, 'blur', itemBlurred); 11228 }); 11229 11230 var rootElm = dom.get(root); 11231 dom.unbind(rootElm, 'focus', rootFocussed); 11232 dom.unbind(rootElm, 'keydown', rootKeydown); 11233 11234 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 11235 t.destroy = function() {}; 11236 }; 11237 11238 t.moveFocus = function(dir, evt) { 11239 var idx = -1, controls = t.controls, newFocus; 11240 11241 if (!focussedId) 11242 return; 11243 11244 each(items, function(item, index) { 11245 if (item.id === focussedId) { 11246 idx = index; 11247 return false; 11248 } 11249 }); 11250 11251 idx += dir; 11252 if (idx < 0) { 11253 idx = items.length - 1; 11254 } else if (idx >= items.length) { 11255 idx = 0; 11256 } 11257 11258 newFocus = items[idx]; 11259 dom.setAttrib(focussedId, 'tabindex', '-1'); 11260 dom.setAttrib(newFocus.id, 'tabindex', '0'); 11261 dom.get(newFocus.id).focus(); 11262 11263 if (settings.actOnFocus) { 11264 settings.onAction(newFocus.id); 11265 } 11266 11267 if (evt) 11268 Event.cancel(evt); 11269 }; 11270 11271 rootKeydown = function(evt) { 11272 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; 11273 11274 switch (evt.keyCode) { 11275 case DOM_VK_LEFT: 11276 if (enableLeftRight) t.moveFocus(-1); 11277 Event.cancel(evt); 11278 break; 11279 11280 case DOM_VK_RIGHT: 11281 if (enableLeftRight) t.moveFocus(1); 11282 Event.cancel(evt); 11283 break; 11284 11285 case DOM_VK_UP: 11286 if (enableUpDown) t.moveFocus(-1); 11287 Event.cancel(evt); 11288 break; 11289 11290 case DOM_VK_DOWN: 11291 if (enableUpDown) t.moveFocus(1); 11292 Event.cancel(evt); 11293 break; 11294 11295 case DOM_VK_ESCAPE: 11296 if (settings.onCancel) { 11297 settings.onCancel(); 11298 Event.cancel(evt); 11299 } 11300 break; 11301 11302 case DOM_VK_ENTER: 11303 case DOM_VK_RETURN: 11304 case DOM_VK_SPACE: 11305 if (settings.onAction) { 11306 settings.onAction(focussedId); 11307 Event.cancel(evt); 11308 } 11309 break; 11310 } 11311 }; 11312 11313 // Set up state and listeners for each item. 11314 each(items, function(item, idx) { 11315 var tabindex, elm; 11316 11317 if (!item.id) { 11318 item.id = dom.uniqueId('_mce_item_'); 11319 } 11320 11321 elm = dom.get(item.id); 11322 11323 if (excludeFromTabOrder) { 11324 dom.bind(elm, 'blur', itemBlurred); 11325 tabindex = '-1'; 11326 } else { 11327 tabindex = (idx === 0 ? '0' : '-1'); 11328 } 11329 11330 elm.setAttribute('tabindex', tabindex); 11331 dom.bind(elm, 'focus', itemFocussed); 11332 }); 11333 11334 // Setup initial state for root element. 11335 if (items[0]){ 11336 focussedId = items[0].id; 11337 } 11338 11339 dom.setAttrib(root, 'tabindex', '-1'); 11340 11341 // Setup listeners for root element. 11342 var rootElm = dom.get(root); 11343 dom.bind(rootElm, 'focus', rootFocussed); 11344 dom.bind(rootElm, 'keydown', rootKeydown); 11345 } 11346 }); 11347 })(tinymce); 11348 (function(tinymce) { 11349 // Shorten class names 11350 var DOM = tinymce.DOM, is = tinymce.is; 11351 11352 tinymce.create('tinymce.ui.Control', { 11353 Control : function(id, s, editor) { 11354 this.id = id; 11355 this.settings = s = s || {}; 11356 this.rendered = false; 11357 this.onRender = new tinymce.util.Dispatcher(this); 11358 this.classPrefix = ''; 11359 this.scope = s.scope || this; 11360 this.disabled = 0; 11361 this.active = 0; 11362 this.editor = editor; 11363 }, 11364 11365 setAriaProperty : function(property, value) { 11366 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11367 if (element) { 11368 DOM.setAttrib(element, 'aria-' + property, !!value); 11369 } 11370 }, 11371 11372 focus : function() { 11373 DOM.get(this.id).focus(); 11374 }, 11375 11376 setDisabled : function(s) { 11377 if (s != this.disabled) { 11378 this.setAriaProperty('disabled', s); 11379 11380 this.setState('Disabled', s); 11381 this.setState('Enabled', !s); 11382 this.disabled = s; 11383 } 11384 }, 11385 11386 isDisabled : function() { 11387 return this.disabled; 11388 }, 11389 11390 setActive : function(s) { 11391 if (s != this.active) { 11392 this.setState('Active', s); 11393 this.active = s; 11394 this.setAriaProperty('pressed', s); 11395 } 11396 }, 11397 11398 isActive : function() { 11399 return this.active; 11400 }, 11401 11402 setState : function(c, s) { 11403 var n = DOM.get(this.id); 11404 11405 c = this.classPrefix + c; 11406 11407 if (s) 11408 DOM.addClass(n, c); 11409 else 11410 DOM.removeClass(n, c); 11411 }, 11412 11413 isRendered : function() { 11414 return this.rendered; 11415 }, 11416 11417 renderHTML : function() { 11418 }, 11419 11420 renderTo : function(n) { 11421 DOM.setHTML(n, this.renderHTML()); 11422 }, 11423 11424 postRender : function() { 11425 var t = this, b; 11426 11427 // Set pending states 11428 if (is(t.disabled)) { 11429 b = t.disabled; 11430 t.disabled = -1; 11431 t.setDisabled(b); 11432 } 11433 11434 if (is(t.active)) { 11435 b = t.active; 11436 t.active = -1; 11437 t.setActive(b); 11438 } 11439 }, 11440 11441 remove : function() { 11442 DOM.remove(this.id); 11443 this.destroy(); 11444 }, 11445 11446 destroy : function() { 11447 tinymce.dom.Event.clear(this.id); 11448 } 11449 }); 11450 })(tinymce); 11451 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11452 Container : function(id, s, editor) { 11453 this.parent(id, s, editor); 11454 11455 this.controls = []; 11456 11457 this.lookup = {}; 11458 }, 11459 11460 add : function(c) { 11461 this.lookup[c.id] = c; 11462 this.controls.push(c); 11463 11464 return c; 11465 }, 11466 11467 get : function(n) { 11468 return this.lookup[n]; 11469 } 11470 }); 11471 11472 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11473 Separator : function(id, s) { 11474 this.parent(id, s); 11475 this.classPrefix = 'mceSeparator'; 11476 this.setDisabled(true); 11477 }, 11478 11479 renderHTML : function() { 11480 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11481 } 11482 }); 11483 (function(tinymce) { 11484 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11485 11486 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11487 MenuItem : function(id, s) { 11488 this.parent(id, s); 11489 this.classPrefix = 'mceMenuItem'; 11490 }, 11491 11492 setSelected : function(s) { 11493 this.setState('Selected', s); 11494 this.setAriaProperty('checked', !!s); 11495 this.selected = s; 11496 }, 11497 11498 isSelected : function() { 11499 return this.selected; 11500 }, 11501 11502 postRender : function() { 11503 var t = this; 11504 11505 t.parent(); 11506 11507 // Set pending state 11508 if (is(t.selected)) 11509 t.setSelected(t.selected); 11510 } 11511 }); 11512 })(tinymce); 11513 (function(tinymce) { 11514 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11515 11516 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11517 Menu : function(id, s) { 11518 var t = this; 11519 11520 t.parent(id, s); 11521 t.items = {}; 11522 t.collapsed = false; 11523 t.menuCount = 0; 11524 t.onAddItem = new tinymce.util.Dispatcher(this); 11525 }, 11526 11527 expand : function(d) { 11528 var t = this; 11529 11530 if (d) { 11531 walk(t, function(o) { 11532 if (o.expand) 11533 o.expand(); 11534 }, 'items', t); 11535 } 11536 11537 t.collapsed = false; 11538 }, 11539 11540 collapse : function(d) { 11541 var t = this; 11542 11543 if (d) { 11544 walk(t, function(o) { 11545 if (o.collapse) 11546 o.collapse(); 11547 }, 'items', t); 11548 } 11549 11550 t.collapsed = true; 11551 }, 11552 11553 isCollapsed : function() { 11554 return this.collapsed; 11555 }, 11556 11557 add : function(o) { 11558 if (!o.settings) 11559 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11560 11561 this.onAddItem.dispatch(this, o); 11562 11563 return this.items[o.id] = o; 11564 }, 11565 11566 addSeparator : function() { 11567 return this.add({separator : true}); 11568 }, 11569 11570 addMenu : function(o) { 11571 if (!o.collapse) 11572 o = this.createMenu(o); 11573 11574 this.menuCount++; 11575 11576 return this.add(o); 11577 }, 11578 11579 hasMenus : function() { 11580 return this.menuCount !== 0; 11581 }, 11582 11583 remove : function(o) { 11584 delete this.items[o.id]; 11585 }, 11586 11587 removeAll : function() { 11588 var t = this; 11589 11590 walk(t, function(o) { 11591 if (o.removeAll) 11592 o.removeAll(); 11593 else 11594 o.remove(); 11595 11596 o.destroy(); 11597 }, 'items', t); 11598 11599 t.items = {}; 11600 }, 11601 11602 createMenu : function(o) { 11603 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11604 11605 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11606 11607 return m; 11608 } 11609 }); 11610 })(tinymce); 11611 (function(tinymce) { 11612 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11613 11614 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11615 DropMenu : function(id, s) { 11616 s = s || {}; 11617 s.container = s.container || DOM.doc.body; 11618 s.offset_x = s.offset_x || 0; 11619 s.offset_y = s.offset_y || 0; 11620 s.vp_offset_x = s.vp_offset_x || 0; 11621 s.vp_offset_y = s.vp_offset_y || 0; 11622 11623 if (is(s.icons) && !s.icons) 11624 s['class'] += ' mceNoIcons'; 11625 11626 this.parent(id, s); 11627 this.onShowMenu = new tinymce.util.Dispatcher(this); 11628 this.onHideMenu = new tinymce.util.Dispatcher(this); 11629 this.classPrefix = 'mceMenu'; 11630 }, 11631 11632 createMenu : function(s) { 11633 var t = this, cs = t.settings, m; 11634 11635 s.container = s.container || cs.container; 11636 s.parent = t; 11637 s.constrain = s.constrain || cs.constrain; 11638 s['class'] = s['class'] || cs['class']; 11639 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11640 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11641 s.keyboard_focus = cs.keyboard_focus; 11642 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11643 11644 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11645 11646 return m; 11647 }, 11648 11649 focus : function() { 11650 var t = this; 11651 if (t.keyboardNav) { 11652 t.keyboardNav.focus(); 11653 } 11654 }, 11655 11656 update : function() { 11657 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11658 11659 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11660 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11661 11662 if (!DOM.boxModel) 11663 t.element.setStyles({width : tw + 2, height : th + 2}); 11664 else 11665 t.element.setStyles({width : tw, height : th}); 11666 11667 if (s.max_width) 11668 DOM.setStyle(co, 'width', tw); 11669 11670 if (s.max_height) { 11671 DOM.setStyle(co, 'height', th); 11672 11673 if (tb.clientHeight < s.max_height) 11674 DOM.setStyle(co, 'overflow', 'hidden'); 11675 } 11676 }, 11677 11678 showMenu : function(x, y, px) { 11679 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11680 11681 t.collapse(1); 11682 11683 if (t.isMenuVisible) 11684 return; 11685 11686 if (!t.rendered) { 11687 co = DOM.add(t.settings.container, t.renderNode()); 11688 11689 each(t.items, function(o) { 11690 o.postRender(); 11691 }); 11692 11693 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11694 } else 11695 co = DOM.get('menu_' + t.id); 11696 11697 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11698 if (!tinymce.isOpera) 11699 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11700 11701 DOM.show(co); 11702 t.update(); 11703 11704 x += s.offset_x || 0; 11705 y += s.offset_y || 0; 11706 vp.w -= 4; 11707 vp.h -= 4; 11708 11709 // Move inside viewport if not submenu 11710 if (s.constrain) { 11711 w = co.clientWidth - ot; 11712 h = co.clientHeight - ot; 11713 mx = vp.x + vp.w; 11714 my = vp.y + vp.h; 11715 11716 if ((x + s.vp_offset_x + w) > mx) 11717 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11718 11719 if ((y + s.vp_offset_y + h) > my) 11720 y = Math.max(0, (my - s.vp_offset_y) - h); 11721 } 11722 11723 DOM.setStyles(co, {left : x , top : y}); 11724 t.element.update(); 11725 11726 t.isMenuVisible = 1; 11727 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11728 var m; 11729 11730 e = e.target; 11731 11732 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11733 m = t.items[e.id]; 11734 11735 if (m.isDisabled()) 11736 return; 11737 11738 dm = t; 11739 11740 while (dm) { 11741 if (dm.hideMenu) 11742 dm.hideMenu(); 11743 11744 dm = dm.settings.parent; 11745 } 11746 11747 if (m.settings.onclick) 11748 m.settings.onclick(e); 11749 11750 return false; // Cancel to fix onbeforeunload problem 11751 } 11752 }); 11753 11754 if (t.hasMenus()) { 11755 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11756 var m, r, mi; 11757 11758 e = e.target; 11759 if (e && (e = DOM.getParent(e, 'tr'))) { 11760 m = t.items[e.id]; 11761 11762 if (t.lastMenu) 11763 t.lastMenu.collapse(1); 11764 11765 if (m.isDisabled()) 11766 return; 11767 11768 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11769 //p = DOM.getPos(s.container); 11770 r = DOM.getRect(e); 11771 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11772 t.lastMenu = m; 11773 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11774 } 11775 } 11776 }); 11777 } 11778 11779 Event.add(co, 'keydown', t._keyHandler, t); 11780 11781 t.onShowMenu.dispatch(t); 11782 11783 if (s.keyboard_focus) { 11784 t._setupKeyboardNav(); 11785 } 11786 }, 11787 11788 hideMenu : function(c) { 11789 var t = this, co = DOM.get('menu_' + t.id), e; 11790 11791 if (!t.isMenuVisible) 11792 return; 11793 11794 if (t.keyboardNav) t.keyboardNav.destroy(); 11795 Event.remove(co, 'mouseover', t.mouseOverFunc); 11796 Event.remove(co, 'click', t.mouseClickFunc); 11797 Event.remove(co, 'keydown', t._keyHandler); 11798 DOM.hide(co); 11799 t.isMenuVisible = 0; 11800 11801 if (!c) 11802 t.collapse(1); 11803 11804 if (t.element) 11805 t.element.hide(); 11806 11807 if (e = DOM.get(t.id)) 11808 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11809 11810 t.onHideMenu.dispatch(t); 11811 }, 11812 11813 add : function(o) { 11814 var t = this, co; 11815 11816 o = t.parent(o); 11817 11818 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11819 t._add(DOM.select('tbody', co)[0], o); 11820 11821 return o; 11822 }, 11823 11824 collapse : function(d) { 11825 this.parent(d); 11826 this.hideMenu(1); 11827 }, 11828 11829 remove : function(o) { 11830 DOM.remove(o.id); 11831 this.destroy(); 11832 11833 return this.parent(o); 11834 }, 11835 11836 destroy : function() { 11837 var t = this, co = DOM.get('menu_' + t.id); 11838 11839 if (t.keyboardNav) t.keyboardNav.destroy(); 11840 Event.remove(co, 'mouseover', t.mouseOverFunc); 11841 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11842 Event.remove(co, 'click', t.mouseClickFunc); 11843 Event.remove(co, 'keydown', t._keyHandler); 11844 11845 if (t.element) 11846 t.element.remove(); 11847 11848 DOM.remove(co); 11849 }, 11850 11851 renderNode : function() { 11852 var t = this, s = t.settings, n, tb, co, w; 11853 11854 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'}); 11855 if (t.settings.parent) { 11856 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11857 } 11858 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11859 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11860 11861 if (s.menu_line) 11862 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11863 11864 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11865 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11866 tb = DOM.add(n, 'tbody'); 11867 11868 each(t.items, function(o) { 11869 t._add(tb, o); 11870 }); 11871 11872 t.rendered = true; 11873 11874 return w; 11875 }, 11876 11877 // Internal functions 11878 _setupKeyboardNav : function(){ 11879 var contextMenu, menuItems, t=this; 11880 contextMenu = DOM.get('menu_' + t.id); 11881 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11882 menuItems.splice(0,0,contextMenu); 11883 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11884 root: 'menu_' + t.id, 11885 items: menuItems, 11886 onCancel: function() { 11887 t.hideMenu(); 11888 }, 11889 enableUpDown: true 11890 }); 11891 contextMenu.focus(); 11892 }, 11893 11894 _keyHandler : function(evt) { 11895 var t = this, e; 11896 switch (evt.keyCode) { 11897 case 37: // Left 11898 if (t.settings.parent) { 11899 t.hideMenu(); 11900 t.settings.parent.focus(); 11901 Event.cancel(evt); 11902 } 11903 break; 11904 case 39: // Right 11905 if (t.mouseOverFunc) 11906 t.mouseOverFunc(evt); 11907 break; 11908 } 11909 }, 11910 11911 _add : function(tb, o) { 11912 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11913 11914 if (s.separator) { 11915 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11916 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11917 11918 if (n = ro.previousSibling) 11919 DOM.addClass(n, 'mceLast'); 11920 11921 return; 11922 } 11923 11924 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11925 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11926 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11927 11928 if (s.parent) { 11929 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11930 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11931 } 11932 11933 DOM.addClass(it, s['class']); 11934 // n = DOM.add(n, 'span', {'class' : 'item'}); 11935 11936 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11937 11938 if (s.icon_src) 11939 DOM.add(ic, 'img', {src : s.icon_src}); 11940 11941 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11942 11943 if (o.settings.style) { 11944 if (typeof o.settings.style == "function") 11945 o.settings.style = o.settings.style(); 11946 11947 DOM.setAttrib(n, 'style', o.settings.style); 11948 } 11949 11950 if (tb.childNodes.length == 1) 11951 DOM.addClass(ro, 'mceFirst'); 11952 11953 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11954 DOM.addClass(ro, 'mceFirst'); 11955 11956 if (o.collapse) 11957 DOM.addClass(ro, cp + 'ItemSub'); 11958 11959 if (n = ro.previousSibling) 11960 DOM.removeClass(n, 'mceLast'); 11961 11962 DOM.addClass(ro, 'mceLast'); 11963 } 11964 }); 11965 })(tinymce); 11966 (function(tinymce) { 11967 var DOM = tinymce.DOM; 11968 11969 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11970 Button : function(id, s, ed) { 11971 this.parent(id, s, ed); 11972 this.classPrefix = 'mceButton'; 11973 }, 11974 11975 renderHTML : function() { 11976 var cp = this.classPrefix, s = this.settings, h, l; 11977 11978 l = DOM.encode(s.label || ''); 11979 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) + '">'; 11980 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11981 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11982 else 11983 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11984 11985 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11986 h += '</a>'; 11987 return h; 11988 }, 11989 11990 postRender : function() { 11991 var t = this, s = t.settings, imgBookmark; 11992 11993 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11994 // need to keep the selection in case the selection is lost 11995 if (tinymce.isIE && t.editor) { 11996 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11997 var nodeName = t.editor.selection.getNode().nodeName; 11998 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11999 }); 12000 } 12001 tinymce.dom.Event.add(t.id, 'click', function(e) { 12002 if (!t.isDisabled()) { 12003 // restore the selection in case the selection is lost in IE 12004 if (tinymce.isIE && t.editor && imgBookmark !== null) { 12005 t.editor.selection.moveToBookmark(imgBookmark); 12006 } 12007 return s.onclick.call(s.scope, e); 12008 } 12009 }); 12010 tinymce.dom.Event.add(t.id, 'keydown', function(e) { 12011 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) { 12012 tinymce.dom.Event.cancel(e); 12013 return s.onclick.call(s.scope, e); 12014 } 12015 }); 12016 } 12017 }); 12018 })(tinymce); 12019 (function(tinymce) { 12020 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 12021 12022 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 12023 ListBox : function(id, s, ed) { 12024 var t = this; 12025 12026 t.parent(id, s, ed); 12027 12028 t.items = []; 12029 12030 t.onChange = new Dispatcher(t); 12031 12032 t.onPostRender = new Dispatcher(t); 12033 12034 t.onAdd = new Dispatcher(t); 12035 12036 t.onRenderMenu = new tinymce.util.Dispatcher(this); 12037 12038 t.classPrefix = 'mceListBox'; 12039 t.marked = {}; 12040 }, 12041 12042 select : function(va) { 12043 var t = this, fv, f; 12044 12045 t.marked = {}; 12046 12047 if (va == undef) 12048 return t.selectByIndex(-1); 12049 12050 // Is string or number make function selector 12051 if (va && typeof(va)=="function") 12052 f = va; 12053 else { 12054 f = function(v) { 12055 return v == va; 12056 }; 12057 } 12058 12059 // Do we need to do something? 12060 if (va != t.selectedValue) { 12061 // Find item 12062 each(t.items, function(o, i) { 12063 if (f(o.value)) { 12064 fv = 1; 12065 t.selectByIndex(i); 12066 return false; 12067 } 12068 }); 12069 12070 if (!fv) 12071 t.selectByIndex(-1); 12072 } 12073 }, 12074 12075 selectByIndex : function(idx) { 12076 var t = this, e, o, label; 12077 12078 t.marked = {}; 12079 12080 if (idx != t.selectedIndex) { 12081 e = DOM.get(t.id + '_text'); 12082 label = DOM.get(t.id + '_voiceDesc'); 12083 o = t.items[idx]; 12084 12085 if (o) { 12086 t.selectedValue = o.value; 12087 t.selectedIndex = idx; 12088 DOM.setHTML(e, DOM.encode(o.title)); 12089 DOM.setHTML(label, t.settings.title + " - " + o.title); 12090 DOM.removeClass(e, 'mceTitle'); 12091 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 12092 } else { 12093 DOM.setHTML(e, DOM.encode(t.settings.title)); 12094 DOM.setHTML(label, DOM.encode(t.settings.title)); 12095 DOM.addClass(e, 'mceTitle'); 12096 t.selectedValue = t.selectedIndex = null; 12097 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 12098 } 12099 e = 0; 12100 } 12101 }, 12102 12103 mark : function(value) { 12104 this.marked[value] = true; 12105 }, 12106 12107 add : function(n, v, o) { 12108 var t = this; 12109 12110 o = o || {}; 12111 o = tinymce.extend(o, { 12112 title : n, 12113 value : v 12114 }); 12115 12116 t.items.push(o); 12117 t.onAdd.dispatch(t, o); 12118 }, 12119 12120 getLength : function() { 12121 return this.items.length; 12122 }, 12123 12124 renderHTML : function() { 12125 var h = '', t = this, s = t.settings, cp = t.classPrefix; 12126 12127 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 12128 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 12129 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>'; 12130 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>'; 12131 h += '</tr></tbody></table></span>'; 12132 12133 return h; 12134 }, 12135 12136 showMenu : function() { 12137 var t = this, p2, e = DOM.get(this.id), m; 12138 12139 if (t.isDisabled() || t.items.length === 0) 12140 return; 12141 12142 if (t.menu && t.menu.isMenuVisible) 12143 return t.hideMenu(); 12144 12145 if (!t.isMenuRendered) { 12146 t.renderMenu(); 12147 t.isMenuRendered = true; 12148 } 12149 12150 p2 = DOM.getPos(e); 12151 12152 m = t.menu; 12153 m.settings.offset_x = p2.x; 12154 m.settings.offset_y = p2.y; 12155 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 12156 12157 // Select in menu 12158 each(t.items, function(o) { 12159 if (m.items[o.id]) { 12160 m.items[o.id].setSelected(0); 12161 } 12162 }); 12163 12164 each(t.items, function(o) { 12165 if (m.items[o.id] && t.marked[o.value]) { 12166 m.items[o.id].setSelected(1); 12167 } 12168 12169 if (o.value === t.selectedValue) { 12170 m.items[o.id].setSelected(1); 12171 } 12172 }); 12173 12174 m.showMenu(0, e.clientHeight); 12175 12176 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12177 DOM.addClass(t.id, t.classPrefix + 'Selected'); 12178 12179 //DOM.get(t.id + '_text').focus(); 12180 }, 12181 12182 hideMenu : function(e) { 12183 var t = this; 12184 12185 if (t.menu && t.menu.isMenuVisible) { 12186 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12187 12188 // Prevent double toogles by canceling the mouse click event to the button 12189 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 12190 return; 12191 12192 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12193 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12194 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12195 t.menu.hideMenu(); 12196 } 12197 } 12198 }, 12199 12200 renderMenu : function() { 12201 var t = this, m; 12202 12203 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12204 menu_line : 1, 12205 'class' : t.classPrefix + 'Menu mceNoIcons', 12206 max_width : 250, 12207 max_height : 150 12208 }); 12209 12210 m.onHideMenu.add(function() { 12211 t.hideMenu(); 12212 t.focus(); 12213 }); 12214 12215 m.add({ 12216 title : t.settings.title, 12217 'class' : 'mceMenuItemTitle', 12218 onclick : function() { 12219 if (t.settings.onselect('') !== false) 12220 t.select(''); // Must be runned after 12221 } 12222 }); 12223 12224 each(t.items, function(o) { 12225 // No value then treat it as a title 12226 if (o.value === undef) { 12227 m.add({ 12228 title : o.title, 12229 role : "option", 12230 'class' : 'mceMenuItemTitle', 12231 onclick : function() { 12232 if (t.settings.onselect('') !== false) 12233 t.select(''); // Must be runned after 12234 } 12235 }); 12236 } else { 12237 o.id = DOM.uniqueId(); 12238 o.role= "option"; 12239 o.onclick = function() { 12240 if (t.settings.onselect(o.value) !== false) 12241 t.select(o.value); // Must be runned after 12242 }; 12243 12244 m.add(o); 12245 } 12246 }); 12247 12248 t.onRenderMenu.dispatch(t, m); 12249 t.menu = m; 12250 }, 12251 12252 postRender : function() { 12253 var t = this, cp = t.classPrefix; 12254 12255 Event.add(t.id, 'click', t.showMenu, t); 12256 Event.add(t.id, 'keydown', function(evt) { 12257 if (evt.keyCode == 32) { // Space 12258 t.showMenu(evt); 12259 Event.cancel(evt); 12260 } 12261 }); 12262 Event.add(t.id, 'focus', function() { 12263 if (!t._focused) { 12264 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 12265 if (e.keyCode == 40) { 12266 t.showMenu(); 12267 Event.cancel(e); 12268 } 12269 }); 12270 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 12271 var v; 12272 if (e.keyCode == 13) { 12273 // Fake select on enter 12274 v = t.selectedValue; 12275 t.selectedValue = null; // Needs to be null to fake change 12276 Event.cancel(e); 12277 t.settings.onselect(v); 12278 } 12279 }); 12280 } 12281 12282 t._focused = 1; 12283 }); 12284 Event.add(t.id, 'blur', function() { 12285 Event.remove(t.id, 'keydown', t.keyDownHandler); 12286 Event.remove(t.id, 'keypress', t.keyPressHandler); 12287 t._focused = 0; 12288 }); 12289 12290 // Old IE doesn't have hover on all elements 12291 if (tinymce.isIE6 || !DOM.boxModel) { 12292 Event.add(t.id, 'mouseover', function() { 12293 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12294 DOM.addClass(t.id, cp + 'Hover'); 12295 }); 12296 12297 Event.add(t.id, 'mouseout', function() { 12298 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12299 DOM.removeClass(t.id, cp + 'Hover'); 12300 }); 12301 } 12302 12303 t.onPostRender.dispatch(t, DOM.get(t.id)); 12304 }, 12305 12306 destroy : function() { 12307 this.parent(); 12308 12309 Event.clear(this.id + '_text'); 12310 Event.clear(this.id + '_open'); 12311 } 12312 }); 12313 })(tinymce); 12314 (function(tinymce) { 12315 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 12316 12317 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 12318 NativeListBox : function(id, s) { 12319 this.parent(id, s); 12320 this.classPrefix = 'mceNativeListBox'; 12321 }, 12322 12323 setDisabled : function(s) { 12324 DOM.get(this.id).disabled = s; 12325 this.setAriaProperty('disabled', s); 12326 }, 12327 12328 isDisabled : function() { 12329 return DOM.get(this.id).disabled; 12330 }, 12331 12332 select : function(va) { 12333 var t = this, fv, f; 12334 12335 if (va == undef) 12336 return t.selectByIndex(-1); 12337 12338 // Is string or number make function selector 12339 if (va && typeof(va)=="function") 12340 f = va; 12341 else { 12342 f = function(v) { 12343 return v == va; 12344 }; 12345 } 12346 12347 // Do we need to do something? 12348 if (va != t.selectedValue) { 12349 // Find item 12350 each(t.items, function(o, i) { 12351 if (f(o.value)) { 12352 fv = 1; 12353 t.selectByIndex(i); 12354 return false; 12355 } 12356 }); 12357 12358 if (!fv) 12359 t.selectByIndex(-1); 12360 } 12361 }, 12362 12363 selectByIndex : function(idx) { 12364 DOM.get(this.id).selectedIndex = idx + 1; 12365 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12366 }, 12367 12368 add : function(n, v, a) { 12369 var o, t = this; 12370 12371 a = a || {}; 12372 a.value = v; 12373 12374 if (t.isRendered()) 12375 DOM.add(DOM.get(this.id), 'option', a, n); 12376 12377 o = { 12378 title : n, 12379 value : v, 12380 attribs : a 12381 }; 12382 12383 t.items.push(o); 12384 t.onAdd.dispatch(t, o); 12385 }, 12386 12387 getLength : function() { 12388 return this.items.length; 12389 }, 12390 12391 renderHTML : function() { 12392 var h, t = this; 12393 12394 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12395 12396 each(t.items, function(it) { 12397 h += DOM.createHTML('option', {value : it.value}, it.title); 12398 }); 12399 12400 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12401 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12402 return h; 12403 }, 12404 12405 postRender : function() { 12406 var t = this, ch, changeListenerAdded = true; 12407 12408 t.rendered = true; 12409 12410 function onChange(e) { 12411 var v = t.items[e.target.selectedIndex - 1]; 12412 12413 if (v && (v = v.value)) { 12414 t.onChange.dispatch(t, v); 12415 12416 if (t.settings.onselect) 12417 t.settings.onselect(v); 12418 } 12419 }; 12420 12421 Event.add(t.id, 'change', onChange); 12422 12423 // Accessibility keyhandler 12424 Event.add(t.id, 'keydown', function(e) { 12425 var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 12426 12427 Event.remove(t.id, 'change', ch); 12428 changeListenerAdded = false; 12429 12430 bf = Event.add(t.id, 'blur', function() { 12431 if (changeListenerAdded) return; 12432 changeListenerAdded = true; 12433 Event.add(t.id, 'change', onChange); 12434 Event.remove(t.id, 'blur', bf); 12435 }); 12436 12437 if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) { 12438 onChange(e); 12439 return Event.cancel(e); 12440 } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) { 12441 // allow native implementation (navigate select element options) 12442 e.stopImmediatePropagation(); 12443 } 12444 }); 12445 12446 t.onPostRender.dispatch(t, DOM.get(t.id)); 12447 } 12448 }); 12449 })(tinymce); 12450 (function(tinymce) { 12451 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12452 12453 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12454 MenuButton : function(id, s, ed) { 12455 this.parent(id, s, ed); 12456 12457 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12458 12459 s.menu_container = s.menu_container || DOM.doc.body; 12460 }, 12461 12462 showMenu : function() { 12463 var t = this, p1, p2, e = DOM.get(t.id), m; 12464 12465 if (t.isDisabled()) 12466 return; 12467 12468 if (!t.isMenuRendered) { 12469 t.renderMenu(); 12470 t.isMenuRendered = true; 12471 } 12472 12473 if (t.isMenuVisible) 12474 return t.hideMenu(); 12475 12476 p1 = DOM.getPos(t.settings.menu_container); 12477 p2 = DOM.getPos(e); 12478 12479 m = t.menu; 12480 m.settings.offset_x = p2.x; 12481 m.settings.offset_y = p2.y; 12482 m.settings.vp_offset_x = p2.x; 12483 m.settings.vp_offset_y = p2.y; 12484 m.settings.keyboard_focus = t._focused; 12485 m.showMenu(0, e.firstChild.clientHeight); 12486 12487 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12488 t.setState('Selected', 1); 12489 12490 t.isMenuVisible = 1; 12491 }, 12492 12493 renderMenu : function() { 12494 var t = this, m; 12495 12496 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12497 menu_line : 1, 12498 'class' : this.classPrefix + 'Menu', 12499 icons : t.settings.icons 12500 }); 12501 12502 m.onHideMenu.add(function() { 12503 t.hideMenu(); 12504 t.focus(); 12505 }); 12506 12507 t.onRenderMenu.dispatch(t, m); 12508 t.menu = m; 12509 }, 12510 12511 hideMenu : function(e) { 12512 var t = this; 12513 12514 // Prevent double toogles by canceling the mouse click event to the button 12515 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12516 return; 12517 12518 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12519 t.setState('Selected', 0); 12520 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12521 if (t.menu) 12522 t.menu.hideMenu(); 12523 } 12524 12525 t.isMenuVisible = 0; 12526 }, 12527 12528 postRender : function() { 12529 var t = this, s = t.settings; 12530 12531 Event.add(t.id, 'click', function() { 12532 if (!t.isDisabled()) { 12533 if (s.onclick) 12534 s.onclick(t.value); 12535 12536 t.showMenu(); 12537 } 12538 }); 12539 } 12540 }); 12541 })(tinymce); 12542 (function(tinymce) { 12543 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12544 12545 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12546 SplitButton : function(id, s, ed) { 12547 this.parent(id, s, ed); 12548 this.classPrefix = 'mceSplitButton'; 12549 }, 12550 12551 renderHTML : function() { 12552 var h, t = this, s = t.settings, h1; 12553 12554 h = '<tbody><tr>'; 12555 12556 if (s.image) 12557 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12558 else 12559 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12560 12561 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12562 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>'; 12563 12564 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12565 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>'; 12566 12567 h += '</tr></tbody>'; 12568 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12569 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12570 }, 12571 12572 postRender : function() { 12573 var t = this, s = t.settings, activate; 12574 12575 if (s.onclick) { 12576 activate = function(evt) { 12577 if (!t.isDisabled()) { 12578 s.onclick(t.value); 12579 Event.cancel(evt); 12580 } 12581 }; 12582 Event.add(t.id + '_action', 'click', activate); 12583 Event.add(t.id, ['click', 'keydown'], function(evt) { 12584 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12585 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12586 activate(); 12587 Event.cancel(evt); 12588 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12589 t.showMenu(); 12590 Event.cancel(evt); 12591 } 12592 }); 12593 } 12594 12595 Event.add(t.id + '_open', 'click', function (evt) { 12596 t.showMenu(); 12597 Event.cancel(evt); 12598 }); 12599 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12600 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12601 12602 // Old IE doesn't have hover on all elements 12603 if (tinymce.isIE6 || !DOM.boxModel) { 12604 Event.add(t.id, 'mouseover', function() { 12605 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12606 DOM.addClass(t.id, 'mceSplitButtonHover'); 12607 }); 12608 12609 Event.add(t.id, 'mouseout', function() { 12610 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12611 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12612 }); 12613 } 12614 }, 12615 12616 destroy : function() { 12617 this.parent(); 12618 12619 Event.clear(this.id + '_action'); 12620 Event.clear(this.id + '_open'); 12621 Event.clear(this.id); 12622 } 12623 }); 12624 })(tinymce); 12625 (function(tinymce) { 12626 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12627 12628 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12629 ColorSplitButton : function(id, s, ed) { 12630 var t = this; 12631 12632 t.parent(id, s, ed); 12633 12634 t.settings = s = tinymce.extend({ 12635 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', 12636 grid_width : 8, 12637 default_color : '#888888' 12638 }, t.settings); 12639 12640 t.onShowMenu = new tinymce.util.Dispatcher(t); 12641 12642 t.onHideMenu = new tinymce.util.Dispatcher(t); 12643 12644 t.value = s.default_color; 12645 }, 12646 12647 showMenu : function() { 12648 var t = this, r, p, e, p2; 12649 12650 if (t.isDisabled()) 12651 return; 12652 12653 if (!t.isMenuRendered) { 12654 t.renderMenu(); 12655 t.isMenuRendered = true; 12656 } 12657 12658 if (t.isMenuVisible) 12659 return t.hideMenu(); 12660 12661 e = DOM.get(t.id); 12662 DOM.show(t.id + '_menu'); 12663 DOM.addClass(e, 'mceSplitButtonSelected'); 12664 p2 = DOM.getPos(e); 12665 DOM.setStyles(t.id + '_menu', { 12666 left : p2.x, 12667 top : p2.y + e.firstChild.clientHeight, 12668 zIndex : 200000 12669 }); 12670 e = 0; 12671 12672 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12673 t.onShowMenu.dispatch(t); 12674 12675 if (t._focused) { 12676 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12677 if (e.keyCode == 27) 12678 t.hideMenu(); 12679 }); 12680 12681 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12682 } 12683 12684 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12685 root: t.id + '_menu', 12686 items: DOM.select('a', t.id + '_menu'), 12687 onCancel: function() { 12688 t.hideMenu(); 12689 t.focus(); 12690 } 12691 }); 12692 12693 t.keyboardNav.focus(); 12694 t.isMenuVisible = 1; 12695 }, 12696 12697 hideMenu : function(e) { 12698 var t = this; 12699 12700 if (t.isMenuVisible) { 12701 // Prevent double toogles by canceling the mouse click event to the button 12702 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12703 return; 12704 12705 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12706 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12707 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12708 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12709 DOM.hide(t.id + '_menu'); 12710 } 12711 12712 t.isMenuVisible = 0; 12713 t.onHideMenu.dispatch(); 12714 t.keyboardNav.destroy(); 12715 } 12716 }, 12717 12718 renderMenu : function() { 12719 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12720 12721 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;'}); 12722 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12723 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12724 12725 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12726 tb = DOM.add(n, 'tbody'); 12727 12728 // Generate color grid 12729 i = 0; 12730 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12731 c = c.replace(/^#/, ''); 12732 12733 if (!i--) { 12734 tr = DOM.add(tb, 'tr'); 12735 i = s.grid_width - 1; 12736 } 12737 12738 n = DOM.add(tr, 'td'); 12739 var settings = { 12740 href : 'javascript:;', 12741 style : { 12742 backgroundColor : '#' + c 12743 }, 12744 'title': t.editor.getLang('colors.' + c, c), 12745 'data-mce-color' : '#' + c 12746 }; 12747 12748 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12749 if (!tinymce.isIE ) { 12750 settings.role = 'option'; 12751 } 12752 12753 n = DOM.add(n, 'a', settings); 12754 12755 if (t.editor.forcedHighContrastMode) { 12756 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12757 if (n.getContext && (context = n.getContext("2d"))) { 12758 context.fillStyle = '#' + c; 12759 context.fillRect(0, 0, 16, 16); 12760 } else { 12761 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12762 DOM.remove(n); 12763 } 12764 } 12765 }); 12766 12767 if (s.more_colors_func) { 12768 n = DOM.add(tb, 'tr'); 12769 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12770 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12771 12772 Event.add(n, 'click', function(e) { 12773 s.more_colors_func.call(s.more_colors_scope || this); 12774 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12775 }); 12776 } 12777 12778 DOM.addClass(m, 'mceColorSplitMenu'); 12779 12780 // Prevent IE from scrolling and hindering click to occur #4019 12781 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12782 12783 Event.add(t.id + '_menu', 'click', function(e) { 12784 var c; 12785 12786 e = DOM.getParent(e.target, 'a', tb); 12787 12788 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12789 t.setColor(c); 12790 12791 return false; // Prevent IE auto save warning 12792 }); 12793 12794 return w; 12795 }, 12796 12797 setColor : function(c) { 12798 this.displayColor(c); 12799 this.hideMenu(); 12800 this.settings.onselect(c); 12801 }, 12802 12803 displayColor : function(c) { 12804 var t = this; 12805 12806 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12807 12808 t.value = c; 12809 }, 12810 12811 postRender : function() { 12812 var t = this, id = t.id; 12813 12814 t.parent(); 12815 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12816 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12817 }, 12818 12819 destroy : function() { 12820 var self = this; 12821 12822 self.parent(); 12823 12824 Event.clear(self.id + '_menu'); 12825 Event.clear(self.id + '_more'); 12826 DOM.remove(self.id + '_menu'); 12827 12828 if (self.keyboardNav) { 12829 self.keyboardNav.destroy(); 12830 } 12831 } 12832 }); 12833 })(tinymce); 12834 (function(tinymce) { 12835 // Shorten class names 12836 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12837 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12838 renderHTML : function() { 12839 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12840 12841 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12842 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12843 h.push("<span role='application'>"); 12844 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12845 each(controls, function(toolbar) { 12846 h.push(toolbar.renderHTML()); 12847 }); 12848 h.push("</span>"); 12849 h.push('</div>'); 12850 12851 return h.join(''); 12852 }, 12853 12854 focus : function() { 12855 var t = this; 12856 dom.get(t.id).focus(); 12857 }, 12858 12859 postRender : function() { 12860 var t = this, items = []; 12861 12862 each(t.controls, function(toolbar) { 12863 each (toolbar.controls, function(control) { 12864 if (control.id) { 12865 items.push(control); 12866 } 12867 }); 12868 }); 12869 12870 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12871 root: t.id, 12872 items: items, 12873 onCancel: function() { 12874 //Move focus if webkit so that navigation back will read the item. 12875 if (tinymce.isWebKit) { 12876 dom.get(t.editor.id+"_ifr").focus(); 12877 } 12878 t.editor.focus(); 12879 }, 12880 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12881 }); 12882 }, 12883 12884 destroy : function() { 12885 var self = this; 12886 12887 self.parent(); 12888 self.keyNav.destroy(); 12889 Event.clear(self.id); 12890 } 12891 }); 12892 })(tinymce); 12893 (function(tinymce) { 12894 // Shorten class names 12895 var dom = tinymce.DOM, each = tinymce.each; 12896 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12897 renderHTML : function() { 12898 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12899 12900 cl = t.controls; 12901 for (i=0; i<cl.length; i++) { 12902 // Get current control, prev control, next control and if the control is a list box or not 12903 co = cl[i]; 12904 pr = cl[i - 1]; 12905 nx = cl[i + 1]; 12906 12907 // Add toolbar start 12908 if (i === 0) { 12909 c = 'mceToolbarStart'; 12910 12911 if (co.Button) 12912 c += ' mceToolbarStartButton'; 12913 else if (co.SplitButton) 12914 c += ' mceToolbarStartSplitButton'; 12915 else if (co.ListBox) 12916 c += ' mceToolbarStartListBox'; 12917 12918 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12919 } 12920 12921 // Add toolbar end before list box and after the previous button 12922 // This is to fix the o2k7 editor skins 12923 if (pr && co.ListBox) { 12924 if (pr.Button || pr.SplitButton) 12925 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12926 } 12927 12928 // Render control HTML 12929 12930 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12931 if (dom.stdMode) 12932 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12933 else 12934 h += '<td>' + co.renderHTML() + '</td>'; 12935 12936 // Add toolbar start after list box and before the next button 12937 // This is to fix the o2k7 editor skins 12938 if (nx && co.ListBox) { 12939 if (nx.Button || nx.SplitButton) 12940 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12941 } 12942 } 12943 12944 c = 'mceToolbarEnd'; 12945 12946 if (co.Button) 12947 c += ' mceToolbarEndButton'; 12948 else if (co.SplitButton) 12949 c += ' mceToolbarEndSplitButton'; 12950 else if (co.ListBox) 12951 c += ' mceToolbarEndListBox'; 12952 12953 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12954 12955 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>'); 12956 } 12957 }); 12958 })(tinymce); 12959 (function(tinymce) { 12960 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12961 12962 tinymce.create('tinymce.AddOnManager', { 12963 AddOnManager : function() { 12964 var self = this; 12965 12966 self.items = []; 12967 self.urls = {}; 12968 self.lookup = {}; 12969 self.onAdd = new Dispatcher(self); 12970 }, 12971 12972 get : function(n) { 12973 if (this.lookup[n]) { 12974 return this.lookup[n].instance; 12975 } else { 12976 return undefined; 12977 } 12978 }, 12979 12980 dependencies : function(n) { 12981 var result; 12982 if (this.lookup[n]) { 12983 result = this.lookup[n].dependencies; 12984 } 12985 return result || []; 12986 }, 12987 12988 requireLangPack : function(n) { 12989 var s = tinymce.settings; 12990 12991 if (s && s.language && s.language_load !== false) 12992 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12993 }, 12994 12995 add : function(id, o, dependencies) { 12996 this.items.push(o); 12997 this.lookup[id] = {instance:o, dependencies:dependencies}; 12998 this.onAdd.dispatch(this, id, o); 12999 13000 return o; 13001 }, 13002 createUrl: function(baseUrl, dep) { 13003 if (typeof dep === "object") { 13004 return dep 13005 } else { 13006 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 13007 } 13008 }, 13009 13010 addComponents: function(pluginName, scripts) { 13011 var pluginUrl = this.urls[pluginName]; 13012 tinymce.each(scripts, function(script){ 13013 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 13014 }); 13015 }, 13016 13017 load : function(n, u, cb, s) { 13018 var t = this, url = u; 13019 13020 function loadDependencies() { 13021 var dependencies = t.dependencies(n); 13022 tinymce.each(dependencies, function(dep) { 13023 var newUrl = t.createUrl(u, dep); 13024 t.load(newUrl.resource, newUrl, undefined, undefined); 13025 }); 13026 if (cb) { 13027 if (s) { 13028 cb.call(s); 13029 } else { 13030 cb.call(tinymce.ScriptLoader); 13031 } 13032 } 13033 } 13034 13035 if (t.urls[n]) 13036 return; 13037 if (typeof u === "object") 13038 url = u.prefix + u.resource + u.suffix; 13039 13040 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 13041 url = tinymce.baseURL + '/' + url; 13042 13043 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 13044 13045 if (t.lookup[n]) { 13046 loadDependencies(); 13047 } else { 13048 tinymce.ScriptLoader.add(url, loadDependencies, s); 13049 } 13050 } 13051 }); 13052 13053 // Create plugin and theme managers 13054 tinymce.PluginManager = new tinymce.AddOnManager(); 13055 tinymce.ThemeManager = new tinymce.AddOnManager(); 13056 }(tinymce)); 13057 13058 (function(tinymce) { 13059 // Shorten names 13060 var each = tinymce.each, extend = tinymce.extend, 13061 DOM = tinymce.DOM, Event = tinymce.dom.Event, 13062 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13063 explode = tinymce.explode, 13064 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 13065 13066 // Setup some URLs where the editor API is located and where the document is 13067 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 13068 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 13069 tinymce.documentBaseURL += '/'; 13070 13071 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 13072 13073 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 13074 13075 // Add before unload listener 13076 // This was required since IE was leaking memory if you added and removed beforeunload listeners 13077 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 13078 tinymce.onBeforeUnload = new Dispatcher(tinymce); 13079 13080 // Must be on window or IE will leak if the editor is placed in frame or iframe 13081 Event.add(window, 'beforeunload', function(e) { 13082 tinymce.onBeforeUnload.dispatch(tinymce, e); 13083 }); 13084 13085 tinymce.onAddEditor = new Dispatcher(tinymce); 13086 13087 tinymce.onRemoveEditor = new Dispatcher(tinymce); 13088 13089 tinymce.EditorManager = extend(tinymce, { 13090 editors : [], 13091 13092 i18n : {}, 13093 13094 activeEditor : null, 13095 13096 init : function(s) { 13097 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 13098 13099 function createId(elm) { 13100 var id = elm.id; 13101 13102 // Use element id, or unique name or generate a unique id 13103 if (!id) { 13104 id = elm.name; 13105 13106 if (id && !DOM.get(id)) { 13107 id = elm.name; 13108 } else { 13109 // Generate unique name 13110 id = DOM.uniqueId(); 13111 } 13112 13113 elm.setAttribute('id', id); 13114 } 13115 13116 return id; 13117 }; 13118 13119 function execCallback(se, n, s) { 13120 var f = se[n]; 13121 13122 if (!f) 13123 return; 13124 13125 if (tinymce.is(f, 'string')) { 13126 s = f.replace(/\.\w+$/, ''); 13127 s = s ? tinymce.resolve(s) : 0; 13128 f = tinymce.resolve(f); 13129 } 13130 13131 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 13132 }; 13133 13134 function hasClass(n, c) { 13135 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 13136 }; 13137 13138 t.settings = s; 13139 13140 // Legacy call 13141 Event.bind(window, 'ready', function() { 13142 var l, co; 13143 13144 execCallback(s, 'onpageload'); 13145 13146 switch (s.mode) { 13147 case "exact": 13148 l = s.elements || ''; 13149 13150 if(l.length > 0) { 13151 each(explode(l), function(v) { 13152 if (DOM.get(v)) { 13153 ed = new tinymce.Editor(v, s); 13154 el.push(ed); 13155 ed.render(1); 13156 } else { 13157 each(document.forms, function(f) { 13158 each(f.elements, function(e) { 13159 if (e.name === v) { 13160 v = 'mce_editor_' + instanceCounter++; 13161 DOM.setAttrib(e, 'id', v); 13162 13163 ed = new tinymce.Editor(v, s); 13164 el.push(ed); 13165 ed.render(1); 13166 } 13167 }); 13168 }); 13169 } 13170 }); 13171 } 13172 break; 13173 13174 case "textareas": 13175 case "specific_textareas": 13176 each(DOM.select('textarea'), function(elm) { 13177 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 13178 return; 13179 13180 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 13181 ed = new tinymce.Editor(createId(elm), s); 13182 el.push(ed); 13183 ed.render(1); 13184 } 13185 }); 13186 break; 13187 13188 default: 13189 if (s.types) { 13190 // Process type specific selector 13191 each(s.types, function(type) { 13192 each(DOM.select(type.selector), function(elm) { 13193 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 13194 el.push(editor); 13195 editor.render(1); 13196 }); 13197 }); 13198 } else if (s.selector) { 13199 // Process global selector 13200 each(DOM.select(s.selector), function(elm) { 13201 var editor = new tinymce.Editor(createId(elm), s); 13202 el.push(editor); 13203 editor.render(1); 13204 }); 13205 } 13206 } 13207 13208 // Call onInit when all editors are initialized 13209 if (s.oninit) { 13210 l = co = 0; 13211 13212 each(el, function(ed) { 13213 co++; 13214 13215 if (!ed.initialized) { 13216 // Wait for it 13217 ed.onInit.add(function() { 13218 l++; 13219 13220 // All done 13221 if (l == co) 13222 execCallback(s, 'oninit'); 13223 }); 13224 } else 13225 l++; 13226 13227 // All done 13228 if (l == co) 13229 execCallback(s, 'oninit'); 13230 }); 13231 } 13232 }); 13233 }, 13234 13235 get : function(id) { 13236 if (id === undef) 13237 return this.editors; 13238 13239 if (!this.editors.hasOwnProperty(id)) 13240 return undef; 13241 13242 return this.editors[id]; 13243 }, 13244 13245 getInstanceById : function(id) { 13246 return this.get(id); 13247 }, 13248 13249 add : function(editor) { 13250 var self = this, editors = self.editors; 13251 13252 // Add named and index editor instance 13253 editors[editor.id] = editor; 13254 editors.push(editor); 13255 13256 self._setActive(editor); 13257 self.onAddEditor.dispatch(self, editor); 13258 13259 13260 return editor; 13261 }, 13262 13263 remove : function(editor) { 13264 var t = this, i, editors = t.editors; 13265 13266 // Not in the collection 13267 if (!editors[editor.id]) 13268 return null; 13269 13270 delete editors[editor.id]; 13271 13272 for (i = 0; i < editors.length; i++) { 13273 if (editors[i] == editor) { 13274 editors.splice(i, 1); 13275 break; 13276 } 13277 } 13278 13279 // Select another editor since the active one was removed 13280 if (t.activeEditor == editor) 13281 t._setActive(editors[0]); 13282 13283 editor.destroy(); 13284 t.onRemoveEditor.dispatch(t, editor); 13285 13286 return editor; 13287 }, 13288 13289 execCommand : function(c, u, v) { 13290 var t = this, ed = t.get(v), w; 13291 13292 function clr() { 13293 ed.destroy(); 13294 w.detachEvent('onunload', clr); 13295 w = w.tinyMCE = w.tinymce = null; // IE leak 13296 }; 13297 13298 // Manager commands 13299 switch (c) { 13300 case "mceFocus": 13301 ed.focus(); 13302 return true; 13303 13304 case "mceAddEditor": 13305 case "mceAddControl": 13306 if (!t.get(v)) 13307 new tinymce.Editor(v, t.settings).render(); 13308 13309 return true; 13310 13311 case "mceAddFrameControl": 13312 w = v.window; 13313 13314 // Add tinyMCE global instance and tinymce namespace to specified window 13315 w.tinyMCE = tinyMCE; 13316 w.tinymce = tinymce; 13317 13318 tinymce.DOM.doc = w.document; 13319 tinymce.DOM.win = w; 13320 13321 ed = new tinymce.Editor(v.element_id, v); 13322 ed.render(); 13323 13324 // Fix IE memory leaks 13325 if (tinymce.isIE && ! tinymce.isIE11) { 13326 w.attachEvent('onunload', clr); 13327 } 13328 13329 v.page_window = null; 13330 13331 return true; 13332 13333 case "mceRemoveEditor": 13334 case "mceRemoveControl": 13335 if (ed) 13336 ed.remove(); 13337 13338 return true; 13339 13340 case 'mceToggleEditor': 13341 if (!ed) { 13342 t.execCommand('mceAddControl', 0, v); 13343 return true; 13344 } 13345 13346 if (ed.isHidden()) 13347 ed.show(); 13348 else 13349 ed.hide(); 13350 13351 return true; 13352 } 13353 13354 // Run command on active editor 13355 if (t.activeEditor) 13356 return t.activeEditor.execCommand(c, u, v); 13357 13358 return false; 13359 }, 13360 13361 execInstanceCommand : function(id, c, u, v) { 13362 var ed = this.get(id); 13363 13364 if (ed) 13365 return ed.execCommand(c, u, v); 13366 13367 return false; 13368 }, 13369 13370 triggerSave : function() { 13371 each(this.editors, function(e) { 13372 e.save(); 13373 }); 13374 }, 13375 13376 addI18n : function(p, o) { 13377 var lo, i18n = this.i18n; 13378 13379 if (!tinymce.is(p, 'string')) { 13380 each(p, function(o, lc) { 13381 each(o, function(o, g) { 13382 each(o, function(o, k) { 13383 if (g === 'common') 13384 i18n[lc + '.' + k] = o; 13385 else 13386 i18n[lc + '.' + g + '.' + k] = o; 13387 }); 13388 }); 13389 }); 13390 } else { 13391 each(o, function(o, k) { 13392 i18n[p + '.' + k] = o; 13393 }); 13394 } 13395 }, 13396 13397 // Private methods 13398 13399 _setActive : function(editor) { 13400 this.selectedInstance = this.activeEditor = editor; 13401 } 13402 }); 13403 })(tinymce); 13404 13405 (function(tinymce) { 13406 // Shorten these names 13407 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13408 each = tinymce.each, isGecko = tinymce.isGecko, 13409 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13410 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13411 explode = tinymce.explode; 13412 13413 tinymce.create('tinymce.Editor', { 13414 Editor : function(id, settings) { 13415 var self = this, TRUE = true; 13416 13417 self.settings = settings = extend({ 13418 id : id, 13419 language : 'en', 13420 theme : 'advanced', 13421 skin : 'default', 13422 delta_width : 0, 13423 delta_height : 0, 13424 popup_css : '', 13425 plugins : '', 13426 document_base_url : tinymce.documentBaseURL, 13427 add_form_submit_trigger : TRUE, 13428 submit_patch : TRUE, 13429 add_unload_trigger : TRUE, 13430 convert_urls : TRUE, 13431 relative_urls : TRUE, 13432 remove_script_host : TRUE, 13433 table_inline_editing : false, 13434 object_resizing : TRUE, 13435 accessibility_focus : TRUE, 13436 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13437 visual : TRUE, 13438 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13439 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13440 apply_source_formatting : TRUE, 13441 directionality : 'ltr', 13442 forced_root_block : 'p', 13443 hidden_input : TRUE, 13444 padd_empty_editor : TRUE, 13445 render_ui : TRUE, 13446 indentation : '30px', 13447 fix_table_elements : TRUE, 13448 inline_styles : TRUE, 13449 convert_fonts_to_spans : TRUE, 13450 indent : 'simple', 13451 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13452 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13453 validate : TRUE, 13454 entity_encoding : 'named', 13455 url_converter : self.convertURL, 13456 url_converter_scope : self, 13457 ie7_compat : TRUE 13458 }, settings); 13459 13460 self.id = self.editorId = id; 13461 13462 self.isNotDirty = false; 13463 13464 self.plugins = {}; 13465 13466 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13467 base_uri : tinyMCE.baseURI 13468 }); 13469 13470 self.baseURI = tinymce.baseURI; 13471 13472 self.contentCSS = []; 13473 13474 self.contentStyles = []; 13475 13476 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13477 self.setupEvents(); 13478 13479 // Internal command handler objects 13480 self.execCommands = {}; 13481 self.queryStateCommands = {}; 13482 self.queryValueCommands = {}; 13483 13484 // Call setup 13485 self.execCallback('setup', self); 13486 }, 13487 13488 render : function(nst) { 13489 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13490 13491 // Page is not loaded yet, wait for it 13492 if (!Event.domLoaded) { 13493 Event.add(window, 'ready', function() { 13494 t.render(); 13495 }); 13496 return; 13497 } 13498 13499 tinyMCE.settings = s; 13500 13501 // Element not found, then skip initialization 13502 if (!t.getElement()) 13503 return; 13504 13505 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13506 // here since the browser says it has contentEditable support but there is no visible caret. 13507 if (tinymce.isIDevice && !tinymce.isIOS5) 13508 return; 13509 13510 // Add hidden input for non input elements inside form elements 13511 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13512 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13513 13514 // Hide target element early to prevent content flashing 13515 if (!s.content_editable) { 13516 t.orgVisibility = t.getElement().style.visibility; 13517 t.getElement().style.visibility = 'hidden'; 13518 } 13519 13520 if (tinymce.WindowManager) 13521 t.windowManager = new tinymce.WindowManager(t); 13522 13523 if (s.encoding == 'xml') { 13524 t.onGetContent.add(function(ed, o) { 13525 if (o.save) 13526 o.content = DOM.encode(o.content); 13527 }); 13528 } 13529 13530 if (s.add_form_submit_trigger) { 13531 t.onSubmit.addToTop(function() { 13532 if (t.initialized) { 13533 t.save(); 13534 t.isNotDirty = 1; 13535 } 13536 }); 13537 } 13538 13539 if (s.add_unload_trigger) { 13540 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13541 if (t.initialized && !t.destroyed && !t.isHidden()) 13542 t.save({format : 'raw', no_events : true}); 13543 }); 13544 } 13545 13546 tinymce.addUnload(t.destroy, t); 13547 13548 if (s.submit_patch) { 13549 t.onBeforeRenderUI.add(function() { 13550 var n = t.getElement().form; 13551 13552 if (!n) 13553 return; 13554 13555 // Already patched 13556 if (n._mceOldSubmit) 13557 return; 13558 13559 // Check page uses id="submit" or name="submit" for it's submit button 13560 if (!n.submit.nodeType && !n.submit.length) { 13561 t.formElement = n; 13562 n._mceOldSubmit = n.submit; 13563 n.submit = function() { 13564 // Save all instances 13565 tinymce.triggerSave(); 13566 t.isNotDirty = 1; 13567 13568 return t.formElement._mceOldSubmit(t.formElement); 13569 }; 13570 } 13571 13572 n = null; 13573 }); 13574 } 13575 13576 // Load scripts 13577 function loadScripts() { 13578 if (s.language && s.language_load !== false) 13579 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13580 13581 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13582 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13583 13584 each(explode(s.plugins), function(p) { 13585 if (p &&!PluginManager.urls[p]) { 13586 if (p.charAt(0) == '-') { 13587 p = p.substr(1, p.length); 13588 var dependencies = PluginManager.dependencies(p); 13589 each(dependencies, function(dep) { 13590 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13591 dep = PluginManager.createUrl(defaultSettings, dep); 13592 PluginManager.load(dep.resource, dep); 13593 }); 13594 } else { 13595 // Skip safari plugin, since it is removed as of 3.3b1 13596 if (p == 'safari') { 13597 return; 13598 } 13599 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13600 } 13601 } 13602 }); 13603 13604 // Init when que is loaded 13605 sl.loadQueue(function() { 13606 if (!t.removed) 13607 t.init(); 13608 }); 13609 }; 13610 13611 loadScripts(); 13612 }, 13613 13614 init : function() { 13615 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13616 13617 tinymce.add(t); 13618 13619 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13620 13621 if (s.theme) { 13622 if (typeof s.theme != "function") { 13623 s.theme = s.theme.replace(/-/, ''); 13624 o = ThemeManager.get(s.theme); 13625 t.theme = new o(); 13626 13627 if (t.theme.init) 13628 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13629 } else { 13630 t.theme = s.theme; 13631 } 13632 } 13633 13634 function initPlugin(p) { 13635 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13636 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13637 each(PluginManager.dependencies(p), function(dep){ 13638 initPlugin(dep); 13639 }); 13640 po = new c(t, u); 13641 13642 t.plugins[p] = po; 13643 13644 if (po.init) { 13645 po.init(t, u); 13646 initializedPlugins.push(p); 13647 } 13648 } 13649 } 13650 13651 // Create all plugins 13652 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13653 13654 // Setup popup CSS path(s) 13655 if (s.popup_css !== false) { 13656 if (s.popup_css) 13657 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13658 else 13659 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13660 } 13661 13662 if (s.popup_css_add) 13663 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13664 13665 t.controlManager = new tinymce.ControlManager(t); 13666 13667 // Enables users to override the control factory 13668 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13669 13670 // Measure box 13671 if (s.render_ui && t.theme) { 13672 t.orgDisplay = e.style.display; 13673 13674 if (typeof s.theme != "function") { 13675 w = s.width || e.style.width || e.offsetWidth; 13676 h = s.height || e.style.height || e.offsetHeight; 13677 mh = s.min_height || 100; 13678 re = /^[0-9\.]+(|px)$/i; 13679 13680 if (re.test('' + w)) 13681 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13682 13683 if (re.test('' + h)) 13684 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13685 13686 // Render UI 13687 o = t.theme.renderUI({ 13688 targetNode : e, 13689 width : w, 13690 height : h, 13691 deltaWidth : s.delta_width, 13692 deltaHeight : s.delta_height 13693 }); 13694 13695 // Resize editor 13696 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13697 width : w, 13698 height : h 13699 }); 13700 13701 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13702 if (h < mh) 13703 h = mh; 13704 } else { 13705 o = s.theme(t, e); 13706 13707 // Convert element type to id:s 13708 if (o.editorContainer.nodeType) { 13709 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13710 } 13711 13712 // Convert element type to id:s 13713 if (o.iframeContainer.nodeType) { 13714 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13715 } 13716 13717 // Use specified iframe height or the targets offsetHeight 13718 h = o.iframeHeight || e.offsetHeight; 13719 13720 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13721 if (isIE) { 13722 t.onInit.add(function(ed) { 13723 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() { 13724 ed.bookmark = ed.selection.getBookmark(1); 13725 }); 13726 }); 13727 13728 t.onNodeChange.add(function(ed) { 13729 if (document.activeElement.id == ed.id + "_ifr") { 13730 ed.bookmark = ed.selection.getBookmark(1); 13731 } 13732 }); 13733 } 13734 } 13735 13736 t.editorContainer = o.editorContainer; 13737 } 13738 13739 // Load specified content CSS last 13740 if (s.content_css) { 13741 each(explode(s.content_css), function(u) { 13742 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13743 }); 13744 } 13745 13746 // Load specified content CSS last 13747 if (s.content_style) { 13748 t.contentStyles.push(s.content_style); 13749 } 13750 13751 // Content editable mode ends here 13752 if (s.content_editable) { 13753 e = n = o = null; // Fix IE leak 13754 return t.initContentBody(); 13755 } 13756 13757 // User specified a document.domain value 13758 if (document.domain && location.hostname != document.domain) 13759 tinymce.relaxedDomain = document.domain; 13760 13761 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13762 13763 // We only need to override paths if we have to 13764 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13765 if (s.document_base_url != tinymce.documentBaseURL) 13766 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13767 13768 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13769 if (tinymce.isIE8) { 13770 if (s.ie7_compat) 13771 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13772 else 13773 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13774 } 13775 13776 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13777 13778 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13779 for (i = 0; i < t.contentCSS.length; i++) { 13780 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13781 } 13782 13783 t.contentCSS = []; 13784 13785 bi = s.body_id || 'tinymce'; 13786 if (bi.indexOf('=') != -1) { 13787 bi = t.getParam('body_id', '', 'hash'); 13788 bi = bi[t.id] || bi; 13789 } 13790 13791 bc = s.body_class || ''; 13792 if (bc.indexOf('=') != -1) { 13793 bc = t.getParam('body_class', '', 'hash'); 13794 bc = bc[t.id] || ''; 13795 } 13796 13797 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13798 13799 // Domain relaxing enabled, then set document domain 13800 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13801 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13802 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13803 } 13804 13805 // Create iframe 13806 // TODO: ACC add the appropriate description on this. 13807 n = DOM.add(o.iframeContainer, 'iframe', { 13808 id : t.id + "_ifr", 13809 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13810 frameBorder : '0', 13811 allowTransparency : "true", 13812 title : s.aria_label, 13813 style : { 13814 width : '100%', 13815 height : h, 13816 display : 'block' // Important for Gecko to render the iframe correctly 13817 } 13818 }); 13819 13820 t.contentAreaContainer = o.iframeContainer; 13821 13822 if (o.editorContainer) { 13823 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13824 } 13825 13826 // Restore visibility on target element 13827 e.style.visibility = t.orgVisibility; 13828 13829 DOM.get(t.id).style.display = 'none'; 13830 DOM.setAttrib(t.id, 'aria-hidden', true); 13831 13832 if (!tinymce.relaxedDomain || !u) 13833 t.initContentBody(); 13834 13835 e = n = o = null; // Cleanup 13836 }, 13837 13838 initContentBody : function() { 13839 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13840 13841 // Setup iframe body 13842 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13843 doc.open(); 13844 doc.write(self.iframeHTML); 13845 doc.close(); 13846 13847 if (tinymce.relaxedDomain) 13848 doc.domain = tinymce.relaxedDomain; 13849 } 13850 13851 if (settings.content_editable) { 13852 DOM.addClass(targetElm, 'mceContentBody'); 13853 self.contentDocument = doc = settings.content_document || document; 13854 self.contentWindow = settings.content_window || window; 13855 self.bodyElement = targetElm; 13856 13857 // Prevent leak in IE 13858 settings.content_document = settings.content_window = null; 13859 } 13860 13861 // It will not steal focus while setting contentEditable 13862 body = self.getBody(); 13863 body.disabled = true; 13864 13865 if (!settings.readonly) 13866 body.contentEditable = self.getParam('content_editable_state', true); 13867 13868 body.disabled = false; 13869 13870 self.schema = new tinymce.html.Schema(settings); 13871 13872 self.dom = new tinymce.dom.DOMUtils(doc, { 13873 keep_values : true, 13874 url_converter : self.convertURL, 13875 url_converter_scope : self, 13876 hex_colors : settings.force_hex_style_colors, 13877 class_filter : settings.class_filter, 13878 update_styles : true, 13879 root_element : settings.content_editable ? self.id : null, 13880 schema : self.schema 13881 }); 13882 13883 self.parser = new tinymce.html.DomParser(settings, self.schema); 13884 13885 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13886 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13887 var i = nodes.length, node, dom = self.dom, value, internalName; 13888 13889 while (i--) { 13890 node = nodes[i]; 13891 value = node.attr(name); 13892 internalName = 'data-mce-' + name; 13893 13894 // Add internal attribute if we need to we don't on a refresh of the document 13895 if (!node.attributes.map[internalName]) { 13896 if (name === "style") 13897 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13898 else 13899 node.attr(internalName, self.convertURL(value, name, node.name)); 13900 } 13901 } 13902 }); 13903 13904 // Keep scripts from executing 13905 self.parser.addNodeFilter('script', function(nodes, name) { 13906 var i = nodes.length, node; 13907 13908 while (i--) { 13909 node = nodes[i]; 13910 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13911 } 13912 }); 13913 13914 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13915 var i = nodes.length, node; 13916 13917 while (i--) { 13918 node = nodes[i]; 13919 node.type = 8; 13920 node.name = '#comment'; 13921 node.value = '[CDATA[' + node.value + ']]'; 13922 } 13923 }); 13924 13925 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13926 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13927 13928 while (i--) { 13929 node = nodes[i]; 13930 13931 if (node.isEmpty(nonEmptyElements)) 13932 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13933 } 13934 }); 13935 13936 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13937 13938 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13939 13940 self.formatter = new tinymce.Formatter(self); 13941 13942 self.undoManager = new tinymce.UndoManager(self); 13943 13944 self.forceBlocks = new tinymce.ForceBlocks(self); 13945 self.enterKey = new tinymce.EnterKey(self); 13946 self.editorCommands = new tinymce.EditorCommands(self); 13947 13948 self.onExecCommand.add(function(editor, command) { 13949 // Don't refresh the select lists until caret move 13950 if (!/^(FontName|FontSize)$/.test(command)) 13951 self.nodeChanged(); 13952 }); 13953 13954 // Pass through 13955 self.serializer.onPreProcess.add(function(se, o) { 13956 return self.onPreProcess.dispatch(self, o, se); 13957 }); 13958 13959 self.serializer.onPostProcess.add(function(se, o) { 13960 return self.onPostProcess.dispatch(self, o, se); 13961 }); 13962 13963 self.onPreInit.dispatch(self); 13964 13965 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13966 doc.body.spellcheck = false; 13967 13968 if (!settings.readonly) { 13969 self.bindNativeEvents(); 13970 } 13971 13972 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13973 self.onPostRender.dispatch(self); 13974 13975 self.quirks = tinymce.util.Quirks(self); 13976 13977 if (settings.directionality) 13978 body.dir = settings.directionality; 13979 13980 if (settings.nowrap) 13981 body.style.whiteSpace = "nowrap"; 13982 13983 if (settings.protect) { 13984 self.onBeforeSetContent.add(function(ed, o) { 13985 each(settings.protect, function(pattern) { 13986 o.content = o.content.replace(pattern, function(str) { 13987 return '<!--mce:protected ' + escape(str) + '-->'; 13988 }); 13989 }); 13990 }); 13991 } 13992 13993 // Add visual aids when new contents is added 13994 self.onSetContent.add(function() { 13995 self.addVisual(self.getBody()); 13996 }); 13997 13998 // Remove empty contents 13999 if (settings.padd_empty_editor) { 14000 self.onPostProcess.add(function(ed, o) { 14001 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 14002 }); 14003 } 14004 14005 self.load({initial : true, format : 'html'}); 14006 self.startContent = self.getContent({format : 'raw'}); 14007 14008 self.initialized = true; 14009 14010 self.onInit.dispatch(self); 14011 self.execCallback('setupcontent_callback', self.id, body, doc); 14012 self.execCallback('init_instance_callback', self); 14013 self.focus(true); 14014 self.nodeChanged({initial : true}); 14015 14016 // Add editor specific CSS styles 14017 if (self.contentStyles.length > 0) { 14018 contentCssText = ''; 14019 14020 each(self.contentStyles, function(style) { 14021 contentCssText += style + "\r\n"; 14022 }); 14023 14024 self.dom.addStyle(contentCssText); 14025 } 14026 14027 // Load specified content CSS last 14028 each(self.contentCSS, function(url) { 14029 self.dom.loadCSS(url); 14030 }); 14031 14032 // Handle auto focus 14033 if (settings.auto_focus) { 14034 setTimeout(function () { 14035 var ed = tinymce.get(settings.auto_focus); 14036 14037 ed.selection.select(ed.getBody(), 1); 14038 ed.selection.collapse(1); 14039 ed.getBody().focus(); 14040 ed.getWin().focus(); 14041 }, 100); 14042 } 14043 14044 // Clean up references for IE 14045 targetElm = doc = body = null; 14046 }, 14047 14048 focus : function(skip_focus) { 14049 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 14050 14051 if (!skip_focus) { 14052 if (self.bookmark) { 14053 selection.moveToBookmark(self.bookmark); 14054 self.bookmark = null; 14055 } 14056 14057 // Get selected control element 14058 ieRng = selection.getRng(); 14059 if (ieRng.item) { 14060 controlElm = ieRng.item(0); 14061 } 14062 14063 self._refreshContentEditable(); 14064 14065 // Focus the window iframe 14066 if (!contentEditable) { 14067 self.getWin().focus(); 14068 } 14069 14070 // Focus the body as well since it's contentEditable 14071 if (tinymce.isGecko || contentEditable) { 14072 body = self.getBody(); 14073 14074 // Check for setActive since it doesn't scroll to the element 14075 if (body.setActive && ! tinymce.isIE11) { 14076 body.setActive(); 14077 } else { 14078 body.focus(); 14079 } 14080 14081 if (contentEditable) { 14082 selection.normalize(); 14083 } 14084 } 14085 14086 // Restore selected control element 14087 // This is needed when for example an image is selected within a 14088 // layer a call to focus will then remove the control selection 14089 if (controlElm && controlElm.ownerDocument == doc) { 14090 ieRng = doc.body.createControlRange(); 14091 ieRng.addElement(controlElm); 14092 ieRng.select(); 14093 } 14094 } 14095 14096 if (tinymce.activeEditor != self) { 14097 if ((oed = tinymce.activeEditor) != null) 14098 oed.onDeactivate.dispatch(oed, self); 14099 14100 self.onActivate.dispatch(self, oed); 14101 } 14102 14103 tinymce._setActive(self); 14104 }, 14105 14106 execCallback : function(n) { 14107 var t = this, f = t.settings[n], s; 14108 14109 if (!f) 14110 return; 14111 14112 // Look through lookup 14113 if (t.callbackLookup && (s = t.callbackLookup[n])) { 14114 f = s.func; 14115 s = s.scope; 14116 } 14117 14118 if (is(f, 'string')) { 14119 s = f.replace(/\.\w+$/, ''); 14120 s = s ? tinymce.resolve(s) : 0; 14121 f = tinymce.resolve(f); 14122 t.callbackLookup = t.callbackLookup || {}; 14123 t.callbackLookup[n] = {func : f, scope : s}; 14124 } 14125 14126 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 14127 }, 14128 14129 translate : function(s) { 14130 var c = this.settings.language || 'en', i18n = tinymce.i18n; 14131 14132 if (!s) 14133 return ''; 14134 14135 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 14136 return i18n[c + '.' + b] || '{#' + b + '}'; 14137 }); 14138 }, 14139 14140 getLang : function(n, dv) { 14141 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 14142 }, 14143 14144 getParam : function(n, dv, ty) { 14145 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 14146 14147 if (ty === 'hash') { 14148 o = {}; 14149 14150 if (is(v, 'string')) { 14151 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 14152 v = v.split('='); 14153 14154 if (v.length > 1) 14155 o[tr(v[0])] = tr(v[1]); 14156 else 14157 o[tr(v[0])] = tr(v); 14158 }); 14159 } else 14160 o = v; 14161 14162 return o; 14163 } 14164 14165 return v; 14166 }, 14167 14168 nodeChanged : function(o) { 14169 var self = this, selection = self.selection, node; 14170 14171 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 14172 if (self.initialized) { 14173 o = o || {}; 14174 14175 // Get start node 14176 node = selection.getStart() || self.getBody(); 14177 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 14178 14179 // Get parents and add them to object 14180 o.parents = []; 14181 self.dom.getParent(node, function(node) { 14182 if (node.nodeName == 'BODY') 14183 return true; 14184 14185 o.parents.push(node); 14186 }); 14187 14188 self.onNodeChange.dispatch( 14189 self, 14190 o ? o.controlManager || self.controlManager : self.controlManager, 14191 node, 14192 selection.isCollapsed(), 14193 o 14194 ); 14195 } 14196 }, 14197 14198 addButton : function(name, settings) { 14199 var self = this; 14200 14201 self.buttons = self.buttons || {}; 14202 self.buttons[name] = settings; 14203 }, 14204 14205 addCommand : function(name, callback, scope) { 14206 this.execCommands[name] = {func : callback, scope : scope || this}; 14207 }, 14208 14209 addQueryStateHandler : function(name, callback, scope) { 14210 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 14211 }, 14212 14213 addQueryValueHandler : function(name, callback, scope) { 14214 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 14215 }, 14216 14217 addShortcut : function(pa, desc, cmd_func, sc) { 14218 var t = this, c; 14219 14220 if (t.settings.custom_shortcuts === false) 14221 return false; 14222 14223 t.shortcuts = t.shortcuts || {}; 14224 14225 if (is(cmd_func, 'string')) { 14226 c = cmd_func; 14227 14228 cmd_func = function() { 14229 t.execCommand(c, false, null); 14230 }; 14231 } 14232 14233 if (is(cmd_func, 'object')) { 14234 c = cmd_func; 14235 14236 cmd_func = function() { 14237 t.execCommand(c[0], c[1], c[2]); 14238 }; 14239 } 14240 14241 each(explode(pa), function(pa) { 14242 var o = { 14243 func : cmd_func, 14244 scope : sc || this, 14245 desc : t.translate(desc), 14246 alt : false, 14247 ctrl : false, 14248 shift : false 14249 }; 14250 14251 each(explode(pa, '+'), function(v) { 14252 switch (v) { 14253 case 'alt': 14254 case 'ctrl': 14255 case 'shift': 14256 o[v] = true; 14257 break; 14258 14259 default: 14260 o.charCode = v.charCodeAt(0); 14261 o.keyCode = v.toUpperCase().charCodeAt(0); 14262 } 14263 }); 14264 14265 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 14266 }); 14267 14268 return true; 14269 }, 14270 14271 execCommand : function(cmd, ui, val, a) { 14272 var t = this, s = 0, o, st; 14273 14274 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 14275 t.focus(); 14276 14277 a = extend({}, a); 14278 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 14279 if (a.terminate) 14280 return false; 14281 14282 // Command callback 14283 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 14284 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14285 return true; 14286 } 14287 14288 // Registred commands 14289 if (o = t.execCommands[cmd]) { 14290 st = o.func.call(o.scope, ui, val); 14291 14292 // Fall through on true 14293 if (st !== true) { 14294 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14295 return st; 14296 } 14297 } 14298 14299 // Plugin commands 14300 each(t.plugins, function(p) { 14301 if (p.execCommand && p.execCommand(cmd, ui, val)) { 14302 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14303 s = 1; 14304 return false; 14305 } 14306 }); 14307 14308 if (s) 14309 return true; 14310 14311 // Theme commands 14312 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 14313 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14314 return true; 14315 } 14316 14317 // Editor commands 14318 if (t.editorCommands.execCommand(cmd, ui, val)) { 14319 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14320 return true; 14321 } 14322 14323 // Browser commands 14324 t.getDoc().execCommand(cmd, ui, val); 14325 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14326 }, 14327 14328 queryCommandState : function(cmd) { 14329 var t = this, o, s; 14330 14331 // Is hidden then return undefined 14332 if (t._isHidden()) 14333 return; 14334 14335 // Registred commands 14336 if (o = t.queryStateCommands[cmd]) { 14337 s = o.func.call(o.scope); 14338 14339 // Fall though on true 14340 if (s !== true) 14341 return s; 14342 } 14343 14344 // Registred commands 14345 o = t.editorCommands.queryCommandState(cmd); 14346 if (o !== -1) 14347 return o; 14348 14349 // Browser commands 14350 try { 14351 return this.getDoc().queryCommandState(cmd); 14352 } catch (ex) { 14353 // Fails sometimes see bug: 1896577 14354 } 14355 }, 14356 14357 queryCommandValue : function(c) { 14358 var t = this, o, s; 14359 14360 // Is hidden then return undefined 14361 if (t._isHidden()) 14362 return; 14363 14364 // Registred commands 14365 if (o = t.queryValueCommands[c]) { 14366 s = o.func.call(o.scope); 14367 14368 // Fall though on true 14369 if (s !== true) 14370 return s; 14371 } 14372 14373 // Registred commands 14374 o = t.editorCommands.queryCommandValue(c); 14375 if (is(o)) 14376 return o; 14377 14378 // Browser commands 14379 try { 14380 return this.getDoc().queryCommandValue(c); 14381 } catch (ex) { 14382 // Fails sometimes see bug: 1896577 14383 } 14384 }, 14385 14386 show : function() { 14387 var self = this; 14388 14389 DOM.show(self.getContainer()); 14390 DOM.hide(self.id); 14391 self.load(); 14392 }, 14393 14394 hide : function() { 14395 var self = this, doc = self.getDoc(); 14396 14397 // Fixed bug where IE has a blinking cursor left from the editor 14398 if (isIE && doc) 14399 doc.execCommand('SelectAll'); 14400 14401 // We must save before we hide so Safari doesn't crash 14402 self.save(); 14403 14404 // defer the call to hide to prevent an IE9 crash #4921 14405 DOM.hide(self.getContainer()); 14406 DOM.setStyle(self.id, 'display', self.orgDisplay); 14407 }, 14408 14409 isHidden : function() { 14410 return !DOM.isHidden(this.id); 14411 }, 14412 14413 setProgressState : function(b, ti, o) { 14414 this.onSetProgressState.dispatch(this, b, ti, o); 14415 14416 return b; 14417 }, 14418 14419 load : function(o) { 14420 var t = this, e = t.getElement(), h; 14421 14422 if (e) { 14423 o = o || {}; 14424 o.load = true; 14425 14426 // Double encode existing entities in the value 14427 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14428 o.element = e; 14429 14430 if (!o.no_events) 14431 t.onLoadContent.dispatch(t, o); 14432 14433 o.element = e = null; 14434 14435 return h; 14436 } 14437 }, 14438 14439 save : function(o) { 14440 var t = this, e = t.getElement(), h, f; 14441 14442 if (!e || !t.initialized) 14443 return; 14444 14445 o = o || {}; 14446 o.save = true; 14447 14448 o.element = e; 14449 h = o.content = t.getContent(o); 14450 14451 if (!o.no_events) 14452 t.onSaveContent.dispatch(t, o); 14453 14454 h = o.content; 14455 14456 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14457 e.innerHTML = h; 14458 14459 // Update hidden form element 14460 if (f = DOM.getParent(t.id, 'form')) { 14461 each(f.elements, function(e) { 14462 if (e.name == t.id) { 14463 e.value = h; 14464 return false; 14465 } 14466 }); 14467 } 14468 } else 14469 e.value = h; 14470 14471 o.element = e = null; 14472 14473 return h; 14474 }, 14475 14476 setContent : function(content, args) { 14477 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14478 14479 // Setup args object 14480 args = args || {}; 14481 args.format = args.format || 'html'; 14482 args.set = true; 14483 args.content = content; 14484 14485 // Do preprocessing 14486 if (!args.no_events) 14487 self.onBeforeSetContent.dispatch(self, args); 14488 14489 content = args.content; 14490 14491 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14492 // It will also be impossible to place the caret in the editor unless there is a BR element present 14493 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14494 forcedRootBlockName = self.settings.forced_root_block; 14495 if (forcedRootBlockName) 14496 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14497 else 14498 content = '<br data-mce-bogus="1">'; 14499 14500 body.innerHTML = content; 14501 self.selection.select(body, true); 14502 self.selection.collapse(true); 14503 return; 14504 } 14505 14506 // Parse and serialize the html 14507 if (args.format !== 'raw') { 14508 content = new tinymce.html.Serializer({}, self.schema).serialize( 14509 self.parser.parse(content) 14510 ); 14511 } 14512 14513 // Set the new cleaned contents to the editor 14514 args.content = tinymce.trim(content); 14515 self.dom.setHTML(body, args.content); 14516 14517 // Do post processing 14518 if (!args.no_events) 14519 self.onSetContent.dispatch(self, args); 14520 14521 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14522 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14523 self.selection.normalize(); 14524 } 14525 14526 return args.content; 14527 }, 14528 14529 getContent : function(args) { 14530 var self = this, content, body = self.getBody(); 14531 14532 // Setup args object 14533 args = args || {}; 14534 args.format = args.format || 'html'; 14535 args.get = true; 14536 args.getInner = true; 14537 14538 // Do preprocessing 14539 if (!args.no_events) 14540 self.onBeforeGetContent.dispatch(self, args); 14541 14542 // Get raw contents or by default the cleaned contents 14543 if (args.format == 'raw') 14544 content = body.innerHTML; 14545 else if (args.format == 'text') 14546 content = body.innerText || body.textContent; 14547 else 14548 content = self.serializer.serialize(body, args); 14549 14550 // Trim whitespace in beginning/end of HTML 14551 if (args.format != 'text') { 14552 args.content = tinymce.trim(content); 14553 } else { 14554 args.content = content; 14555 } 14556 14557 // Do post processing 14558 if (!args.no_events) 14559 self.onGetContent.dispatch(self, args); 14560 14561 return args.content; 14562 }, 14563 14564 isDirty : function() { 14565 var self = this; 14566 14567 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14568 }, 14569 14570 getContainer : function() { 14571 var self = this; 14572 14573 if (!self.container) 14574 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14575 14576 return self.container; 14577 }, 14578 14579 getContentAreaContainer : function() { 14580 return this.contentAreaContainer; 14581 }, 14582 14583 getElement : function() { 14584 return DOM.get(this.settings.content_element || this.id); 14585 }, 14586 14587 getWin : function() { 14588 var self = this, elm; 14589 14590 if (!self.contentWindow) { 14591 elm = DOM.get(self.id + "_ifr"); 14592 14593 if (elm) 14594 self.contentWindow = elm.contentWindow; 14595 } 14596 14597 return self.contentWindow; 14598 }, 14599 14600 getDoc : function() { 14601 var self = this, win; 14602 14603 if (!self.contentDocument) { 14604 win = self.getWin(); 14605 14606 if (win) 14607 self.contentDocument = win.document; 14608 } 14609 14610 return self.contentDocument; 14611 }, 14612 14613 getBody : function() { 14614 return this.bodyElement || this.getDoc().body; 14615 }, 14616 14617 convertURL : function(url, name, elm) { 14618 var self = this, settings = self.settings; 14619 14620 // Use callback instead 14621 if (settings.urlconverter_callback) 14622 return self.execCallback('urlconverter_callback', url, elm, true, name); 14623 14624 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14625 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14626 return url; 14627 14628 // Convert to relative 14629 if (settings.relative_urls) 14630 return self.documentBaseURI.toRelative(url); 14631 14632 // Convert to absolute 14633 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14634 14635 return url; 14636 }, 14637 14638 addVisual : function(elm) { 14639 var self = this, settings = self.settings, dom = self.dom, cls; 14640 14641 elm = elm || self.getBody(); 14642 14643 if (!is(self.hasVisual)) 14644 self.hasVisual = settings.visual; 14645 14646 each(dom.select('table,a', elm), function(elm) { 14647 var value; 14648 14649 switch (elm.nodeName) { 14650 case 'TABLE': 14651 cls = settings.visual_table_class || 'mceItemTable'; 14652 value = dom.getAttrib(elm, 'border'); 14653 14654 if (!value || value == '0') { 14655 if (self.hasVisual) 14656 dom.addClass(elm, cls); 14657 else 14658 dom.removeClass(elm, cls); 14659 } 14660 14661 return; 14662 14663 case 'A': 14664 if (!dom.getAttrib(elm, 'href', false)) { 14665 value = dom.getAttrib(elm, 'name') || elm.id; 14666 cls = 'mceItemAnchor'; 14667 14668 if (value) { 14669 if (self.hasVisual) 14670 dom.addClass(elm, cls); 14671 else 14672 dom.removeClass(elm, cls); 14673 } 14674 } 14675 14676 return; 14677 } 14678 }); 14679 14680 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14681 }, 14682 14683 remove : function() { 14684 var self = this, elm = self.getContainer(), doc = self.getDoc(); 14685 14686 if (!self.removed) { 14687 self.removed = 1; // Cancels post remove event execution 14688 14689 // Fixed bug where IE has a blinking cursor left from the editor 14690 if (isIE && doc) 14691 doc.execCommand('SelectAll'); 14692 14693 // We must save before we hide so Safari doesn't crash 14694 self.save(); 14695 14696 DOM.setStyle(self.id, 'display', self.orgDisplay); 14697 14698 // Don't clear the window or document if content editable 14699 // is enabled since other instances might still be present 14700 if (!self.settings.content_editable) { 14701 Event.unbind(self.getWin()); 14702 Event.unbind(self.getDoc()); 14703 } 14704 14705 Event.unbind(self.getBody()); 14706 Event.clear(elm); 14707 14708 self.execCallback('remove_instance_callback', self); 14709 self.onRemove.dispatch(self); 14710 14711 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14712 self.onExecCommand.listeners = []; 14713 14714 tinymce.remove(self); 14715 DOM.remove(elm); 14716 } 14717 }, 14718 14719 destroy : function(s) { 14720 var t = this; 14721 14722 // One time is enough 14723 if (t.destroyed) 14724 return; 14725 14726 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14727 if (isGecko) { 14728 Event.unbind(t.getDoc()); 14729 Event.unbind(t.getWin()); 14730 Event.unbind(t.getBody()); 14731 } 14732 14733 if (!s) { 14734 tinymce.removeUnload(t.destroy); 14735 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14736 14737 // Manual destroy 14738 if (t.theme && t.theme.destroy) 14739 t.theme.destroy(); 14740 14741 // Destroy controls, selection and dom 14742 t.controlManager.destroy(); 14743 t.selection.destroy(); 14744 t.dom.destroy(); 14745 } 14746 14747 if (t.formElement) { 14748 t.formElement.submit = t.formElement._mceOldSubmit; 14749 t.formElement._mceOldSubmit = null; 14750 } 14751 14752 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14753 14754 if (t.selection) 14755 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14756 14757 t.destroyed = 1; 14758 }, 14759 14760 // Internal functions 14761 14762 _refreshContentEditable : function() { 14763 var self = this, body, parent; 14764 14765 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14766 if (self._isHidden()) { 14767 body = self.getBody(); 14768 parent = body.parentNode; 14769 14770 parent.removeChild(body); 14771 parent.appendChild(body); 14772 14773 body.focus(); 14774 } 14775 }, 14776 14777 _isHidden : function() { 14778 var s; 14779 14780 if (!isGecko) 14781 return 0; 14782 14783 // Weird, wheres that cursor selection? 14784 s = this.selection.getSel(); 14785 return (!s || !s.rangeCount || s.rangeCount === 0); 14786 } 14787 }); 14788 })(tinymce); 14789 (function(tinymce) { 14790 var each = tinymce.each; 14791 14792 tinymce.Editor.prototype.setupEvents = function() { 14793 var self = this, settings = self.settings; 14794 14795 // Add events to the editor 14796 each([ 14797 'onPreInit', 14798 14799 'onBeforeRenderUI', 14800 14801 'onPostRender', 14802 14803 'onLoad', 14804 14805 'onInit', 14806 14807 'onRemove', 14808 14809 'onActivate', 14810 14811 'onDeactivate', 14812 14813 'onClick', 14814 14815 'onEvent', 14816 14817 'onMouseUp', 14818 14819 'onMouseDown', 14820 14821 'onDblClick', 14822 14823 'onKeyDown', 14824 14825 'onKeyUp', 14826 14827 'onKeyPress', 14828 14829 'onContextMenu', 14830 14831 'onSubmit', 14832 14833 'onReset', 14834 14835 'onPaste', 14836 14837 'onPreProcess', 14838 14839 'onPostProcess', 14840 14841 'onBeforeSetContent', 14842 14843 'onBeforeGetContent', 14844 14845 'onSetContent', 14846 14847 'onGetContent', 14848 14849 'onLoadContent', 14850 14851 'onSaveContent', 14852 14853 'onNodeChange', 14854 14855 'onChange', 14856 14857 'onBeforeExecCommand', 14858 14859 'onExecCommand', 14860 14861 'onUndo', 14862 14863 'onRedo', 14864 14865 'onVisualAid', 14866 14867 'onSetProgressState', 14868 14869 'onSetAttrib' 14870 ], function(name) { 14871 self[name] = new tinymce.util.Dispatcher(self); 14872 }); 14873 14874 // Handle legacy cleanup_callback option 14875 if (settings.cleanup_callback) { 14876 self.onBeforeSetContent.add(function(ed, o) { 14877 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14878 }); 14879 14880 self.onPreProcess.add(function(ed, o) { 14881 if (o.set) 14882 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14883 14884 if (o.get) 14885 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14886 }); 14887 14888 self.onPostProcess.add(function(ed, o) { 14889 if (o.set) 14890 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14891 14892 if (o.get) 14893 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14894 }); 14895 } 14896 14897 // Handle legacy save_callback option 14898 if (settings.save_callback) { 14899 self.onGetContent.add(function(ed, o) { 14900 if (o.save) 14901 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14902 }); 14903 } 14904 14905 // Handle legacy handle_event_callback option 14906 if (settings.handle_event_callback) { 14907 self.onEvent.add(function(ed, e, o) { 14908 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14909 e.preventDefault(); 14910 e.stopPropagation(); 14911 } 14912 }); 14913 } 14914 14915 // Handle legacy handle_node_change_callback option 14916 if (settings.handle_node_change_callback) { 14917 self.onNodeChange.add(function(ed, cm, n) { 14918 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14919 }); 14920 } 14921 14922 // Handle legacy save_callback option 14923 if (settings.save_callback) { 14924 self.onSaveContent.add(function(ed, o) { 14925 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14926 14927 if (h) 14928 o.content = h; 14929 }); 14930 } 14931 14932 // Handle legacy onchange_callback option 14933 if (settings.onchange_callback) { 14934 self.onChange.add(function(ed, l) { 14935 ed.execCallback('onchange_callback', ed, l); 14936 }); 14937 } 14938 }; 14939 14940 tinymce.Editor.prototype.bindNativeEvents = function() { 14941 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14942 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14943 14944 nativeToDispatcherMap = { 14945 mouseup : 'onMouseUp', 14946 mousedown : 'onMouseDown', 14947 click : 'onClick', 14948 keyup : 'onKeyUp', 14949 keydown : 'onKeyDown', 14950 keypress : 'onKeyPress', 14951 submit : 'onSubmit', 14952 reset : 'onReset', 14953 contextmenu : 'onContextMenu', 14954 dblclick : 'onDblClick', 14955 paste : 'onPaste' // Doesn't work in all browsers yet 14956 }; 14957 14958 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14959 function eventHandler(evt, args) { 14960 var type = evt.type; 14961 14962 // Don't fire events when it's removed 14963 if (self.removed) 14964 return; 14965 14966 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14967 if (self.onEvent.dispatch(self, evt, args) !== false) { 14968 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14969 } 14970 }; 14971 14972 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14973 function doOperaFocus(e) { 14974 self.focus(true); 14975 }; 14976 14977 function nodeChanged(ed, e) { 14978 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14979 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14980 self.selection.normalize(); 14981 } 14982 14983 self.nodeChanged(); 14984 } 14985 14986 // Add DOM events 14987 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14988 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14989 14990 switch (nativeName) { 14991 case 'contextmenu': 14992 dom.bind(root, nativeName, eventHandler); 14993 break; 14994 14995 case 'paste': 14996 dom.bind(self.getBody(), nativeName, eventHandler); 14997 break; 14998 14999 case 'submit': 15000 case 'reset': 15001 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 15002 break; 15003 15004 default: 15005 dom.bind(root, nativeName, eventHandler); 15006 } 15007 }); 15008 15009 // Set the editor as active when focused 15010 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 15011 self.focus(true); 15012 }); 15013 15014 if (settings.content_editable && tinymce.isOpera) { 15015 dom.bind(self.getBody(), 'click', doOperaFocus); 15016 dom.bind(self.getBody(), 'keydown', doOperaFocus); 15017 } 15018 15019 // Add node change handler 15020 self.onMouseUp.add(nodeChanged); 15021 15022 self.onKeyUp.add(function(ed, e) { 15023 var keyCode = e.keyCode; 15024 15025 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 15026 nodeChanged(ed, e); 15027 }); 15028 15029 // Add reset handler 15030 self.onReset.add(function() { 15031 self.setContent(self.startContent, {format : 'raw'}); 15032 }); 15033 15034 // Add shortcuts 15035 function handleShortcut(e, execute) { 15036 if (e.altKey || e.ctrlKey || e.metaKey) { 15037 each(self.shortcuts, function(shortcut) { 15038 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 15039 15040 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 15041 return; 15042 15043 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 15044 e.preventDefault(); 15045 15046 if (execute) { 15047 shortcut.func.call(shortcut.scope); 15048 } 15049 15050 return true; 15051 } 15052 }); 15053 } 15054 }; 15055 15056 self.onKeyUp.add(function(ed, e) { 15057 handleShortcut(e); 15058 }); 15059 15060 self.onKeyPress.add(function(ed, e) { 15061 handleShortcut(e); 15062 }); 15063 15064 self.onKeyDown.add(function(ed, e) { 15065 handleShortcut(e, true); 15066 }); 15067 15068 if (tinymce.isOpera) { 15069 self.onClick.add(function(ed, e) { 15070 e.preventDefault(); 15071 }); 15072 } 15073 }; 15074 })(tinymce); 15075 (function(tinymce) { 15076 // Added for compression purposes 15077 var each = tinymce.each, undef, TRUE = true, FALSE = false; 15078 15079 tinymce.EditorCommands = function(editor) { 15080 var dom = editor.dom, 15081 selection = editor.selection, 15082 commands = {state: {}, exec : {}, value : {}}, 15083 settings = editor.settings, 15084 formatter = editor.formatter, 15085 bookmark; 15086 15087 function execCommand(command, ui, value) { 15088 var func; 15089 15090 command = command.toLowerCase(); 15091 if (func = commands.exec[command]) { 15092 func(command, ui, value); 15093 return TRUE; 15094 } 15095 15096 return FALSE; 15097 }; 15098 15099 function queryCommandState(command) { 15100 var func; 15101 15102 command = command.toLowerCase(); 15103 if (func = commands.state[command]) 15104 return func(command); 15105 15106 return -1; 15107 }; 15108 15109 function queryCommandValue(command) { 15110 var func; 15111 15112 command = command.toLowerCase(); 15113 if (func = commands.value[command]) 15114 return func(command); 15115 15116 return FALSE; 15117 }; 15118 15119 function addCommands(command_list, type) { 15120 type = type || 'exec'; 15121 15122 each(command_list, function(callback, command) { 15123 each(command.toLowerCase().split(','), function(command) { 15124 commands[type][command] = callback; 15125 }); 15126 }); 15127 }; 15128 15129 // Expose public methods 15130 tinymce.extend(this, { 15131 execCommand : execCommand, 15132 queryCommandState : queryCommandState, 15133 queryCommandValue : queryCommandValue, 15134 addCommands : addCommands 15135 }); 15136 15137 // Private methods 15138 15139 function execNativeCommand(command, ui, value) { 15140 if (ui === undef) 15141 ui = FALSE; 15142 15143 if (value === undef) 15144 value = null; 15145 15146 return editor.getDoc().execCommand(command, ui, value); 15147 }; 15148 15149 function isFormatMatch(name) { 15150 return formatter.match(name); 15151 }; 15152 15153 function toggleFormat(name, value) { 15154 formatter.toggle(name, value ? {value : value} : undef); 15155 }; 15156 15157 function storeSelection(type) { 15158 bookmark = selection.getBookmark(type); 15159 }; 15160 15161 function restoreSelection() { 15162 selection.moveToBookmark(bookmark); 15163 }; 15164 15165 // Add execCommand overrides 15166 addCommands({ 15167 // Ignore these, added for compatibility 15168 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 15169 15170 // Add undo manager logic 15171 'mceEndUndoLevel,mceAddUndoLevel' : function() { 15172 editor.undoManager.add(); 15173 }, 15174 15175 'Cut,Copy,Paste' : function(command) { 15176 var doc = editor.getDoc(), failed; 15177 15178 // Try executing the native command 15179 try { 15180 execNativeCommand(command); 15181 } catch (ex) { 15182 // Command failed 15183 failed = TRUE; 15184 } 15185 15186 // Present alert message about clipboard access not being available 15187 if (failed || !doc.queryCommandSupported(command)) { 15188 if (tinymce.isGecko) { 15189 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 15190 if (state) 15191 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 15192 }); 15193 } else 15194 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 15195 } 15196 }, 15197 15198 // Override unlink command 15199 unlink : function(command) { 15200 if (selection.isCollapsed()) 15201 selection.select(selection.getNode()); 15202 15203 execNativeCommand(command); 15204 selection.collapse(FALSE); 15205 }, 15206 15207 // Override justify commands to use the text formatter engine 15208 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15209 var align = command.substring(7); 15210 15211 // Remove all other alignments first 15212 each('left,center,right,full'.split(','), function(name) { 15213 if (align != name) 15214 formatter.remove('align' + name); 15215 }); 15216 15217 toggleFormat('align' + align); 15218 execCommand('mceRepaint'); 15219 }, 15220 15221 // Override list commands to fix WebKit bug 15222 'InsertUnorderedList,InsertOrderedList' : function(command) { 15223 var listElm, listParent; 15224 15225 execNativeCommand(command); 15226 15227 // WebKit produces lists within block elements so we need to split them 15228 // we will replace the native list creation logic to custom logic later on 15229 // TODO: Remove this when the list creation logic is removed 15230 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 15231 if (listElm) { 15232 listParent = listElm.parentNode; 15233 15234 // If list is within a text block then split that block 15235 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 15236 storeSelection(); 15237 dom.split(listParent, listElm); 15238 restoreSelection(); 15239 } 15240 } 15241 }, 15242 15243 // Override commands to use the text formatter engine 15244 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15245 toggleFormat(command); 15246 }, 15247 15248 // Override commands to use the text formatter engine 15249 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 15250 toggleFormat(command, value); 15251 }, 15252 15253 FontSize : function(command, ui, value) { 15254 var fontClasses, fontSizes; 15255 15256 // Convert font size 1-7 to styles 15257 if (value >= 1 && value <= 7) { 15258 fontSizes = tinymce.explode(settings.font_size_style_values); 15259 fontClasses = tinymce.explode(settings.font_size_classes); 15260 15261 if (fontClasses) 15262 value = fontClasses[value - 1] || value; 15263 else 15264 value = fontSizes[value - 1] || value; 15265 } 15266 15267 toggleFormat(command, value); 15268 }, 15269 15270 RemoveFormat : function(command) { 15271 formatter.remove(command); 15272 }, 15273 15274 mceBlockQuote : function(command) { 15275 toggleFormat('blockquote'); 15276 }, 15277 15278 FormatBlock : function(command, ui, value) { 15279 return toggleFormat(value || 'p'); 15280 }, 15281 15282 mceCleanup : function() { 15283 var bookmark = selection.getBookmark(); 15284 15285 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 15286 15287 selection.moveToBookmark(bookmark); 15288 }, 15289 15290 mceRemoveNode : function(command, ui, value) { 15291 var node = value || selection.getNode(); 15292 15293 // Make sure that the body node isn't removed 15294 if (node != editor.getBody()) { 15295 storeSelection(); 15296 editor.dom.remove(node, TRUE); 15297 restoreSelection(); 15298 } 15299 }, 15300 15301 mceSelectNodeDepth : function(command, ui, value) { 15302 var counter = 0; 15303 15304 dom.getParent(selection.getNode(), function(node) { 15305 if (node.nodeType == 1 && counter++ == value) { 15306 selection.select(node); 15307 return FALSE; 15308 } 15309 }, editor.getBody()); 15310 }, 15311 15312 mceSelectNode : function(command, ui, value) { 15313 selection.select(value); 15314 }, 15315 15316 mceInsertContent : function(command, ui, value) { 15317 var parser, serializer, parentNode, rootNode, fragment, args, 15318 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 15319 15320 //selection.normalize(); 15321 15322 // Setup parser and serializer 15323 parser = editor.parser; 15324 serializer = new tinymce.html.Serializer({}, editor.schema); 15325 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 15326 15327 // Run beforeSetContent handlers on the HTML to be inserted 15328 args = {content: value, format: 'html'}; 15329 selection.onBeforeSetContent.dispatch(selection, args); 15330 value = args.content; 15331 15332 // Add caret at end of contents if it's missing 15333 if (value.indexOf('{$caret}') == -1) 15334 value += '{$caret}'; 15335 15336 // Replace the caret marker with a span bookmark element 15337 value = value.replace(/\{\$caret\}/, bookmarkHtml); 15338 15339 // Insert node maker where we will insert the new HTML and get it's parent 15340 if (!selection.isCollapsed()) 15341 editor.getDoc().execCommand('Delete', false, null); 15342 15343 parentNode = selection.getNode(); 15344 15345 // Parse the fragment within the context of the parent node 15346 args = {context : parentNode.nodeName.toLowerCase()}; 15347 fragment = parser.parse(value, args); 15348 15349 // Move the caret to a more suitable location 15350 node = fragment.lastChild; 15351 if (node.attr('id') == 'mce_marker') { 15352 marker = node; 15353 15354 for (node = node.prev; node; node = node.walk(true)) { 15355 if (node.type == 3 || !dom.isBlock(node.name)) { 15356 node.parent.insert(marker, node, node.name === 'br'); 15357 break; 15358 } 15359 } 15360 } 15361 15362 // If parser says valid we can insert the contents into that parent 15363 if (!args.invalid) { 15364 value = serializer.serialize(fragment); 15365 15366 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 15367 node = parentNode.firstChild; 15368 node2 = parentNode.lastChild; 15369 if (!node || (node === node2 && node.nodeName === 'BR')) 15370 dom.setHTML(parentNode, value); 15371 else 15372 selection.setContent(value); 15373 } else { 15374 // If the fragment was invalid within that context then we need 15375 // to parse and process the parent it's inserted into 15376 15377 // Insert bookmark node and get the parent 15378 selection.setContent(bookmarkHtml); 15379 parentNode = selection.getNode(); 15380 rootNode = editor.getBody(); 15381 15382 // Opera will return the document node when selection is in root 15383 if (parentNode.nodeType == 9) 15384 parentNode = node = rootNode; 15385 else 15386 node = parentNode; 15387 15388 // Find the ancestor just before the root element 15389 while (node !== rootNode) { 15390 parentNode = node; 15391 node = node.parentNode; 15392 } 15393 15394 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15395 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15396 value = serializer.serialize( 15397 parser.parse( 15398 // Need to replace by using a function since $ in the contents would otherwise be a problem 15399 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15400 return serializer.serialize(fragment); 15401 }) 15402 ) 15403 ); 15404 15405 // Set the inner/outer HTML depending on if we are in the root or not 15406 if (parentNode == rootNode) 15407 dom.setHTML(rootNode, value); 15408 else 15409 dom.setOuterHTML(parentNode, value); 15410 } 15411 15412 marker = dom.get('mce_marker'); 15413 15414 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15415 nodeRect = dom.getRect(marker); 15416 viewPortRect = dom.getViewPort(editor.getWin()); 15417 15418 // Check if node is out side the viewport if it is then scroll to it 15419 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15420 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15421 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15422 viewportBodyElement.scrollLeft = nodeRect.x; 15423 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15424 } 15425 15426 // Move selection before marker and remove it 15427 rng = dom.createRng(); 15428 15429 // If previous sibling is a text node set the selection to the end of that node 15430 node = marker.previousSibling; 15431 if (node && node.nodeType == 3) { 15432 rng.setStart(node, node.nodeValue.length); 15433 } else { 15434 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15435 rng.setStartBefore(marker); 15436 rng.setEndBefore(marker); 15437 } 15438 15439 // Remove the marker node and set the new range 15440 dom.remove(marker); 15441 selection.setRng(rng); 15442 15443 // Dispatch after event and add any visual elements needed 15444 selection.onSetContent.dispatch(selection, args); 15445 editor.addVisual(); 15446 }, 15447 15448 mceInsertRawHTML : function(command, ui, value) { 15449 selection.setContent('tiny_mce_marker'); 15450 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15451 }, 15452 15453 mceToggleFormat : function(command, ui, value) { 15454 toggleFormat(value); 15455 }, 15456 15457 mceSetContent : function(command, ui, value) { 15458 editor.setContent(value); 15459 }, 15460 15461 'Indent,Outdent' : function(command) { 15462 var intentValue, indentUnit, value; 15463 15464 // Setup indent level 15465 intentValue = settings.indentation; 15466 indentUnit = /[a-z%]+$/i.exec(intentValue); 15467 intentValue = parseInt(intentValue); 15468 15469 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15470 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15471 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15472 formatter.apply('div'); 15473 } 15474 15475 each(selection.getSelectedBlocks(), function(element) { 15476 if (command == 'outdent') { 15477 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15478 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15479 } else 15480 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15481 }); 15482 } else 15483 execNativeCommand(command); 15484 }, 15485 15486 mceRepaint : function() { 15487 var bookmark; 15488 15489 if (tinymce.isGecko) { 15490 try { 15491 storeSelection(TRUE); 15492 15493 if (selection.getSel()) 15494 selection.getSel().selectAllChildren(editor.getBody()); 15495 15496 selection.collapse(TRUE); 15497 restoreSelection(); 15498 } catch (ex) { 15499 // Ignore 15500 } 15501 } 15502 }, 15503 15504 mceToggleFormat : function(command, ui, value) { 15505 formatter.toggle(value); 15506 }, 15507 15508 InsertHorizontalRule : function() { 15509 editor.execCommand('mceInsertContent', false, '<hr />'); 15510 }, 15511 15512 mceToggleVisualAid : function() { 15513 editor.hasVisual = !editor.hasVisual; 15514 editor.addVisual(); 15515 }, 15516 15517 mceReplaceContent : function(command, ui, value) { 15518 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15519 }, 15520 15521 mceInsertLink : function(command, ui, value) { 15522 var anchor; 15523 15524 if (typeof(value) == 'string') 15525 value = {href : value}; 15526 15527 anchor = dom.getParent(selection.getNode(), 'a'); 15528 15529 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15530 value.href = value.href.replace(' ', '%20'); 15531 15532 // Remove existing links if there could be child links or that the href isn't specified 15533 if (!anchor || !value.href) { 15534 formatter.remove('link'); 15535 } 15536 15537 // Apply new link to selection 15538 if (value.href) { 15539 formatter.apply('link', value, anchor); 15540 } 15541 }, 15542 15543 selectAll : function() { 15544 var root = dom.getRoot(), rng = dom.createRng(); 15545 15546 // Old IE does a better job with selectall than new versions 15547 if (selection.getRng().setStart) { 15548 rng.setStart(root, 0); 15549 rng.setEnd(root, root.childNodes.length); 15550 15551 selection.setRng(rng); 15552 } else { 15553 execNativeCommand('SelectAll'); 15554 } 15555 } 15556 }); 15557 15558 // Add queryCommandState overrides 15559 addCommands({ 15560 // Override justify commands 15561 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15562 var name = 'align' + command.substring(7); 15563 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15564 var matches = tinymce.map(nodes, function(node) { 15565 return !!formatter.matchNode(node, name); 15566 }); 15567 return tinymce.inArray(matches, TRUE) !== -1; 15568 }, 15569 15570 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15571 return isFormatMatch(command); 15572 }, 15573 15574 mceBlockQuote : function() { 15575 return isFormatMatch('blockquote'); 15576 }, 15577 15578 Outdent : function() { 15579 var node; 15580 15581 if (settings.inline_styles) { 15582 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15583 return TRUE; 15584 15585 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15586 return TRUE; 15587 } 15588 15589 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15590 }, 15591 15592 'InsertUnorderedList,InsertOrderedList' : function(command) { 15593 var list = dom.getParent(selection.getNode(), 'ul,ol'); 15594 return list && 15595 (command === 'insertunorderedlist' && list.tagName === 'UL' 15596 || command === 'insertorderedlist' && list.tagName === 'OL'); 15597 } 15598 }, 'state'); 15599 15600 // Add queryCommandValue overrides 15601 addCommands({ 15602 'FontSize,FontName' : function(command) { 15603 var value = 0, parent; 15604 15605 if (parent = dom.getParent(selection.getNode(), 'span')) { 15606 if (command == 'fontsize') 15607 value = parent.style.fontSize; 15608 else 15609 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15610 } 15611 15612 return value; 15613 } 15614 }, 'value'); 15615 15616 // Add undo manager logic 15617 addCommands({ 15618 Undo : function() { 15619 editor.undoManager.undo(); 15620 }, 15621 15622 Redo : function() { 15623 editor.undoManager.redo(); 15624 } 15625 }); 15626 }; 15627 })(tinymce); 15628 (function(tinymce) { 15629 var Dispatcher = tinymce.util.Dispatcher; 15630 15631 tinymce.UndoManager = function(editor) { 15632 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15633 15634 function getContent() { 15635 // Remove whitespace before/after and remove pure bogus nodes 15636 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15637 }; 15638 15639 function addNonTypingUndoLevel() { 15640 self.typing = false; 15641 self.add(); 15642 }; 15643 15644 // Create event instances 15645 onBeforeAdd = new Dispatcher(self); 15646 onAdd = new Dispatcher(self); 15647 onUndo = new Dispatcher(self); 15648 onRedo = new Dispatcher(self); 15649 15650 // Pass though onAdd event from UndoManager to Editor as onChange 15651 onAdd.add(function(undoman, level) { 15652 if (undoman.hasUndo()) 15653 return editor.onChange.dispatch(editor, level, undoman); 15654 }); 15655 15656 // Pass though onUndo event from UndoManager to Editor 15657 onUndo.add(function(undoman, level) { 15658 return editor.onUndo.dispatch(editor, level, undoman); 15659 }); 15660 15661 // Pass though onRedo event from UndoManager to Editor 15662 onRedo.add(function(undoman, level) { 15663 return editor.onRedo.dispatch(editor, level, undoman); 15664 }); 15665 15666 // Add initial undo level when the editor is initialized 15667 editor.onInit.add(function() { 15668 self.add(); 15669 }); 15670 15671 // Get position before an execCommand is processed 15672 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15673 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15674 self.beforeChange(); 15675 } 15676 }); 15677 15678 // Add undo level after an execCommand call was made 15679 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15680 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15681 self.add(); 15682 } 15683 }); 15684 15685 // Add undo level on save contents, drag end and blur/focusout 15686 editor.onSaveContent.add(addNonTypingUndoLevel); 15687 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15688 editor.dom.bind(editor.getBody(), 'focusout', function(e) { 15689 if (!editor.removed && self.typing) { 15690 addNonTypingUndoLevel(); 15691 } 15692 }); 15693 15694 editor.onKeyUp.add(function(editor, e) { 15695 var keyCode = e.keyCode; 15696 15697 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15698 addNonTypingUndoLevel(); 15699 } 15700 }); 15701 15702 editor.onKeyDown.add(function(editor, e) { 15703 var keyCode = e.keyCode; 15704 15705 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15706 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15707 if (self.typing) { 15708 addNonTypingUndoLevel(); 15709 } 15710 15711 return; 15712 } 15713 15714 // If key isn't shift,ctrl,alt,capslock,metakey 15715 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15716 self.beforeChange(); 15717 self.typing = true; 15718 self.add(); 15719 } 15720 }); 15721 15722 editor.onMouseDown.add(function(editor, e) { 15723 if (self.typing) { 15724 addNonTypingUndoLevel(); 15725 } 15726 }); 15727 15728 // Add keyboard shortcuts for undo/redo keys 15729 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15730 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15731 15732 self = { 15733 // Explose for debugging reasons 15734 data : data, 15735 15736 typing : false, 15737 15738 onBeforeAdd: onBeforeAdd, 15739 15740 onAdd : onAdd, 15741 15742 onUndo : onUndo, 15743 15744 onRedo : onRedo, 15745 15746 beforeChange : function() { 15747 beforeBookmark = editor.selection.getBookmark(2, true); 15748 }, 15749 15750 add : function(level) { 15751 var i, settings = editor.settings, lastLevel; 15752 15753 level = level || {}; 15754 level.content = getContent(); 15755 15756 self.onBeforeAdd.dispatch(self, level); 15757 15758 // Add undo level if needed 15759 lastLevel = data[index]; 15760 if (lastLevel && lastLevel.content == level.content) 15761 return null; 15762 15763 // Set before bookmark on previous level 15764 if (data[index]) 15765 data[index].beforeBookmark = beforeBookmark; 15766 15767 // Time to compress 15768 if (settings.custom_undo_redo_levels) { 15769 if (data.length > settings.custom_undo_redo_levels) { 15770 for (i = 0; i < data.length - 1; i++) 15771 data[i] = data[i + 1]; 15772 15773 data.length--; 15774 index = data.length; 15775 } 15776 } 15777 15778 // Get a non intrusive normalized bookmark 15779 level.bookmark = editor.selection.getBookmark(2, true); 15780 15781 // Crop array if needed 15782 if (index < data.length - 1) 15783 data.length = index + 1; 15784 15785 data.push(level); 15786 index = data.length - 1; 15787 15788 self.onAdd.dispatch(self, level); 15789 editor.isNotDirty = 0; 15790 15791 return level; 15792 }, 15793 15794 undo : function() { 15795 var level, i; 15796 15797 if (self.typing) { 15798 self.add(); 15799 self.typing = false; 15800 } 15801 15802 if (index > 0) { 15803 level = data[--index]; 15804 15805 editor.setContent(level.content, {format : 'raw'}); 15806 editor.selection.moveToBookmark(level.beforeBookmark); 15807 15808 self.onUndo.dispatch(self, level); 15809 } 15810 15811 return level; 15812 }, 15813 15814 redo : function() { 15815 var level; 15816 15817 if (index < data.length - 1) { 15818 level = data[++index]; 15819 15820 editor.setContent(level.content, {format : 'raw'}); 15821 editor.selection.moveToBookmark(level.bookmark); 15822 15823 self.onRedo.dispatch(self, level); 15824 } 15825 15826 return level; 15827 }, 15828 15829 clear : function() { 15830 data = []; 15831 index = 0; 15832 self.typing = false; 15833 }, 15834 15835 hasUndo : function() { 15836 return index > 0 || this.typing; 15837 }, 15838 15839 hasRedo : function() { 15840 return index < data.length - 1 && !this.typing; 15841 } 15842 }; 15843 15844 return self; 15845 }; 15846 })(tinymce); 15847 tinymce.ForceBlocks = function(editor) { 15848 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15849 15850 function addRootBlocks() { 15851 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15852 15853 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15854 return; 15855 15856 // Check if node is wrapped in block 15857 while (node && node != rootNode) { 15858 if (blockElements[node.nodeName]) 15859 return; 15860 15861 node = node.parentNode; 15862 } 15863 15864 // Get current selection 15865 rng = selection.getRng(); 15866 if (rng.setStart) { 15867 startContainer = rng.startContainer; 15868 startOffset = rng.startOffset; 15869 endContainer = rng.endContainer; 15870 endOffset = rng.endOffset; 15871 } else { 15872 // Force control range into text range 15873 if (rng.item) { 15874 node = rng.item(0); 15875 rng = editor.getDoc().body.createTextRange(); 15876 rng.moveToElementText(node); 15877 } 15878 15879 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15880 tmpRng = rng.duplicate(); 15881 tmpRng.collapse(true); 15882 startOffset = tmpRng.move('character', offset) * -1; 15883 15884 if (!tmpRng.collapsed) { 15885 tmpRng = rng.duplicate(); 15886 tmpRng.collapse(false); 15887 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15888 } 15889 } 15890 15891 // Wrap non block elements and text nodes 15892 node = rootNode.firstChild; 15893 while (node) { 15894 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15895 // Remove empty text nodes 15896 if (node.nodeType === 3 && node.nodeValue.length == 0) { 15897 tempNode = node; 15898 node = node.nextSibling; 15899 dom.remove(tempNode); 15900 continue; 15901 } 15902 15903 if (!rootBlockNode) { 15904 rootBlockNode = dom.create(settings.forced_root_block); 15905 node.parentNode.insertBefore(rootBlockNode, node); 15906 wrapped = true; 15907 } 15908 15909 tempNode = node; 15910 node = node.nextSibling; 15911 rootBlockNode.appendChild(tempNode); 15912 } else { 15913 rootBlockNode = null; 15914 node = node.nextSibling; 15915 } 15916 } 15917 15918 if (wrapped) { 15919 if (rng.setStart) { 15920 rng.setStart(startContainer, startOffset); 15921 rng.setEnd(endContainer, endOffset); 15922 selection.setRng(rng); 15923 } else { 15924 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15925 if (isInEditorDocument) { 15926 try { 15927 rng = editor.getDoc().body.createTextRange(); 15928 rng.moveToElementText(rootNode); 15929 rng.collapse(true); 15930 rng.moveStart('character', startOffset); 15931 15932 if (endOffset > 0) 15933 rng.moveEnd('character', endOffset); 15934 15935 rng.select(); 15936 } catch (ex) { 15937 // Ignore 15938 } 15939 } 15940 } 15941 15942 editor.nodeChanged(); 15943 } 15944 }; 15945 15946 // Force root blocks 15947 if (settings.forced_root_block) { 15948 editor.onKeyUp.add(addRootBlocks); 15949 editor.onNodeChange.add(addRootBlocks); 15950 } 15951 }; 15952 (function(tinymce) { 15953 // Shorten names 15954 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15955 15956 tinymce.create('tinymce.ControlManager', { 15957 ControlManager : function(ed, s) { 15958 var t = this, i; 15959 15960 s = s || {}; 15961 t.editor = ed; 15962 t.controls = {}; 15963 t.onAdd = new tinymce.util.Dispatcher(t); 15964 t.onPostRender = new tinymce.util.Dispatcher(t); 15965 t.prefix = s.prefix || ed.id + '_'; 15966 t._cls = {}; 15967 15968 t.onPostRender.add(function() { 15969 each(t.controls, function(c) { 15970 c.postRender(); 15971 }); 15972 }); 15973 }, 15974 15975 get : function(id) { 15976 return this.controls[this.prefix + id] || this.controls[id]; 15977 }, 15978 15979 setActive : function(id, s) { 15980 var c = null; 15981 15982 if (c = this.get(id)) 15983 c.setActive(s); 15984 15985 return c; 15986 }, 15987 15988 setDisabled : function(id, s) { 15989 var c = null; 15990 15991 if (c = this.get(id)) 15992 c.setDisabled(s); 15993 15994 return c; 15995 }, 15996 15997 add : function(c) { 15998 var t = this; 15999 16000 if (c) { 16001 t.controls[c.id] = c; 16002 t.onAdd.dispatch(c, t); 16003 } 16004 16005 return c; 16006 }, 16007 16008 createControl : function(name) { 16009 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 16010 16011 // Build control factory cache 16012 if (!self.controlFactories) { 16013 self.controlFactories = []; 16014 each(editor.plugins, function(plugin) { 16015 if (plugin.createControl) { 16016 self.controlFactories.push(plugin); 16017 } 16018 }); 16019 } 16020 16021 // Create controls by asking cached factories 16022 factories = self.controlFactories; 16023 for (i = 0, l = factories.length; i < l; i++) { 16024 ctrl = factories[i].createControl(name, self); 16025 16026 if (ctrl) { 16027 return self.add(ctrl); 16028 } 16029 } 16030 16031 // Create sepearator 16032 if (name === "|" || name === "separator") { 16033 return self.createSeparator(); 16034 } 16035 16036 // Create control from button collection 16037 if (editor.buttons && (ctrl = editor.buttons[name])) { 16038 return self.createButton(name, ctrl); 16039 } 16040 16041 return self.add(ctrl); 16042 }, 16043 16044 createDropMenu : function(id, s, cc) { 16045 var t = this, ed = t.editor, c, bm, v, cls; 16046 16047 s = extend({ 16048 'class' : 'mceDropDown', 16049 constrain : ed.settings.constrain_menus 16050 }, s); 16051 16052 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 16053 if (v = ed.getParam('skin_variant')) 16054 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 16055 16056 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 16057 16058 id = t.prefix + id; 16059 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 16060 c = t.controls[id] = new cls(id, s); 16061 c.onAddItem.add(function(c, o) { 16062 var s = o.settings; 16063 16064 s.title = ed.getLang(s.title, s.title); 16065 16066 if (!s.onclick) { 16067 s.onclick = function(v) { 16068 if (s.cmd) 16069 ed.execCommand(s.cmd, s.ui || false, s.value); 16070 }; 16071 } 16072 }); 16073 16074 ed.onRemove.add(function() { 16075 c.destroy(); 16076 }); 16077 16078 // Fix for bug #1897785, #1898007 16079 if (tinymce.isIE) { 16080 c.onShowMenu.add(function() { 16081 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16082 ed.focus(); 16083 16084 bm = ed.selection.getBookmark(1); 16085 }); 16086 16087 c.onHideMenu.add(function() { 16088 if (bm) { 16089 ed.selection.moveToBookmark(bm); 16090 bm = 0; 16091 } 16092 }); 16093 } 16094 16095 return t.add(c); 16096 }, 16097 16098 createListBox : function(id, s, cc) { 16099 var t = this, ed = t.editor, cmd, c, cls; 16100 16101 if (t.get(id)) 16102 return null; 16103 16104 s.title = ed.translate(s.title); 16105 s.scope = s.scope || ed; 16106 16107 if (!s.onselect) { 16108 s.onselect = function(v) { 16109 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16110 }; 16111 } 16112 16113 s = extend({ 16114 title : s.title, 16115 'class' : 'mce_' + id, 16116 scope : s.scope, 16117 control_manager : t 16118 }, s); 16119 16120 id = t.prefix + id; 16121 16122 16123 function useNativeListForAccessibility(ed) { 16124 return ed.settings.use_accessible_selects && !tinymce.isGecko 16125 } 16126 16127 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 16128 c = new tinymce.ui.NativeListBox(id, s); 16129 else { 16130 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 16131 c = new cls(id, s, ed); 16132 } 16133 16134 t.controls[id] = c; 16135 16136 // Fix focus problem in Safari 16137 if (tinymce.isWebKit) { 16138 c.onPostRender.add(function(c, n) { 16139 // Store bookmark on mousedown 16140 Event.add(n, 'mousedown', function() { 16141 ed.bookmark = ed.selection.getBookmark(1); 16142 }); 16143 16144 // Restore on focus, since it might be lost 16145 Event.add(n, 'focus', function() { 16146 ed.selection.moveToBookmark(ed.bookmark); 16147 ed.bookmark = null; 16148 }); 16149 }); 16150 } 16151 16152 if (c.hideMenu) 16153 ed.onMouseDown.add(c.hideMenu, c); 16154 16155 return t.add(c); 16156 }, 16157 16158 createButton : function(id, s, cc) { 16159 var t = this, ed = t.editor, o, c, cls; 16160 16161 if (t.get(id)) 16162 return null; 16163 16164 s.title = ed.translate(s.title); 16165 s.label = ed.translate(s.label); 16166 s.scope = s.scope || ed; 16167 16168 if (!s.onclick && !s.menu_button) { 16169 s.onclick = function() { 16170 ed.execCommand(s.cmd, s.ui || false, s.value); 16171 }; 16172 } 16173 16174 s = extend({ 16175 title : s.title, 16176 'class' : 'mce_' + id, 16177 unavailable_prefix : ed.getLang('unavailable', ''), 16178 scope : s.scope, 16179 control_manager : t 16180 }, s); 16181 16182 id = t.prefix + id; 16183 16184 if (s.menu_button) { 16185 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 16186 c = new cls(id, s, ed); 16187 ed.onMouseDown.add(c.hideMenu, c); 16188 } else { 16189 cls = t._cls.button || tinymce.ui.Button; 16190 c = new cls(id, s, ed); 16191 } 16192 16193 return t.add(c); 16194 }, 16195 16196 createMenuButton : function(id, s, cc) { 16197 s = s || {}; 16198 s.menu_button = 1; 16199 16200 return this.createButton(id, s, cc); 16201 }, 16202 16203 createSplitButton : function(id, s, cc) { 16204 var t = this, ed = t.editor, cmd, c, cls; 16205 16206 if (t.get(id)) 16207 return null; 16208 16209 s.title = ed.translate(s.title); 16210 s.scope = s.scope || ed; 16211 16212 if (!s.onclick) { 16213 s.onclick = function(v) { 16214 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16215 }; 16216 } 16217 16218 if (!s.onselect) { 16219 s.onselect = function(v) { 16220 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16221 }; 16222 } 16223 16224 s = extend({ 16225 title : s.title, 16226 'class' : 'mce_' + id, 16227 scope : s.scope, 16228 control_manager : t 16229 }, s); 16230 16231 id = t.prefix + id; 16232 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 16233 c = t.add(new cls(id, s, ed)); 16234 ed.onMouseDown.add(c.hideMenu, c); 16235 16236 return c; 16237 }, 16238 16239 createColorSplitButton : function(id, s, cc) { 16240 var t = this, ed = t.editor, cmd, c, cls, bm; 16241 16242 if (t.get(id)) 16243 return null; 16244 16245 s.title = ed.translate(s.title); 16246 s.scope = s.scope || ed; 16247 16248 if (!s.onclick) { 16249 s.onclick = function(v) { 16250 if (tinymce.isIE) 16251 bm = ed.selection.getBookmark(1); 16252 16253 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16254 }; 16255 } 16256 16257 if (!s.onselect) { 16258 s.onselect = function(v) { 16259 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16260 }; 16261 } 16262 16263 s = extend({ 16264 title : s.title, 16265 'class' : 'mce_' + id, 16266 'menu_class' : ed.getParam('skin') + 'Skin', 16267 scope : s.scope, 16268 more_colors_title : ed.getLang('more_colors') 16269 }, s); 16270 16271 id = t.prefix + id; 16272 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 16273 c = new cls(id, s, ed); 16274 ed.onMouseDown.add(c.hideMenu, c); 16275 16276 // Remove the menu element when the editor is removed 16277 ed.onRemove.add(function() { 16278 c.destroy(); 16279 }); 16280 16281 // Fix for bug #1897785, #1898007 16282 if (tinymce.isIE) { 16283 c.onShowMenu.add(function() { 16284 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16285 ed.focus(); 16286 bm = ed.selection.getBookmark(1); 16287 }); 16288 16289 c.onHideMenu.add(function() { 16290 if (bm) { 16291 ed.selection.moveToBookmark(bm); 16292 bm = 0; 16293 } 16294 }); 16295 } 16296 16297 return t.add(c); 16298 }, 16299 16300 createToolbar : function(id, s, cc) { 16301 var c, t = this, cls; 16302 16303 id = t.prefix + id; 16304 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 16305 c = new cls(id, s, t.editor); 16306 16307 if (t.get(id)) 16308 return null; 16309 16310 return t.add(c); 16311 }, 16312 16313 createToolbarGroup : function(id, s, cc) { 16314 var c, t = this, cls; 16315 id = t.prefix + id; 16316 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 16317 c = new cls(id, s, t.editor); 16318 16319 if (t.get(id)) 16320 return null; 16321 16322 return t.add(c); 16323 }, 16324 16325 createSeparator : function(cc) { 16326 var cls = cc || this._cls.separator || tinymce.ui.Separator; 16327 16328 return new cls(); 16329 }, 16330 16331 setControlType : function(n, c) { 16332 return this._cls[n.toLowerCase()] = c; 16333 }, 16334 16335 destroy : function() { 16336 each(this.controls, function(c) { 16337 c.destroy(); 16338 }); 16339 16340 this.controls = null; 16341 } 16342 }); 16343 })(tinymce); 16344 (function(tinymce) { 16345 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 16346 16347 tinymce.create('tinymce.WindowManager', { 16348 WindowManager : function(ed) { 16349 var t = this; 16350 16351 t.editor = ed; 16352 t.onOpen = new Dispatcher(t); 16353 t.onClose = new Dispatcher(t); 16354 t.params = {}; 16355 t.features = {}; 16356 }, 16357 16358 open : function(s, p) { 16359 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 16360 16361 // Default some options 16362 s = s || {}; 16363 p = p || {}; 16364 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 16365 sh = isOpera ? vp.h : screen.height; 16366 s.name = s.name || 'mc_' + new Date().getTime(); 16367 s.width = parseInt(s.width || 320); 16368 s.height = parseInt(s.height || 240); 16369 s.resizable = true; 16370 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 16371 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 16372 p.inline = false; 16373 p.mce_width = s.width; 16374 p.mce_height = s.height; 16375 p.mce_auto_focus = s.auto_focus; 16376 16377 if (mo) { 16378 if (isIE) { 16379 s.center = true; 16380 s.help = false; 16381 s.dialogWidth = s.width + 'px'; 16382 s.dialogHeight = s.height + 'px'; 16383 s.scroll = s.scrollbars || false; 16384 } 16385 } 16386 16387 // Build features string 16388 each(s, function(v, k) { 16389 if (tinymce.is(v, 'boolean')) 16390 v = v ? 'yes' : 'no'; 16391 16392 if (!/^(name|url)$/.test(k)) { 16393 if (isIE && mo) 16394 f += (f ? ';' : '') + k + ':' + v; 16395 else 16396 f += (f ? ',' : '') + k + '=' + v; 16397 } 16398 }); 16399 16400 t.features = s; 16401 t.params = p; 16402 t.onOpen.dispatch(t, s, p); 16403 16404 u = s.url || s.file; 16405 u = tinymce._addVer(u); 16406 16407 try { 16408 if (isIE && mo) { 16409 w = 1; 16410 window.showModalDialog(u, window, f); 16411 } else 16412 w = window.open(u, s.name, f); 16413 } catch (ex) { 16414 // Ignore 16415 } 16416 16417 if (!w) 16418 alert(t.editor.getLang('popup_blocked')); 16419 }, 16420 16421 close : function(w) { 16422 w.close(); 16423 this.onClose.dispatch(this); 16424 }, 16425 16426 createInstance : function(cl, a, b, c, d, e) { 16427 var f = tinymce.resolve(cl); 16428 16429 return new f(a, b, c, d, e); 16430 }, 16431 16432 confirm : function(t, cb, s, w) { 16433 w = w || window; 16434 16435 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16436 }, 16437 16438 alert : function(tx, cb, s, w) { 16439 var t = this; 16440 16441 w = w || window; 16442 w.alert(t._decode(t.editor.getLang(tx, tx))); 16443 16444 if (cb) 16445 cb.call(s || t); 16446 }, 16447 16448 resizeBy : function(dw, dh, win) { 16449 win.resizeBy(dw, dh); 16450 }, 16451 16452 // Internal functions 16453 16454 _decode : function(s) { 16455 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16456 } 16457 }); 16458 }(tinymce)); 16459 (function(tinymce) { 16460 tinymce.Formatter = function(ed) { 16461 var formats = {}, 16462 each = tinymce.each, 16463 dom = ed.dom, 16464 selection = ed.selection, 16465 TreeWalker = tinymce.dom.TreeWalker, 16466 rangeUtils = new tinymce.dom.RangeUtils(dom), 16467 isValid = ed.schema.isValidChild, 16468 isArray = tinymce.isArray, 16469 isBlock = dom.isBlock, 16470 forcedRootBlock = ed.settings.forced_root_block, 16471 nodeIndex = dom.nodeIndex, 16472 INVISIBLE_CHAR = '\uFEFF', 16473 MCE_ATTR_RE = /^(src|href|style)$/, 16474 FALSE = false, 16475 TRUE = true, 16476 formatChangeData, 16477 undef, 16478 getContentEditable = dom.getContentEditable; 16479 16480 function isTextBlock(name) { 16481 if (name.nodeType) { 16482 name = name.nodeName; 16483 } 16484 16485 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 16486 } 16487 16488 function getParents(node, selector) { 16489 return dom.getParents(node, selector, dom.getRoot()); 16490 }; 16491 16492 function isCaretNode(node) { 16493 return node.nodeType === 1 && node.id === '_mce_caret'; 16494 }; 16495 16496 function defaultFormats() { 16497 register({ 16498 alignleft : [ 16499 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16500 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16501 ], 16502 16503 aligncenter : [ 16504 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16505 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16506 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16507 ], 16508 16509 alignright : [ 16510 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16511 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16512 ], 16513 16514 alignfull : [ 16515 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16516 ], 16517 16518 bold : [ 16519 {inline : 'strong', remove : 'all'}, 16520 {inline : 'span', styles : {fontWeight : 'bold'}}, 16521 {inline : 'b', remove : 'all'} 16522 ], 16523 16524 italic : [ 16525 {inline : 'em', remove : 'all'}, 16526 {inline : 'span', styles : {fontStyle : 'italic'}}, 16527 {inline : 'i', remove : 'all'} 16528 ], 16529 16530 underline : [ 16531 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16532 {inline : 'u', remove : 'all'} 16533 ], 16534 16535 strikethrough : [ 16536 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16537 {inline : 'strike', remove : 'all'} 16538 ], 16539 16540 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16541 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16542 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16543 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16544 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16545 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16546 subscript : {inline : 'sub'}, 16547 superscript : {inline : 'sup'}, 16548 16549 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16550 onmatch : function(node) { 16551 return true; 16552 }, 16553 16554 onformat : function(elm, fmt, vars) { 16555 each(vars, function(value, key) { 16556 dom.setAttrib(elm, key, value); 16557 }); 16558 } 16559 }, 16560 16561 removeformat : [ 16562 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16563 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16564 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16565 ] 16566 }); 16567 16568 // Register default block formats 16569 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16570 register(name, {block : name, remove : 'all'}); 16571 }); 16572 16573 // Register user defined formats 16574 register(ed.settings.formats); 16575 }; 16576 16577 function addKeyboardShortcuts() { 16578 // Add some inline shortcuts 16579 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16580 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16581 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16582 16583 // BlockFormat shortcuts keys 16584 for (var i = 1; i <= 6; i++) { 16585 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16586 } 16587 16588 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16589 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16590 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16591 }; 16592 16593 // Public functions 16594 16595 function get(name) { 16596 return name ? formats[name] : formats; 16597 }; 16598 16599 function register(name, format) { 16600 if (name) { 16601 if (typeof(name) !== 'string') { 16602 each(name, function(format, name) { 16603 register(name, format); 16604 }); 16605 } else { 16606 // Force format into array and add it to internal collection 16607 format = format.length ? format : [format]; 16608 16609 each(format, function(format) { 16610 // Set deep to false by default on selector formats this to avoid removing 16611 // alignment on images inside paragraphs when alignment is changed on paragraphs 16612 if (format.deep === undef) 16613 format.deep = !format.selector; 16614 16615 // Default to true 16616 if (format.split === undef) 16617 format.split = !format.selector || format.inline; 16618 16619 // Default to true 16620 if (format.remove === undef && format.selector && !format.inline) 16621 format.remove = 'none'; 16622 16623 // Mark format as a mixed format inline + block level 16624 if (format.selector && format.inline) { 16625 format.mixed = true; 16626 format.block_expand = true; 16627 } 16628 16629 // Split classes if needed 16630 if (typeof(format.classes) === 'string') 16631 format.classes = format.classes.split(/\s+/); 16632 }); 16633 16634 formats[name] = format; 16635 } 16636 } 16637 }; 16638 16639 var getTextDecoration = function(node) { 16640 var decoration; 16641 16642 ed.dom.getParent(node, function(n) { 16643 decoration = ed.dom.getStyle(n, 'text-decoration'); 16644 return decoration && decoration !== 'none'; 16645 }); 16646 16647 return decoration; 16648 }; 16649 16650 var processUnderlineAndColor = function(node) { 16651 var textDecoration; 16652 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16653 textDecoration = getTextDecoration(node.parentNode); 16654 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16655 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16656 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16657 ed.dom.setStyle(node, 'text-decoration', null); 16658 } 16659 } 16660 }; 16661 16662 function apply(name, vars, node) { 16663 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16664 16665 function setElementFormat(elm, fmt) { 16666 fmt = fmt || format; 16667 16668 if (elm) { 16669 if (fmt.onformat) { 16670 fmt.onformat(elm, fmt, vars, node); 16671 } 16672 16673 each(fmt.styles, function(value, name) { 16674 dom.setStyle(elm, name, replaceVars(value, vars)); 16675 }); 16676 16677 each(fmt.attributes, function(value, name) { 16678 dom.setAttrib(elm, name, replaceVars(value, vars)); 16679 }); 16680 16681 each(fmt.classes, function(value) { 16682 value = replaceVars(value, vars); 16683 16684 if (!dom.hasClass(elm, value)) 16685 dom.addClass(elm, value); 16686 }); 16687 } 16688 }; 16689 function adjustSelectionToVisibleSelection() { 16690 function findSelectionEnd(start, end) { 16691 var walker = new TreeWalker(end); 16692 for (node = walker.current(); node; node = walker.prev()) { 16693 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16694 return node; 16695 } 16696 } 16697 }; 16698 16699 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16700 // as this isn't visible to the user. 16701 var rng = ed.selection.getRng(); 16702 var start = rng.startContainer; 16703 var end = rng.endContainer; 16704 16705 if (start != end && rng.endOffset === 0) { 16706 var newEnd = findSelectionEnd(start, end); 16707 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16708 16709 rng.setEnd(newEnd, endOffset); 16710 } 16711 16712 return rng; 16713 } 16714 16715 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16716 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16717 16718 // find the index of the first child list. 16719 each(node.childNodes, function(n, index) { 16720 if (n.nodeName === "UL" || n.nodeName === "OL") { 16721 listIndex = index; 16722 list = n; 16723 return false; 16724 } 16725 }); 16726 16727 // get the index of the bookmarks 16728 each(node.childNodes, function(n, index) { 16729 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16730 if (n.id == bookmark.id + "_start") { 16731 startIndex = index; 16732 } else if (n.id == bookmark.id + "_end") { 16733 endIndex = index; 16734 } 16735 } 16736 }); 16737 16738 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16739 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16740 each(tinymce.grep(node.childNodes), process); 16741 return 0; 16742 } else { 16743 currentWrapElm = dom.clone(wrapElm, FALSE); 16744 16745 // create a list of the nodes on the same side of the list as the selection 16746 each(tinymce.grep(node.childNodes), function(n, index) { 16747 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16748 nodes.push(n); 16749 n.parentNode.removeChild(n); 16750 } 16751 }); 16752 16753 // insert the wrapping element either before or after the list. 16754 if (startIndex < listIndex) { 16755 node.insertBefore(currentWrapElm, list); 16756 } else if (startIndex > listIndex) { 16757 node.insertBefore(currentWrapElm, list.nextSibling); 16758 } 16759 16760 // add the new nodes to the list. 16761 newWrappers.push(currentWrapElm); 16762 16763 each(nodes, function(node) { 16764 currentWrapElm.appendChild(node); 16765 }); 16766 16767 return currentWrapElm; 16768 } 16769 }; 16770 16771 function applyRngStyle(rng, bookmark, node_specific) { 16772 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16773 16774 // Setup wrapper element 16775 wrapName = format.inline || format.block; 16776 wrapElm = dom.create(wrapName); 16777 setElementFormat(wrapElm); 16778 16779 rangeUtils.walk(rng, function(nodes) { 16780 var currentWrapElm; 16781 16782 function process(node) { 16783 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16784 16785 lastContentEditable = contentEditable; 16786 nodeName = node.nodeName.toLowerCase(); 16787 parentName = node.parentNode.nodeName.toLowerCase(); 16788 16789 // Node has a contentEditable value 16790 if (node.nodeType === 1 && getContentEditable(node)) { 16791 lastContentEditable = contentEditable; 16792 contentEditable = getContentEditable(node) === "true"; 16793 hasContentEditableState = true; // We don't want to wrap the container only it's children 16794 } 16795 16796 // Stop wrapping on br elements 16797 if (isEq(nodeName, 'br')) { 16798 currentWrapElm = 0; 16799 16800 // Remove any br elements when we wrap things 16801 if (format.block) 16802 dom.remove(node); 16803 16804 return; 16805 } 16806 16807 // If node is wrapper type 16808 if (format.wrapper && matchNode(node, name, vars)) { 16809 currentWrapElm = 0; 16810 return; 16811 } 16812 16813 // Can we rename the block 16814 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16815 node = dom.rename(node, wrapName); 16816 setElementFormat(node); 16817 newWrappers.push(node); 16818 currentWrapElm = 0; 16819 return; 16820 } 16821 16822 // Handle selector patterns 16823 if (format.selector) { 16824 // Look for matching formats 16825 each(formatList, function(format) { 16826 // Check collapsed state if it exists 16827 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16828 return; 16829 } 16830 16831 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16832 setElementFormat(node, format); 16833 found = true; 16834 } 16835 }); 16836 16837 // Continue processing if a selector match wasn't found and a inline element is defined 16838 if (!format.inline || found) { 16839 currentWrapElm = 0; 16840 return; 16841 } 16842 } 16843 16844 // Is it valid to wrap this item 16845 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16846 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) { 16847 // Start wrapping 16848 if (!currentWrapElm) { 16849 // Wrap the node 16850 currentWrapElm = dom.clone(wrapElm, FALSE); 16851 node.parentNode.insertBefore(currentWrapElm, node); 16852 newWrappers.push(currentWrapElm); 16853 } 16854 16855 currentWrapElm.appendChild(node); 16856 } else if (nodeName == 'li' && bookmark) { 16857 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16858 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16859 } else { 16860 // Start a new wrapper for possible children 16861 currentWrapElm = 0; 16862 16863 each(tinymce.grep(node.childNodes), process); 16864 16865 if (hasContentEditableState) { 16866 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16867 } 16868 16869 // End the last wrapper 16870 currentWrapElm = 0; 16871 } 16872 }; 16873 16874 // Process siblings from range 16875 each(nodes, process); 16876 }); 16877 16878 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16879 if (format.wrap_links === false) { 16880 each(newWrappers, function(node) { 16881 function process(node) { 16882 var i, currentWrapElm, children; 16883 16884 if (node.nodeName === 'A') { 16885 currentWrapElm = dom.clone(wrapElm, FALSE); 16886 newWrappers.push(currentWrapElm); 16887 16888 children = tinymce.grep(node.childNodes); 16889 for (i = 0; i < children.length; i++) 16890 currentWrapElm.appendChild(children[i]); 16891 16892 node.appendChild(currentWrapElm); 16893 } 16894 16895 each(tinymce.grep(node.childNodes), process); 16896 }; 16897 16898 process(node); 16899 }); 16900 } 16901 16902 // Cleanup 16903 16904 each(newWrappers, function(node) { 16905 var childCount; 16906 16907 function getChildCount(node) { 16908 var count = 0; 16909 16910 each(node.childNodes, function(node) { 16911 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16912 count++; 16913 }); 16914 16915 return count; 16916 }; 16917 16918 function mergeStyles(node) { 16919 var child, clone; 16920 16921 each(node.childNodes, function(node) { 16922 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16923 child = node; 16924 return FALSE; // break loop 16925 } 16926 }); 16927 16928 // If child was found and of the same type as the current node 16929 if (child && matchName(child, format)) { 16930 clone = dom.clone(child, FALSE); 16931 setElementFormat(clone); 16932 16933 dom.replace(clone, node, TRUE); 16934 dom.remove(child, 1); 16935 } 16936 16937 return clone || node; 16938 }; 16939 16940 childCount = getChildCount(node); 16941 16942 // Remove empty nodes but only if there is multiple wrappers and they are not block 16943 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16944 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16945 dom.remove(node, 1); 16946 return; 16947 } 16948 16949 if (format.inline || format.wrapper) { 16950 // Merges the current node with it's children of similar type to reduce the number of elements 16951 if (!format.exact && childCount === 1) 16952 node = mergeStyles(node); 16953 16954 // Remove/merge children 16955 each(formatList, function(format) { 16956 // Merge all children of similar type will move styles from child to parent 16957 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16958 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16959 each(dom.select(format.inline, node), function(child) { 16960 var parent; 16961 16962 // When wrap_links is set to false we don't want 16963 // to remove the format on children within links 16964 if (format.wrap_links === false) { 16965 parent = child.parentNode; 16966 16967 do { 16968 if (parent.nodeName === 'A') 16969 return; 16970 } while (parent = parent.parentNode); 16971 } 16972 16973 removeFormat(format, vars, child, format.exact ? child : null); 16974 }); 16975 }); 16976 16977 // Remove child if direct parent is of same type 16978 if (matchNode(node.parentNode, name, vars)) { 16979 dom.remove(node, 1); 16980 node = 0; 16981 return TRUE; 16982 } 16983 16984 // Look for parent with similar style format 16985 if (format.merge_with_parents) { 16986 dom.getParent(node.parentNode, function(parent) { 16987 if (matchNode(parent, name, vars)) { 16988 dom.remove(node, 1); 16989 node = 0; 16990 return TRUE; 16991 } 16992 }); 16993 } 16994 16995 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16996 if (node && format.merge_siblings !== false) { 16997 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16998 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16999 } 17000 } 17001 }); 17002 }; 17003 17004 if (format) { 17005 if (node) { 17006 if (node.nodeType) { 17007 rng = dom.createRng(); 17008 rng.setStartBefore(node); 17009 rng.setEndAfter(node); 17010 applyRngStyle(expandRng(rng, formatList), null, true); 17011 } else { 17012 applyRngStyle(node, null, true); 17013 } 17014 } else { 17015 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 17016 // Obtain selection node before selection is unselected by applyRngStyle() 17017 var curSelNode = ed.selection.getNode(); 17018 17019 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 17020 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 17021 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 17022 apply(formatList[0].defaultBlock); 17023 } 17024 17025 // Apply formatting to selection 17026 ed.selection.setRng(adjustSelectionToVisibleSelection()); 17027 bookmark = selection.getBookmark(); 17028 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 17029 17030 // Colored nodes should be underlined so that the color of the underline matches the text color. 17031 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 17032 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 17033 processUnderlineAndColor(curSelNode); 17034 } 17035 17036 selection.moveToBookmark(bookmark); 17037 moveStart(selection.getRng(TRUE)); 17038 ed.nodeChanged(); 17039 } else 17040 performCaretAction('apply', name, vars); 17041 } 17042 } 17043 }; 17044 17045 function remove(name, vars, node) { 17046 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 17047 17048 // Merges the styles for each node 17049 function process(node) { 17050 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 17051 17052 // Skip on text nodes as they have neither format to remove nor children 17053 if (node.nodeType === 3) { 17054 return; 17055 } 17056 17057 // Node has a contentEditable value 17058 if (node.nodeType === 1 && getContentEditable(node)) { 17059 lastContentEditable = contentEditable; 17060 contentEditable = getContentEditable(node) === "true"; 17061 hasContentEditableState = true; // We don't want to wrap the container only it's children 17062 } 17063 17064 // Grab the children first since the nodelist might be changed 17065 children = tinymce.grep(node.childNodes); 17066 17067 // Process current node 17068 if (contentEditable && !hasContentEditableState) { 17069 for (i = 0, l = formatList.length; i < l; i++) { 17070 if (removeFormat(formatList[i], vars, node, node)) 17071 break; 17072 } 17073 } 17074 17075 // Process the children 17076 if (format.deep) { 17077 if (children.length) { 17078 for (i = 0, l = children.length; i < l; i++) 17079 process(children[i]); 17080 17081 if (hasContentEditableState) { 17082 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 17083 } 17084 } 17085 } 17086 }; 17087 17088 function findFormatRoot(container) { 17089 var formatRoot; 17090 17091 // Find format root 17092 each(getParents(container.parentNode).reverse(), function(parent) { 17093 var format; 17094 17095 // Find format root element 17096 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 17097 // Is the node matching the format we are looking for 17098 format = matchNode(parent, name, vars); 17099 if (format && format.split !== false) 17100 formatRoot = parent; 17101 } 17102 }); 17103 17104 return formatRoot; 17105 }; 17106 17107 function wrapAndSplit(format_root, container, target, split) { 17108 var parent, clone, lastClone, firstClone, i, formatRootParent; 17109 17110 // Format root found then clone formats and split it 17111 if (format_root) { 17112 formatRootParent = format_root.parentNode; 17113 17114 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 17115 clone = dom.clone(parent, FALSE); 17116 17117 for (i = 0; i < formatList.length; i++) { 17118 if (removeFormat(formatList[i], vars, clone, clone)) { 17119 clone = 0; 17120 break; 17121 } 17122 } 17123 17124 // Build wrapper node 17125 if (clone) { 17126 if (lastClone) 17127 clone.appendChild(lastClone); 17128 17129 if (!firstClone) 17130 firstClone = clone; 17131 17132 lastClone = clone; 17133 } 17134 } 17135 17136 // Never split block elements if the format is mixed 17137 if (split && (!format.mixed || !isBlock(format_root))) 17138 container = dom.split(format_root, container); 17139 17140 // Wrap container in cloned formats 17141 if (lastClone) { 17142 target.parentNode.insertBefore(lastClone, target); 17143 firstClone.appendChild(target); 17144 } 17145 } 17146 17147 return container; 17148 }; 17149 17150 function splitToFormatRoot(container) { 17151 return wrapAndSplit(findFormatRoot(container), container, container, true); 17152 }; 17153 17154 function unwrap(start) { 17155 var node = dom.get(start ? '_start' : '_end'), 17156 out = node[start ? 'firstChild' : 'lastChild']; 17157 17158 // If the end is placed within the start the result will be removed 17159 // So this checks if the out node is a bookmark node if it is it 17160 // checks for another more suitable node 17161 if (isBookmarkNode(out)) 17162 out = out[start ? 'firstChild' : 'lastChild']; 17163 17164 dom.remove(node, true); 17165 17166 return out; 17167 }; 17168 17169 function removeRngStyle(rng) { 17170 var startContainer, endContainer, node; 17171 17172 rng = expandRng(rng, formatList, TRUE); 17173 17174 if (format.split) { 17175 startContainer = getContainer(rng, TRUE); 17176 endContainer = getContainer(rng); 17177 17178 if (startContainer != endContainer) { 17179 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 17180 // This will happen if you tripple click a table cell and use remove formatting 17181 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 17182 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 17183 } 17184 17185 // Wrap start/end nodes in span element since these might be cloned/moved 17186 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 17187 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 17188 17189 // Split start/end 17190 splitToFormatRoot(startContainer); 17191 splitToFormatRoot(endContainer); 17192 17193 // Unwrap start/end to get real elements again 17194 startContainer = unwrap(TRUE); 17195 endContainer = unwrap(); 17196 } else 17197 startContainer = endContainer = splitToFormatRoot(startContainer); 17198 17199 // Update range positions since they might have changed after the split operations 17200 rng.startContainer = startContainer.parentNode; 17201 rng.startOffset = nodeIndex(startContainer); 17202 rng.endContainer = endContainer.parentNode; 17203 rng.endOffset = nodeIndex(endContainer) + 1; 17204 } 17205 17206 // Remove items between start/end 17207 rangeUtils.walk(rng, function(nodes) { 17208 each(nodes, function(node) { 17209 process(node); 17210 17211 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 17212 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 17213 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 17214 } 17215 }); 17216 }); 17217 }; 17218 17219 // Handle node 17220 if (node) { 17221 if (node.nodeType) { 17222 rng = dom.createRng(); 17223 rng.setStartBefore(node); 17224 rng.setEndAfter(node); 17225 removeRngStyle(rng); 17226 } else { 17227 removeRngStyle(node); 17228 } 17229 17230 return; 17231 } 17232 17233 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 17234 bookmark = selection.getBookmark(); 17235 removeRngStyle(selection.getRng(TRUE)); 17236 selection.moveToBookmark(bookmark); 17237 17238 // 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 17239 if (format.inline && match(name, vars, selection.getStart())) { 17240 moveStart(selection.getRng(true)); 17241 } 17242 17243 ed.nodeChanged(); 17244 } else 17245 performCaretAction('remove', name, vars); 17246 }; 17247 17248 function toggle(name, vars, node) { 17249 var fmt = get(name); 17250 17251 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 17252 remove(name, vars, node); 17253 else 17254 apply(name, vars, node); 17255 }; 17256 17257 function matchNode(node, name, vars, similar) { 17258 var formatList = get(name), format, i, classes; 17259 17260 function matchItems(node, format, item_name) { 17261 var key, value, items = format[item_name], i; 17262 17263 // Custom match 17264 if (format.onmatch) { 17265 return format.onmatch(node, format, item_name); 17266 } 17267 17268 // Check all items 17269 if (items) { 17270 // Non indexed object 17271 if (items.length === undef) { 17272 for (key in items) { 17273 if (items.hasOwnProperty(key)) { 17274 if (item_name === 'attributes') 17275 value = dom.getAttrib(node, key); 17276 else 17277 value = getStyle(node, key); 17278 17279 if (similar && !value && !format.exact) 17280 return; 17281 17282 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 17283 return; 17284 } 17285 } 17286 } else { 17287 // Only one match needed for indexed arrays 17288 for (i = 0; i < items.length; i++) { 17289 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 17290 return format; 17291 } 17292 } 17293 } 17294 17295 return format; 17296 }; 17297 17298 if (formatList && node) { 17299 // Check each format in list 17300 for (i = 0; i < formatList.length; i++) { 17301 format = formatList[i]; 17302 17303 // Name name, attributes, styles and classes 17304 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 17305 // Match classes 17306 if (classes = format.classes) { 17307 for (i = 0; i < classes.length; i++) { 17308 if (!dom.hasClass(node, classes[i])) 17309 return; 17310 } 17311 } 17312 17313 return format; 17314 } 17315 } 17316 } 17317 }; 17318 17319 function match(name, vars, node) { 17320 var startNode; 17321 17322 function matchParents(node) { 17323 // Find first node with similar format settings 17324 node = dom.getParent(node, function(node) { 17325 return !!matchNode(node, name, vars, true); 17326 }); 17327 17328 // Do an exact check on the similar format element 17329 return matchNode(node, name, vars); 17330 }; 17331 17332 // Check specified node 17333 if (node) 17334 return matchParents(node); 17335 17336 // Check selected node 17337 node = selection.getNode(); 17338 if (matchParents(node)) 17339 return TRUE; 17340 17341 // Check start node if it's different 17342 startNode = selection.getStart(); 17343 if (startNode != node) { 17344 if (matchParents(startNode)) 17345 return TRUE; 17346 } 17347 17348 return FALSE; 17349 }; 17350 17351 function matchAll(names, vars) { 17352 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 17353 17354 // Check start of selection for formats 17355 startElement = selection.getStart(); 17356 dom.getParent(startElement, function(node) { 17357 var i, name; 17358 17359 for (i = 0; i < names.length; i++) { 17360 name = names[i]; 17361 17362 if (!checkedMap[name] && matchNode(node, name, vars)) { 17363 checkedMap[name] = true; 17364 matchedFormatNames.push(name); 17365 } 17366 } 17367 }, dom.getRoot()); 17368 17369 return matchedFormatNames; 17370 }; 17371 17372 function canApply(name) { 17373 var formatList = get(name), startNode, parents, i, x, selector; 17374 17375 if (formatList) { 17376 startNode = selection.getStart(); 17377 parents = getParents(startNode); 17378 17379 for (x = formatList.length - 1; x >= 0; x--) { 17380 selector = formatList[x].selector; 17381 17382 // Format is not selector based, then always return TRUE 17383 if (!selector) 17384 return TRUE; 17385 17386 for (i = parents.length - 1; i >= 0; i--) { 17387 if (dom.is(parents[i], selector)) 17388 return TRUE; 17389 } 17390 } 17391 } 17392 17393 return FALSE; 17394 }; 17395 17396 function formatChanged(formats, callback, similar) { 17397 var currentFormats; 17398 17399 // Setup format node change logic 17400 if (!formatChangeData) { 17401 formatChangeData = {}; 17402 currentFormats = {}; 17403 17404 ed.onNodeChange.addToTop(function(ed, cm, node) { 17405 var parents = getParents(node), matchedFormats = {}; 17406 17407 // Check for new formats 17408 each(formatChangeData, function(callbacks, format) { 17409 each(parents, function(node) { 17410 if (matchNode(node, format, {}, callbacks.similar)) { 17411 if (!currentFormats[format]) { 17412 // Execute callbacks 17413 each(callbacks, function(callback) { 17414 callback(true, {node: node, format: format, parents: parents}); 17415 }); 17416 17417 currentFormats[format] = callbacks; 17418 } 17419 17420 matchedFormats[format] = callbacks; 17421 return false; 17422 } 17423 }); 17424 }); 17425 17426 // Check if current formats still match 17427 each(currentFormats, function(callbacks, format) { 17428 if (!matchedFormats[format]) { 17429 delete currentFormats[format]; 17430 17431 each(callbacks, function(callback) { 17432 callback(false, {node: node, format: format, parents: parents}); 17433 }); 17434 } 17435 }); 17436 }); 17437 } 17438 17439 // Add format listeners 17440 each(formats.split(','), function(format) { 17441 if (!formatChangeData[format]) { 17442 formatChangeData[format] = []; 17443 formatChangeData[format].similar = similar; 17444 } 17445 17446 formatChangeData[format].push(callback); 17447 }); 17448 17449 return this; 17450 }; 17451 17452 // Expose to public 17453 tinymce.extend(this, { 17454 get : get, 17455 register : register, 17456 apply : apply, 17457 remove : remove, 17458 toggle : toggle, 17459 match : match, 17460 matchAll : matchAll, 17461 matchNode : matchNode, 17462 canApply : canApply, 17463 formatChanged: formatChanged 17464 }); 17465 17466 // Initialize 17467 defaultFormats(); 17468 addKeyboardShortcuts(); 17469 17470 // Private functions 17471 17472 function matchName(node, format) { 17473 // Check for inline match 17474 if (isEq(node, format.inline)) 17475 return TRUE; 17476 17477 // Check for block match 17478 if (isEq(node, format.block)) 17479 return TRUE; 17480 17481 // Check for selector match 17482 if (format.selector) 17483 return dom.is(node, format.selector); 17484 }; 17485 17486 function isEq(str1, str2) { 17487 str1 = str1 || ''; 17488 str2 = str2 || ''; 17489 17490 str1 = '' + (str1.nodeName || str1); 17491 str2 = '' + (str2.nodeName || str2); 17492 17493 return str1.toLowerCase() == str2.toLowerCase(); 17494 }; 17495 17496 function getStyle(node, name) { 17497 var styleVal = dom.getStyle(node, name); 17498 17499 // Force the format to hex 17500 if (name == 'color' || name == 'backgroundColor') 17501 styleVal = dom.toHex(styleVal); 17502 17503 // Opera will return bold as 700 17504 if (name == 'fontWeight' && styleVal == 700) 17505 styleVal = 'bold'; 17506 17507 return '' + styleVal; 17508 }; 17509 17510 function replaceVars(value, vars) { 17511 if (typeof(value) != "string") 17512 value = value(vars); 17513 else if (vars) { 17514 value = value.replace(/%(\w+)/g, function(str, name) { 17515 return vars[name] || str; 17516 }); 17517 } 17518 17519 return value; 17520 }; 17521 17522 function isWhiteSpaceNode(node) { 17523 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17524 }; 17525 17526 function wrap(node, name, attrs) { 17527 var wrapper = dom.create(name, attrs); 17528 17529 node.parentNode.insertBefore(wrapper, node); 17530 wrapper.appendChild(node); 17531 17532 return wrapper; 17533 }; 17534 17535 function expandRng(rng, format, remove) { 17536 var sibling, lastIdx, leaf, endPoint, 17537 startContainer = rng.startContainer, 17538 startOffset = rng.startOffset, 17539 endContainer = rng.endContainer, 17540 endOffset = rng.endOffset; 17541 17542 // This function walks up the tree if there is no siblings before/after the node 17543 function findParentContainer(start) { 17544 var container, parent, child, sibling, siblingName, root; 17545 17546 container = parent = start ? startContainer : endContainer; 17547 siblingName = start ? 'previousSibling' : 'nextSibling'; 17548 root = dom.getRoot(); 17549 17550 function isBogusBr(node) { 17551 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 17552 }; 17553 17554 // If it's a text node and the offset is inside the text 17555 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17556 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17557 return container; 17558 } 17559 } 17560 17561 for (;;) { 17562 // Stop expanding on block elements 17563 if (!format[0].block_expand && isBlock(parent)) 17564 return parent; 17565 17566 // Walk left/right 17567 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17568 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 17569 return parent; 17570 } 17571 } 17572 17573 // Check if we can move up are we at root level or body level 17574 if (parent.parentNode == root) { 17575 container = parent; 17576 break; 17577 } 17578 17579 parent = parent.parentNode; 17580 } 17581 17582 return container; 17583 }; 17584 17585 // This function walks down the tree to find the leaf at the selection. 17586 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17587 function findLeaf(node, offset) { 17588 if (offset === undef) 17589 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17590 while (node && node.hasChildNodes()) { 17591 node = node.childNodes[offset]; 17592 if (node) 17593 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17594 } 17595 return { node: node, offset: offset }; 17596 } 17597 17598 // If index based start position then resolve it 17599 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17600 lastIdx = startContainer.childNodes.length - 1; 17601 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17602 17603 if (startContainer.nodeType == 3) 17604 startOffset = 0; 17605 } 17606 17607 // If index based end position then resolve it 17608 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17609 lastIdx = endContainer.childNodes.length - 1; 17610 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17611 17612 if (endContainer.nodeType == 3) 17613 endOffset = endContainer.nodeValue.length; 17614 } 17615 17616 // Expands the node to the closes contentEditable false element if it exists 17617 function findParentContentEditable(node) { 17618 var parent = node; 17619 17620 while (parent) { 17621 if (parent.nodeType === 1 && getContentEditable(parent)) { 17622 return getContentEditable(parent) === "false" ? parent : node; 17623 } 17624 17625 parent = parent.parentNode; 17626 } 17627 17628 return node; 17629 }; 17630 17631 function findWordEndPoint(container, offset, start) { 17632 var walker, node, pos, lastTextNode; 17633 17634 function findSpace(node, offset) { 17635 var pos, pos2, str = node.nodeValue; 17636 17637 if (typeof(offset) == "undefined") { 17638 offset = start ? str.length : 0; 17639 } 17640 17641 if (start) { 17642 pos = str.lastIndexOf(' ', offset); 17643 pos2 = str.lastIndexOf('\u00a0', offset); 17644 pos = pos > pos2 ? pos : pos2; 17645 17646 // Include the space on remove to avoid tag soup 17647 if (pos !== -1 && !remove) { 17648 pos++; 17649 } 17650 } else { 17651 pos = str.indexOf(' ', offset); 17652 pos2 = str.indexOf('\u00a0', offset); 17653 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17654 } 17655 17656 return pos; 17657 }; 17658 17659 if (container.nodeType === 3) { 17660 pos = findSpace(container, offset); 17661 17662 if (pos !== -1) { 17663 return {container : container, offset : pos}; 17664 } 17665 17666 lastTextNode = container; 17667 } 17668 17669 // Walk the nodes inside the block 17670 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17671 while (node = walker[start ? 'prev' : 'next']()) { 17672 if (node.nodeType === 3) { 17673 lastTextNode = node; 17674 pos = findSpace(node); 17675 17676 if (pos !== -1) { 17677 return {container : node, offset : pos}; 17678 } 17679 } else if (isBlock(node)) { 17680 break; 17681 } 17682 } 17683 17684 if (lastTextNode) { 17685 if (start) { 17686 offset = 0; 17687 } else { 17688 offset = lastTextNode.length; 17689 } 17690 17691 return {container: lastTextNode, offset: offset}; 17692 } 17693 }; 17694 17695 function findSelectorEndPoint(container, sibling_name) { 17696 var parents, i, y, curFormat; 17697 17698 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17699 container = container[sibling_name]; 17700 17701 parents = getParents(container); 17702 for (i = 0; i < parents.length; i++) { 17703 for (y = 0; y < format.length; y++) { 17704 curFormat = format[y]; 17705 17706 // If collapsed state is set then skip formats that doesn't match that 17707 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17708 continue; 17709 17710 if (dom.is(parents[i], curFormat.selector)) 17711 return parents[i]; 17712 } 17713 } 17714 17715 return container; 17716 }; 17717 17718 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17719 var node; 17720 17721 // Expand to block of similar type 17722 if (!format[0].wrapper) 17723 node = dom.getParent(container, format[0].block); 17724 17725 // Expand to first wrappable block element or any block element 17726 if (!node) 17727 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock); 17728 17729 // Exclude inner lists from wrapping 17730 if (node && format[0].wrapper) 17731 node = getParents(node, 'ul,ol').reverse()[0] || node; 17732 17733 // Didn't find a block element look for first/last wrappable element 17734 if (!node) { 17735 node = container; 17736 17737 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17738 node = node[sibling_name]; 17739 17740 // Break on BR but include it will be removed later on 17741 // we can't remove it now since we need to check if it can be wrapped 17742 if (isEq(node, 'br')) 17743 break; 17744 } 17745 } 17746 17747 return node || container; 17748 }; 17749 17750 // Expand to closest contentEditable element 17751 startContainer = findParentContentEditable(startContainer); 17752 endContainer = findParentContentEditable(endContainer); 17753 17754 // Exclude bookmark nodes if possible 17755 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17756 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17757 startContainer = startContainer.nextSibling || startContainer; 17758 17759 if (startContainer.nodeType == 3) 17760 startOffset = 0; 17761 } 17762 17763 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17764 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17765 endContainer = endContainer.previousSibling || endContainer; 17766 17767 if (endContainer.nodeType == 3) 17768 endOffset = endContainer.length; 17769 } 17770 17771 if (format[0].inline) { 17772 if (rng.collapsed) { 17773 // Expand left to closest word boundery 17774 endPoint = findWordEndPoint(startContainer, startOffset, true); 17775 if (endPoint) { 17776 startContainer = endPoint.container; 17777 startOffset = endPoint.offset; 17778 } 17779 17780 // Expand right to closest word boundery 17781 endPoint = findWordEndPoint(endContainer, endOffset); 17782 if (endPoint) { 17783 endContainer = endPoint.container; 17784 endOffset = endPoint.offset; 17785 } 17786 } 17787 17788 // Avoid applying formatting to a trailing space. 17789 leaf = findLeaf(endContainer, endOffset); 17790 if (leaf.node) { 17791 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17792 leaf = findLeaf(leaf.node.previousSibling); 17793 17794 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17795 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17796 17797 if (leaf.offset > 1) { 17798 endContainer = leaf.node; 17799 endContainer.splitText(leaf.offset - 1); 17800 } 17801 } 17802 } 17803 } 17804 17805 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17806 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17807 // This will reduce the number of wrapper elements that needs to be created 17808 // Move start point up the tree 17809 if (format[0].inline || format[0].block_expand) { 17810 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17811 startContainer = findParentContainer(true); 17812 } 17813 17814 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17815 endContainer = findParentContainer(); 17816 } 17817 } 17818 17819 // Expand start/end container to matching selector 17820 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17821 // Find new startContainer/endContainer if there is better one 17822 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17823 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17824 } 17825 17826 // Expand start/end container to matching block element or text node 17827 if (format[0].block || format[0].selector) { 17828 // Find new startContainer/endContainer if there is better one 17829 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17830 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17831 17832 // Non block element then try to expand up the leaf 17833 if (format[0].block) { 17834 if (!isBlock(startContainer)) 17835 startContainer = findParentContainer(true); 17836 17837 if (!isBlock(endContainer)) 17838 endContainer = findParentContainer(); 17839 } 17840 } 17841 17842 // Setup index for startContainer 17843 if (startContainer.nodeType == 1) { 17844 startOffset = nodeIndex(startContainer); 17845 startContainer = startContainer.parentNode; 17846 } 17847 17848 // Setup index for endContainer 17849 if (endContainer.nodeType == 1) { 17850 endOffset = nodeIndex(endContainer) + 1; 17851 endContainer = endContainer.parentNode; 17852 } 17853 17854 // Return new range like object 17855 return { 17856 startContainer : startContainer, 17857 startOffset : startOffset, 17858 endContainer : endContainer, 17859 endOffset : endOffset 17860 }; 17861 } 17862 17863 function removeFormat(format, vars, node, compare_node) { 17864 var i, attrs, stylesModified; 17865 17866 // Check if node matches format 17867 if (!matchName(node, format)) 17868 return FALSE; 17869 17870 // Should we compare with format attribs and styles 17871 if (format.remove != 'all') { 17872 // Remove styles 17873 each(format.styles, function(value, name) { 17874 value = replaceVars(value, vars); 17875 17876 // Indexed array 17877 if (typeof(name) === 'number') { 17878 name = value; 17879 compare_node = 0; 17880 } 17881 17882 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17883 dom.setStyle(node, name, ''); 17884 17885 stylesModified = 1; 17886 }); 17887 17888 // Remove style attribute if it's empty 17889 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17890 node.removeAttribute('style'); 17891 node.removeAttribute('data-mce-style'); 17892 } 17893 17894 // Remove attributes 17895 each(format.attributes, function(value, name) { 17896 var valueOut; 17897 17898 value = replaceVars(value, vars); 17899 17900 // Indexed array 17901 if (typeof(name) === 'number') { 17902 name = value; 17903 compare_node = 0; 17904 } 17905 17906 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17907 // Keep internal classes 17908 if (name == 'class') { 17909 value = dom.getAttrib(node, name); 17910 if (value) { 17911 // Build new class value where everything is removed except the internal prefixed classes 17912 valueOut = ''; 17913 each(value.split(/\s+/), function(cls) { 17914 if (/mce\w+/.test(cls)) 17915 valueOut += (valueOut ? ' ' : '') + cls; 17916 }); 17917 17918 // We got some internal classes left 17919 if (valueOut) { 17920 dom.setAttrib(node, name, valueOut); 17921 return; 17922 } 17923 } 17924 } 17925 17926 // IE6 has a bug where the attribute doesn't get removed correctly 17927 if (name == "class") 17928 node.removeAttribute('className'); 17929 17930 // Remove mce prefixed attributes 17931 if (MCE_ATTR_RE.test(name)) 17932 node.removeAttribute('data-mce-' + name); 17933 17934 node.removeAttribute(name); 17935 } 17936 }); 17937 17938 // Remove classes 17939 each(format.classes, function(value) { 17940 value = replaceVars(value, vars); 17941 17942 if (!compare_node || dom.hasClass(compare_node, value)) 17943 dom.removeClass(node, value); 17944 }); 17945 17946 // Check for non internal attributes 17947 attrs = dom.getAttribs(node); 17948 for (i = 0; i < attrs.length; i++) { 17949 if (attrs[i].nodeName.indexOf('_') !== 0) 17950 return FALSE; 17951 } 17952 } 17953 17954 // Remove the inline child if it's empty for example <b> or <span> 17955 if (format.remove != 'none') { 17956 removeNode(node, format); 17957 return TRUE; 17958 } 17959 }; 17960 17961 function removeNode(node, format) { 17962 var parentNode = node.parentNode, rootBlockElm; 17963 17964 function find(node, next, inc) { 17965 node = getNonWhiteSpaceSibling(node, next, inc); 17966 17967 return !node || (node.nodeName == 'BR' || isBlock(node)); 17968 }; 17969 17970 if (format.block) { 17971 if (!forcedRootBlock) { 17972 // Append BR elements if needed before we remove the block 17973 if (isBlock(node) && !isBlock(parentNode)) { 17974 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17975 node.insertBefore(dom.create('br'), node.firstChild); 17976 17977 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17978 node.appendChild(dom.create('br')); 17979 } 17980 } else { 17981 // Wrap the block in a forcedRootBlock if we are at the root of document 17982 if (parentNode == dom.getRoot()) { 17983 if (!format.list_block || !isEq(node, format.list_block)) { 17984 each(tinymce.grep(node.childNodes), function(node) { 17985 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17986 if (!rootBlockElm) 17987 rootBlockElm = wrap(node, forcedRootBlock); 17988 else 17989 rootBlockElm.appendChild(node); 17990 } else 17991 rootBlockElm = 0; 17992 }); 17993 } 17994 } 17995 } 17996 } 17997 17998 // Never remove nodes that isn't the specified inline element if a selector is specified too 17999 if (format.selector && format.inline && !isEq(format.inline, node)) 18000 return; 18001 18002 dom.remove(node, 1); 18003 }; 18004 18005 function getNonWhiteSpaceSibling(node, next, inc) { 18006 if (node) { 18007 next = next ? 'nextSibling' : 'previousSibling'; 18008 18009 for (node = inc ? node : node[next]; node; node = node[next]) { 18010 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 18011 return node; 18012 } 18013 } 18014 }; 18015 18016 function isBookmarkNode(node) { 18017 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 18018 }; 18019 18020 function mergeSiblings(prev, next) { 18021 var marker, sibling, tmpSibling; 18022 18023 function compareElements(node1, node2) { 18024 // Not the same name 18025 if (node1.nodeName != node2.nodeName) 18026 return FALSE; 18027 18028 function getAttribs(node) { 18029 var attribs = {}; 18030 18031 each(dom.getAttribs(node), function(attr) { 18032 var name = attr.nodeName.toLowerCase(); 18033 18034 // Don't compare internal attributes or style 18035 if (name.indexOf('_') !== 0 && name !== 'style') 18036 attribs[name] = dom.getAttrib(node, name); 18037 }); 18038 18039 return attribs; 18040 }; 18041 18042 function compareObjects(obj1, obj2) { 18043 var value, name; 18044 18045 for (name in obj1) { 18046 // Obj1 has item obj2 doesn't have 18047 if (obj1.hasOwnProperty(name)) { 18048 value = obj2[name]; 18049 18050 // Obj2 doesn't have obj1 item 18051 if (value === undef) 18052 return FALSE; 18053 18054 // Obj2 item has a different value 18055 if (obj1[name] != value) 18056 return FALSE; 18057 18058 // Delete similar value 18059 delete obj2[name]; 18060 } 18061 } 18062 18063 // Check if obj 2 has something obj 1 doesn't have 18064 for (name in obj2) { 18065 // Obj2 has item obj1 doesn't have 18066 if (obj2.hasOwnProperty(name)) 18067 return FALSE; 18068 } 18069 18070 return TRUE; 18071 }; 18072 18073 // Attribs are not the same 18074 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 18075 return FALSE; 18076 18077 // Styles are not the same 18078 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 18079 return FALSE; 18080 18081 return TRUE; 18082 }; 18083 18084 function findElementSibling(node, sibling_name) { 18085 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 18086 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 18087 return node; 18088 18089 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 18090 return sibling; 18091 } 18092 18093 return node; 18094 }; 18095 18096 // Check if next/prev exists and that they are elements 18097 if (prev && next) { 18098 // If previous sibling is empty then jump over it 18099 prev = findElementSibling(prev, 'previousSibling'); 18100 next = findElementSibling(next, 'nextSibling'); 18101 18102 // Compare next and previous nodes 18103 if (compareElements(prev, next)) { 18104 // Append nodes between 18105 for (sibling = prev.nextSibling; sibling && sibling != next;) { 18106 tmpSibling = sibling; 18107 sibling = sibling.nextSibling; 18108 prev.appendChild(tmpSibling); 18109 } 18110 18111 // Remove next node 18112 dom.remove(next); 18113 18114 // Move children into prev node 18115 each(tinymce.grep(next.childNodes), function(node) { 18116 prev.appendChild(node); 18117 }); 18118 18119 return prev; 18120 } 18121 } 18122 18123 return next; 18124 }; 18125 18126 function getContainer(rng, start) { 18127 var container, offset, lastIdx, walker; 18128 18129 container = rng[start ? 'startContainer' : 'endContainer']; 18130 offset = rng[start ? 'startOffset' : 'endOffset']; 18131 18132 if (container.nodeType == 1) { 18133 lastIdx = container.childNodes.length - 1; 18134 18135 if (!start && offset) 18136 offset--; 18137 18138 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 18139 } 18140 18141 // If start text node is excluded then walk to the next node 18142 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 18143 container = new TreeWalker(container, ed.getBody()).next() || container; 18144 } 18145 18146 // If end text node is excluded then walk to the previous node 18147 if (container.nodeType === 3 && !start && offset === 0) { 18148 container = new TreeWalker(container, ed.getBody()).prev() || container; 18149 } 18150 18151 return container; 18152 }; 18153 18154 function performCaretAction(type, name, vars) { 18155 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 18156 18157 // Creates a caret container bogus element 18158 function createCaretContainer(fill) { 18159 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 18160 18161 if (fill) { 18162 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 18163 } 18164 18165 return caretContainer; 18166 }; 18167 18168 function isCaretContainerEmpty(node, nodes) { 18169 while (node) { 18170 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 18171 return false; 18172 } 18173 18174 // Collect nodes 18175 if (nodes && node.nodeType === 1) { 18176 nodes.push(node); 18177 } 18178 18179 node = node.firstChild; 18180 } 18181 18182 return true; 18183 }; 18184 18185 // Returns any parent caret container element 18186 function getParentCaretContainer(node) { 18187 while (node) { 18188 if (node.id === caretContainerId) { 18189 return node; 18190 } 18191 18192 node = node.parentNode; 18193 } 18194 }; 18195 18196 // Finds the first text node in the specified node 18197 function findFirstTextNode(node) { 18198 var walker; 18199 18200 if (node) { 18201 walker = new TreeWalker(node, node); 18202 18203 for (node = walker.current(); node; node = walker.next()) { 18204 if (node.nodeType === 3) { 18205 return node; 18206 } 18207 } 18208 } 18209 }; 18210 18211 // Removes the caret container for the specified node or all on the current document 18212 function removeCaretContainer(node, move_caret) { 18213 var child, rng; 18214 18215 if (!node) { 18216 node = getParentCaretContainer(selection.getStart()); 18217 18218 if (!node) { 18219 while (node = dom.get(caretContainerId)) { 18220 removeCaretContainer(node, false); 18221 } 18222 } 18223 } else { 18224 rng = selection.getRng(true); 18225 18226 if (isCaretContainerEmpty(node)) { 18227 if (move_caret !== false) { 18228 rng.setStartBefore(node); 18229 rng.setEndBefore(node); 18230 } 18231 18232 dom.remove(node); 18233 } else { 18234 child = findFirstTextNode(node); 18235 18236 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 18237 child = child.deleteData(0, 1); 18238 } 18239 18240 dom.remove(node, 1); 18241 } 18242 18243 selection.setRng(rng); 18244 } 18245 }; 18246 18247 // Applies formatting to the caret postion 18248 function applyCaretFormat() { 18249 var rng, caretContainer, textNode, offset, bookmark, container, text; 18250 18251 rng = selection.getRng(true); 18252 offset = rng.startOffset; 18253 container = rng.startContainer; 18254 text = container.nodeValue; 18255 18256 caretContainer = getParentCaretContainer(selection.getStart()); 18257 if (caretContainer) { 18258 textNode = findFirstTextNode(caretContainer); 18259 } 18260 18261 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 18262 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 18263 // Get bookmark of caret position 18264 bookmark = selection.getBookmark(); 18265 18266 // Collapse bookmark range (WebKit) 18267 rng.collapse(true); 18268 18269 // Expand the range to the closest word and split it at those points 18270 rng = expandRng(rng, get(name)); 18271 rng = rangeUtils.split(rng); 18272 18273 // Apply the format to the range 18274 apply(name, vars, rng); 18275 18276 // Move selection back to caret position 18277 selection.moveToBookmark(bookmark); 18278 } else { 18279 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 18280 caretContainer = createCaretContainer(true); 18281 textNode = caretContainer.firstChild; 18282 18283 rng.insertNode(caretContainer); 18284 offset = 1; 18285 18286 apply(name, vars, caretContainer); 18287 } else { 18288 apply(name, vars, caretContainer); 18289 } 18290 18291 // Move selection to text node 18292 selection.setCursorLocation(textNode, offset); 18293 } 18294 }; 18295 18296 function removeCaretFormat() { 18297 var rng = selection.getRng(true), container, offset, bookmark, 18298 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 18299 18300 container = rng.startContainer; 18301 offset = rng.startOffset; 18302 node = container; 18303 18304 if (container.nodeType == 3) { 18305 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 18306 hasContentAfter = true; 18307 } 18308 18309 node = node.parentNode; 18310 } 18311 18312 while (node) { 18313 if (matchNode(node, name, vars)) { 18314 formatNode = node; 18315 break; 18316 } 18317 18318 if (node.nextSibling) { 18319 hasContentAfter = true; 18320 } 18321 18322 parents.push(node); 18323 node = node.parentNode; 18324 } 18325 18326 // Node doesn't have the specified format 18327 if (!formatNode) { 18328 return; 18329 } 18330 18331 // Is there contents after the caret then remove the format on the element 18332 if (hasContentAfter) { 18333 // Get bookmark of caret position 18334 bookmark = selection.getBookmark(); 18335 18336 // Collapse bookmark range (WebKit) 18337 rng.collapse(true); 18338 18339 // Expand the range to the closest word and split it at those points 18340 rng = expandRng(rng, get(name), true); 18341 rng = rangeUtils.split(rng); 18342 18343 // Remove the format from the range 18344 remove(name, vars, rng); 18345 18346 // Move selection back to caret position 18347 selection.moveToBookmark(bookmark); 18348 } else { 18349 caretContainer = createCaretContainer(); 18350 18351 node = caretContainer; 18352 for (i = parents.length - 1; i >= 0; i--) { 18353 node.appendChild(dom.clone(parents[i], false)); 18354 node = node.firstChild; 18355 } 18356 18357 // Insert invisible character into inner most format element 18358 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 18359 node = node.firstChild; 18360 18361 var block = dom.getParent(formatNode, isTextBlock); 18362 18363 if (block && dom.isEmpty(block)) { 18364 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 18365 formatNode.parentNode.replaceChild(caretContainer, formatNode); 18366 } else { 18367 // Insert caret container after the formated node 18368 dom.insertAfter(caretContainer, formatNode); 18369 } 18370 18371 // Move selection to text node 18372 selection.setCursorLocation(node, 1); 18373 18374 // If the formatNode is empty, we can remove it safely. 18375 if (dom.isEmpty(formatNode)) { 18376 dom.remove(formatNode); 18377 } 18378 } 18379 }; 18380 18381 // Checks if the parent caret container node isn't empty if that is the case it 18382 // will remove the bogus state on all children that isn't empty 18383 function unmarkBogusCaretParents() { 18384 var i, caretContainer, node; 18385 18386 caretContainer = getParentCaretContainer(selection.getStart()); 18387 if (caretContainer && !dom.isEmpty(caretContainer)) { 18388 tinymce.walk(caretContainer, function(node) { 18389 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 18390 dom.setAttrib(node, 'data-mce-bogus', null); 18391 } 18392 }, 'childNodes'); 18393 } 18394 }; 18395 18396 // Only bind the caret events once 18397 if (!self._hasCaretEvents) { 18398 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 18399 ed.onBeforeGetContent.addToTop(function() { 18400 var nodes = [], i; 18401 18402 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 18403 // Mark children 18404 i = nodes.length; 18405 while (i--) { 18406 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 18407 } 18408 } 18409 }); 18410 18411 // Remove caret container on mouse up and on key up 18412 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 18413 ed[name].addToTop(function() { 18414 removeCaretContainer(); 18415 unmarkBogusCaretParents(); 18416 }); 18417 }); 18418 18419 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18420 ed.onKeyDown.addToTop(function(ed, e) { 18421 var keyCode = e.keyCode; 18422 18423 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18424 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18425 } 18426 18427 unmarkBogusCaretParents(); 18428 }); 18429 18430 // Remove bogus state if they got filled by contents using editor.selection.setContent 18431 selection.onSetContent.add(unmarkBogusCaretParents); 18432 18433 self._hasCaretEvents = true; 18434 } 18435 18436 // Do apply or remove caret format 18437 if (type == "apply") { 18438 applyCaretFormat(); 18439 } else { 18440 removeCaretFormat(); 18441 } 18442 }; 18443 18444 function moveStart(rng) { 18445 var container = rng.startContainer, 18446 offset = rng.startOffset, isAtEndOfText, 18447 walker, node, nodes, tmpNode; 18448 18449 // Convert text node into index if possible 18450 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18451 // Get the parent container location and walk from there 18452 offset = nodeIndex(container); 18453 container = container.parentNode; 18454 isAtEndOfText = true; 18455 } 18456 18457 // Move startContainer/startOffset in to a suitable node 18458 if (container.nodeType == 1) { 18459 nodes = container.childNodes; 18460 container = nodes[Math.min(offset, nodes.length - 1)]; 18461 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18462 18463 // If offset is at end of the parent node walk to the next one 18464 if (offset > nodes.length - 1 || isAtEndOfText) 18465 walker.next(); 18466 18467 for (node = walker.current(); node; node = walker.next()) { 18468 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18469 // IE has a "neat" feature where it moves the start node into the closest element 18470 // we can avoid this by inserting an element before it and then remove it after we set the selection 18471 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18472 node.parentNode.insertBefore(tmpNode, node); 18473 18474 // Set selection and remove tmpNode 18475 rng.setStart(node, 0); 18476 selection.setRng(rng); 18477 dom.remove(tmpNode); 18478 18479 return; 18480 } 18481 } 18482 } 18483 }; 18484 }; 18485 })(tinymce); 18486 tinymce.onAddEditor.add(function(tinymce, ed) { 18487 var filters, fontSizes, dom, settings = ed.settings; 18488 18489 function replaceWithSpan(node, styles) { 18490 tinymce.each(styles, function(value, name) { 18491 if (value) 18492 dom.setStyle(node, name, value); 18493 }); 18494 18495 dom.rename(node, 'span'); 18496 }; 18497 18498 function convert(editor, params) { 18499 dom = editor.dom; 18500 18501 if (settings.convert_fonts_to_spans) { 18502 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18503 filters[node.nodeName.toLowerCase()](ed.dom, node); 18504 }); 18505 } 18506 }; 18507 18508 if (settings.inline_styles) { 18509 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18510 18511 filters = { 18512 font : function(dom, node) { 18513 replaceWithSpan(node, { 18514 backgroundColor : node.style.backgroundColor, 18515 color : node.color, 18516 fontFamily : node.face, 18517 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18518 }); 18519 }, 18520 18521 u : function(dom, node) { 18522 replaceWithSpan(node, { 18523 textDecoration : 'underline' 18524 }); 18525 }, 18526 18527 strike : function(dom, node) { 18528 replaceWithSpan(node, { 18529 textDecoration : 'line-through' 18530 }); 18531 } 18532 }; 18533 18534 ed.onPreProcess.add(convert); 18535 ed.onSetContent.add(convert); 18536 18537 ed.onInit.add(function() { 18538 ed.selection.onSetContent.add(convert); 18539 }); 18540 } 18541 }); 18542 (function(tinymce) { 18543 var TreeWalker = tinymce.dom.TreeWalker; 18544 18545 tinymce.EnterKey = function(editor) { 18546 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18547 18548 function handleEnterKey(evt) { 18549 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 18550 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18551 18552 // Returns true if the block can be split into two blocks or not 18553 function canSplitBlock(node) { 18554 return node && 18555 dom.isBlock(node) && 18556 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18557 !/^(fixed|absolute)/i.test(node.style.position) && 18558 dom.getContentEditable(node) !== "true"; 18559 }; 18560 18561 // Renders empty block on IE 18562 function renderBlockOnIE(block) { 18563 var oldRng; 18564 18565 if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) { 18566 oldRng = selection.getRng(); 18567 block.appendChild(dom.create('span', null, '\u00a0')); 18568 selection.select(block); 18569 block.lastChild.outerHTML = ''; 18570 selection.setRng(oldRng); 18571 } 18572 }; 18573 18574 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18575 function trimInlineElementsOnLeftSideOfBlock(block) { 18576 var node = block, firstChilds = [], i; 18577 18578 // Find inner most first child ex: <p><i><b>*</b></i></p> 18579 while (node = node.firstChild) { 18580 if (dom.isBlock(node)) { 18581 return; 18582 } 18583 18584 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18585 firstChilds.push(node); 18586 } 18587 } 18588 18589 i = firstChilds.length; 18590 while (i--) { 18591 node = firstChilds[i]; 18592 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18593 dom.remove(node); 18594 } else { 18595 // Remove <a> </a> see #5381 18596 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 18597 dom.remove(node); 18598 } 18599 } 18600 } 18601 }; 18602 18603 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18604 function moveToCaretPosition(root) { 18605 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18606 18607 rng = dom.createRng(); 18608 18609 if (root.hasChildNodes()) { 18610 walker = new TreeWalker(root, root); 18611 18612 while (node = walker.current()) { 18613 if (node.nodeType == 3) { 18614 rng.setStart(node, 0); 18615 rng.setEnd(node, 0); 18616 break; 18617 } 18618 18619 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18620 rng.setStartBefore(node); 18621 rng.setEndBefore(node); 18622 break; 18623 } 18624 18625 lastNode = node; 18626 node = walker.next(); 18627 } 18628 18629 if (!node) { 18630 rng.setStart(lastNode, 0); 18631 rng.setEnd(lastNode, 0); 18632 } 18633 } else { 18634 if (root.nodeName == 'BR') { 18635 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18636 // Trick on older IE versions to render the caret before the BR between two lists 18637 if (!documentMode || documentMode < 9) { 18638 tempElm = dom.create('br'); 18639 root.parentNode.insertBefore(tempElm, root); 18640 } 18641 18642 rng.setStartBefore(root); 18643 rng.setEndBefore(root); 18644 } else { 18645 rng.setStartAfter(root); 18646 rng.setEndAfter(root); 18647 } 18648 } else { 18649 rng.setStart(root, 0); 18650 rng.setEnd(root, 0); 18651 } 18652 } 18653 18654 selection.setRng(rng); 18655 18656 // Remove tempElm created for old IE:s 18657 dom.remove(tempElm); 18658 18659 viewPort = dom.getViewPort(editor.getWin()); 18660 18661 // 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 18662 y = dom.getPos(root).y; 18663 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18664 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18665 } 18666 }; 18667 18668 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18669 // This function will also copy any text formatting from the parent block and add it to the new one 18670 function createNewBlock(name) { 18671 var node = container, block, clonedNode, caretNode; 18672 18673 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18674 caretNode = block; 18675 18676 // Clone any parent styles 18677 if (settings.keep_styles !== false) { 18678 do { 18679 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18680 // Never clone a caret containers 18681 if (node.id == '_mce_caret') { 18682 continue; 18683 } 18684 18685 clonedNode = node.cloneNode(false); 18686 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18687 18688 if (block.hasChildNodes()) { 18689 clonedNode.appendChild(block.firstChild); 18690 block.appendChild(clonedNode); 18691 } else { 18692 caretNode = clonedNode; 18693 block.appendChild(clonedNode); 18694 } 18695 } 18696 } while (node = node.parentNode); 18697 } 18698 18699 // BR is needed in empty blocks on non IE browsers 18700 if (!tinymce.isIE || tinymce.isIE11) { 18701 caretNode.innerHTML = '<br data-mce-bogus="1">'; 18702 } 18703 18704 return block; 18705 }; 18706 18707 // Returns true/false if the caret is at the start/end of the parent block element 18708 function isCaretAtStartOrEndOfBlock(start) { 18709 var walker, node, name; 18710 18711 // Caret is in the middle of a text node like "a|b" 18712 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18713 return false; 18714 } 18715 18716 // If after the last element in block node edge case for #5091 18717 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18718 return true; 18719 } 18720 18721 // If the caret if before the first element in parentBlock 18722 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18723 return true; 18724 } 18725 18726 // Caret can be before/after a table 18727 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18728 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18729 } 18730 18731 // Walk the DOM and look for text nodes or non empty elements 18732 walker = new TreeWalker(container, parentBlock); 18733 18734 // If caret is in beginning or end of a text block then jump to the next/previous node 18735 if (container.nodeType == 3) { 18736 if (start && offset == 0) { 18737 walker.prev(); 18738 } else if (!start && offset == container.nodeValue.length) { 18739 walker.next(); 18740 } 18741 } 18742 18743 while (node = walker.current()) { 18744 if (node.nodeType === 1) { 18745 // Ignore bogus elements 18746 if (!node.getAttribute('data-mce-bogus')) { 18747 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18748 name = node.nodeName.toLowerCase(); 18749 if (nonEmptyElementsMap[name] && name !== 'br') { 18750 return false; 18751 } 18752 } 18753 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18754 return false; 18755 } 18756 18757 if (start) { 18758 walker.prev(); 18759 } else { 18760 walker.next(); 18761 } 18762 } 18763 18764 return true; 18765 }; 18766 18767 // Wraps any text nodes or inline elements in the specified forced root block name 18768 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18769 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18770 18771 // Not in a block element or in a table cell or caption 18772 parentBlock = dom.getParent(container, dom.isBlock); 18773 if (!parentBlock || !canSplitBlock(parentBlock)) { 18774 parentBlock = parentBlock || editableRoot; 18775 18776 if (!parentBlock.hasChildNodes()) { 18777 newBlock = dom.create(blockName); 18778 parentBlock.appendChild(newBlock); 18779 rng.setStart(newBlock, 0); 18780 rng.setEnd(newBlock, 0); 18781 return newBlock; 18782 } 18783 18784 // Find parent that is the first child of parentBlock 18785 node = container; 18786 while (node.parentNode != parentBlock) { 18787 node = node.parentNode; 18788 } 18789 18790 // Loop left to find start node start wrapping at 18791 while (node && !dom.isBlock(node)) { 18792 startNode = node; 18793 node = node.previousSibling; 18794 } 18795 18796 if (startNode) { 18797 newBlock = dom.create(blockName); 18798 startNode.parentNode.insertBefore(newBlock, startNode); 18799 18800 // Start wrapping until we hit a block 18801 node = startNode; 18802 while (node && !dom.isBlock(node)) { 18803 next = node.nextSibling; 18804 newBlock.appendChild(node); 18805 node = next; 18806 } 18807 18808 // Restore range to it's past location 18809 rng.setStart(container, offset); 18810 rng.setEnd(container, offset); 18811 } 18812 } 18813 18814 return container; 18815 }; 18816 18817 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18818 function handleEmptyListItem() { 18819 function isFirstOrLastLi(first) { 18820 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18821 18822 // Find first/last element since there might be whitespace there 18823 while (node) { 18824 if (node.nodeType == 1) { 18825 break; 18826 } 18827 18828 node = node[first ? 'nextSibling' : 'previousSibling']; 18829 } 18830 18831 return node === parentBlock; 18832 }; 18833 18834 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18835 18836 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18837 // Is first and last list item then replace the OL/UL with a text block 18838 dom.replace(newBlock, containerBlock); 18839 } else if (isFirstOrLastLi(true)) { 18840 // First LI in list then remove LI and add text block before list 18841 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18842 } else if (isFirstOrLastLi()) { 18843 // Last LI in list then temove LI and add text block after list 18844 dom.insertAfter(newBlock, containerBlock); 18845 renderBlockOnIE(newBlock); 18846 } else { 18847 // Middle LI in list the split the list and insert a text block in the middle 18848 // Extract after fragment and insert it after the current block 18849 tmpRng = rng.cloneRange(); 18850 tmpRng.setStartAfter(parentBlock); 18851 tmpRng.setEndAfter(containerBlock); 18852 fragment = tmpRng.extractContents(); 18853 dom.insertAfter(fragment, containerBlock); 18854 dom.insertAfter(newBlock, containerBlock); 18855 } 18856 18857 dom.remove(parentBlock); 18858 moveToCaretPosition(newBlock); 18859 undoManager.add(); 18860 }; 18861 18862 // Walks the parent block to the right and look for any contents 18863 function hasRightSideContent() { 18864 var walker = new TreeWalker(container, parentBlock), node; 18865 18866 while (node = walker.next()) { 18867 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 18868 return true; 18869 } 18870 } 18871 } 18872 18873 // Inserts a BR element if the forced_root_block option is set to false or empty string 18874 function insertBr() { 18875 var brElm, extraBr, marker; 18876 18877 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18878 // Insert extra BR element at the end block elements 18879 if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) { 18880 brElm = dom.create('br'); 18881 rng.insertNode(brElm); 18882 rng.setStartAfter(brElm); 18883 rng.setEndAfter(brElm); 18884 extraBr = true; 18885 } 18886 } 18887 18888 brElm = dom.create('br'); 18889 rng.insertNode(brElm); 18890 18891 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18892 if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18893 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18894 } 18895 18896 // Insert temp marker and scroll to that 18897 marker = dom.create('span', {}, ' '); 18898 brElm.parentNode.insertBefore(marker, brElm); 18899 selection.scrollIntoView(marker); 18900 dom.remove(marker); 18901 18902 if (!extraBr) { 18903 rng.setStartAfter(brElm); 18904 rng.setEndAfter(brElm); 18905 } else { 18906 rng.setStartBefore(brElm); 18907 rng.setEndBefore(brElm); 18908 } 18909 18910 selection.setRng(rng); 18911 undoManager.add(); 18912 }; 18913 18914 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18915 function trimLeadingLineBreaks(node) { 18916 do { 18917 if (node.nodeType === 3) { 18918 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18919 } 18920 18921 node = node.firstChild; 18922 } while (node); 18923 }; 18924 18925 function getEditableRoot(node) { 18926 var root = dom.getRoot(), parent, editableRoot; 18927 18928 // Get all parents until we hit a non editable parent or the root 18929 parent = node; 18930 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18931 if (dom.getContentEditable(parent) === "true") { 18932 editableRoot = parent; 18933 } 18934 18935 parent = parent.parentNode; 18936 } 18937 18938 return parent !== root ? editableRoot : root; 18939 }; 18940 18941 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18942 function addBrToBlockIfNeeded(block) { 18943 var lastChild; 18944 18945 // IE will render the blocks correctly other browsers needs a BR 18946 if (!tinymce.isIE || tinymce.isIE11) { 18947 block.normalize(); // Remove empty text nodes that got left behind by the extract 18948 18949 // Check if the block is empty or contains a floated last child 18950 lastChild = block.lastChild; 18951 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18952 dom.add(block, 'br'); 18953 } 18954 } 18955 }; 18956 18957 // Delete any selected contents 18958 if (!rng.collapsed) { 18959 editor.execCommand('Delete'); 18960 return; 18961 } 18962 18963 // Event is blocked by some other handler for example the lists plugin 18964 if (evt.isDefaultPrevented()) { 18965 return; 18966 } 18967 18968 // Setup range items and newBlockName 18969 container = rng.startContainer; 18970 offset = rng.startOffset; 18971 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 18972 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18973 documentMode = dom.doc.documentMode; 18974 shiftKey = evt.shiftKey; 18975 18976 // Resolve node index 18977 if (container.nodeType == 1 && container.hasChildNodes()) { 18978 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18979 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18980 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18981 offset = container.nodeValue.length; 18982 } else { 18983 offset = 0; 18984 } 18985 } 18986 18987 // Get editable root node normaly the body element but sometimes a div or span 18988 editableRoot = getEditableRoot(container); 18989 18990 // If there is no editable root then enter is done inside a contentEditable false element 18991 if (!editableRoot) { 18992 return; 18993 } 18994 18995 undoManager.beforeChange(); 18996 18997 // If editable root isn't block nor the root of the editor 18998 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18999 if (!newBlockName || shiftKey) { 19000 insertBr(); 19001 } 19002 19003 return; 19004 } 19005 19006 // Wrap the current node and it's sibling in a default block if it's needed. 19007 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 19008 // This won't happen if root blocks are disabled or the shiftKey is pressed 19009 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 19010 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 19011 } 19012 19013 // Find parent block and setup empty block paddings 19014 parentBlock = dom.getParent(container, dom.isBlock); 19015 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 19016 19017 // Setup block names 19018 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 19019 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 19020 19021 // Enter inside block contained within a LI then split or insert before/after LI 19022 if (containerBlockName == 'LI' && !evt.ctrlKey) { 19023 parentBlock = containerBlock; 19024 parentBlockName = containerBlockName; 19025 } 19026 19027 // Handle enter in LI 19028 if (parentBlockName == 'LI') { 19029 if (!newBlockName && shiftKey) { 19030 insertBr(); 19031 return; 19032 } 19033 19034 // Handle enter inside an empty list item 19035 if (dom.isEmpty(parentBlock)) { 19036 // Let the list plugin or browser handle nested lists for now 19037 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 19038 return false; 19039 } 19040 19041 handleEmptyListItem(); 19042 return; 19043 } 19044 } 19045 19046 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 19047 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 19048 if (!shiftKey) { 19049 insertBr(); 19050 return; 19051 } 19052 } else { 19053 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 19054 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 19055 insertBr(); 19056 return; 19057 } 19058 } 19059 19060 // Default block name if it's not configured 19061 newBlockName = newBlockName || 'P'; 19062 19063 // Insert new block before/after the parent block depending on caret location 19064 if (isCaretAtStartOrEndOfBlock()) { 19065 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 19066 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 19067 newBlock = createNewBlock(newBlockName); 19068 } else { 19069 newBlock = createNewBlock(); 19070 } 19071 19072 // Split the current container block element if enter is pressed inside an empty inner block element 19073 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 19074 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 19075 newBlock = dom.split(containerBlock, parentBlock); 19076 } else { 19077 dom.insertAfter(newBlock, parentBlock); 19078 } 19079 19080 moveToCaretPosition(newBlock); 19081 } else if (isCaretAtStartOrEndOfBlock(true)) { 19082 // Insert new block before 19083 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 19084 renderBlockOnIE(newBlock); 19085 } else { 19086 // Extract after fragment and insert it after the current block 19087 tmpRng = rng.cloneRange(); 19088 tmpRng.setEndAfter(parentBlock); 19089 fragment = tmpRng.extractContents(); 19090 trimLeadingLineBreaks(fragment); 19091 newBlock = fragment.firstChild; 19092 dom.insertAfter(fragment, parentBlock); 19093 trimInlineElementsOnLeftSideOfBlock(newBlock); 19094 addBrToBlockIfNeeded(parentBlock); 19095 moveToCaretPosition(newBlock); 19096 } 19097 19098 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 19099 undoManager.add(); 19100 } 19101 19102 editor.onKeyDown.add(function(ed, evt) { 19103 if (evt.keyCode == 13) { 19104 if (handleEnterKey(evt) !== false) { 19105 evt.preventDefault(); 19106 } 19107 } 19108 }); 19109 }; 19110 })(tinymce);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |