[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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.10', 10 11 releaseDate : '2013-10-24', 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 // All browsers 1949 disableBackspaceIntoATable(); 1950 removeBlockQuoteOnBackSpace(); 1951 emptyEditorWhenDeleting(); 1952 1953 // WebKit 1954 if (tinymce.isWebKit) { 1955 keepInlineElementOnDeleteBackspace(); 1956 cleanupStylesWhenDeleting(); 1957 inputMethodFocus(); 1958 selectControlElements(); 1959 setDefaultBlockType(); 1960 1961 // iOS 1962 if (tinymce.isIDevice) { 1963 selectionChangeNodeChanged(); 1964 } else { 1965 fakeImageResize(); 1966 selectAll(); 1967 } 1968 } 1969 1970 // IE 1971 if (tinymce.isIE && !tinymce.isIE11) { 1972 removeHrOnBackspace(); 1973 ensureBodyHasRoleApplication(); 1974 addNewLinesBeforeBrInPre(); 1975 removePreSerializedStylesWhenSelectingControls(); 1976 deleteControlItemOnBackSpace(); 1977 renderEmptyBlocksFix(); 1978 keepNoScriptContents(); 1979 } 1980 1981 // IE 11+ 1982 if (tinymce.isIE11) { 1983 bodyHeight(); 1984 } 1985 1986 // Gecko 1987 if (tinymce.isGecko && !tinymce.isIE11) { 1988 removeHrOnBackspace(); 1989 focusBody(); 1990 removeStylesWhenDeletingAccrossBlockElements(); 1991 setGeckoEditingOptions(); 1992 addBrAfterLastLinks(); 1993 removeGhostSelection(); 1994 } 1995 1996 // Opera 1997 if (tinymce.isOpera) { 1998 fakeImageResize(); 1999 } 2000 }; 2001 (function(tinymce) { 2002 var namedEntities, baseEntities, reverseEntities, 2003 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2004 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2005 rawCharsRegExp = /[<>&\"\']/g, 2006 entityRegExp = /&(#x|#)?([\w]+);/g, 2007 asciiMap = { 2008 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 2009 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 2010 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 2011 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 2012 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 2013 }; 2014 2015 // Raw entities 2016 baseEntities = { 2017 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 2018 "'" : ''', 2019 '<' : '<', 2020 '>' : '>', 2021 '&' : '&' 2022 }; 2023 2024 // Reverse lookup table for raw entities 2025 reverseEntities = { 2026 '<' : '<', 2027 '>' : '>', 2028 '&' : '&', 2029 '"' : '"', 2030 ''' : "'" 2031 }; 2032 2033 // Decodes text by using the browser 2034 function nativeDecode(text) { 2035 var elm; 2036 2037 elm = document.createElement("div"); 2038 elm.innerHTML = text; 2039 2040 return elm.textContent || elm.innerText || text; 2041 }; 2042 2043 // Build a two way lookup table for the entities 2044 function buildEntitiesLookup(items, radix) { 2045 var i, chr, entity, lookup = {}; 2046 2047 if (items) { 2048 items = items.split(','); 2049 radix = radix || 10; 2050 2051 // Build entities lookup table 2052 for (i = 0; i < items.length; i += 2) { 2053 chr = String.fromCharCode(parseInt(items[i], radix)); 2054 2055 // Only add non base entities 2056 if (!baseEntities[chr]) { 2057 entity = '&' + items[i + 1] + ';'; 2058 lookup[chr] = entity; 2059 lookup[entity] = chr; 2060 } 2061 } 2062 2063 return lookup; 2064 } 2065 }; 2066 2067 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 2068 namedEntities = buildEntitiesLookup( 2069 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 2070 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 2071 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 2072 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 2073 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 2074 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 2075 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 2076 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 2077 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 2078 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 2079 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 2080 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 2081 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 2082 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 2083 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 2084 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 2085 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 2086 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 2087 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 2088 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 2089 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 2090 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 2091 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 2092 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 2093 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 2094 2095 tinymce.html = tinymce.html || {}; 2096 2097 tinymce.html.Entities = { 2098 encodeRaw : function(text, attr) { 2099 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2100 return baseEntities[chr] || chr; 2101 }); 2102 }, 2103 2104 encodeAllRaw : function(text) { 2105 return ('' + text).replace(rawCharsRegExp, function(chr) { 2106 return baseEntities[chr] || chr; 2107 }); 2108 }, 2109 2110 encodeNumeric : function(text, attr) { 2111 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2112 // Multi byte sequence convert it to a single entity 2113 if (chr.length > 1) 2114 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 2115 2116 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 2117 }); 2118 }, 2119 2120 encodeNamed : function(text, attr, entities) { 2121 entities = entities || namedEntities; 2122 2123 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2124 return baseEntities[chr] || entities[chr] || chr; 2125 }); 2126 }, 2127 2128 getEncodeFunc : function(name, entities) { 2129 var Entities = tinymce.html.Entities; 2130 2131 entities = buildEntitiesLookup(entities) || namedEntities; 2132 2133 function encodeNamedAndNumeric(text, attr) { 2134 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2135 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 2136 }); 2137 }; 2138 2139 function encodeCustomNamed(text, attr) { 2140 return Entities.encodeNamed(text, attr, entities); 2141 }; 2142 2143 // Replace + with , to be compatible with previous TinyMCE versions 2144 name = tinymce.makeMap(name.replace(/\+/g, ',')); 2145 2146 // Named and numeric encoder 2147 if (name.named && name.numeric) 2148 return encodeNamedAndNumeric; 2149 2150 // Named encoder 2151 if (name.named) { 2152 // Custom names 2153 if (entities) 2154 return encodeCustomNamed; 2155 2156 return Entities.encodeNamed; 2157 } 2158 2159 // Numeric 2160 if (name.numeric) 2161 return Entities.encodeNumeric; 2162 2163 // Raw encoder 2164 return Entities.encodeRaw; 2165 }, 2166 2167 decode : function(text) { 2168 return text.replace(entityRegExp, function(all, numeric, value) { 2169 if (numeric) { 2170 value = parseInt(value, numeric.length === 2 ? 16 : 10); 2171 2172 // Support upper UTF 2173 if (value > 0xFFFF) { 2174 value -= 0x10000; 2175 2176 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 2177 } else 2178 return asciiMap[value] || String.fromCharCode(value); 2179 } 2180 2181 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 2182 }); 2183 } 2184 }; 2185 })(tinymce); 2186 tinymce.html.Styles = function(settings, schema) { 2187 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 2188 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 2189 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 2190 trimRightRegExp = /\s+$/, 2191 urlColorRegExp = /rgb/, 2192 undef, i, encodingLookup = {}, encodingItems; 2193 2194 settings = settings || {}; 2195 2196 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 2197 for (i = 0; i < encodingItems.length; i++) { 2198 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 2199 encodingLookup['\uFEFF' + i] = encodingItems[i]; 2200 } 2201 2202 function toHex(match, r, g, b) { 2203 function hex(val) { 2204 val = parseInt(val).toString(16); 2205 2206 return val.length > 1 ? val : '0' + val; // 0 -> 00 2207 }; 2208 2209 return '#' + hex(r) + hex(g) + hex(b); 2210 }; 2211 2212 return { 2213 toHex : function(color) { 2214 return color.replace(rgbRegExp, toHex); 2215 }, 2216 2217 parse : function(css) { 2218 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 2219 2220 function compress(prefix, suffix) { 2221 var top, right, bottom, left; 2222 2223 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 2224 // So lets asume it shouldn't be there 2225 if (styles['border-image'] === 'none') { 2226 delete styles['border-image']; 2227 } 2228 2229 // Get values and check it it needs compressing 2230 top = styles[prefix + '-top' + suffix]; 2231 if (!top) 2232 return; 2233 2234 right = styles[prefix + '-right' + suffix]; 2235 if (top != right) 2236 return; 2237 2238 bottom = styles[prefix + '-bottom' + suffix]; 2239 if (right != bottom) 2240 return; 2241 2242 left = styles[prefix + '-left' + suffix]; 2243 if (bottom != left) 2244 return; 2245 2246 // Compress 2247 styles[prefix + suffix] = left; 2248 delete styles[prefix + '-top' + suffix]; 2249 delete styles[prefix + '-right' + suffix]; 2250 delete styles[prefix + '-bottom' + suffix]; 2251 delete styles[prefix + '-left' + suffix]; 2252 }; 2253 2254 function canCompress(key) { 2255 var value = styles[key], i; 2256 2257 if (!value || value.indexOf(' ') < 0) 2258 return; 2259 2260 value = value.split(' '); 2261 i = value.length; 2262 while (i--) { 2263 if (value[i] !== value[0]) 2264 return false; 2265 } 2266 2267 styles[key] = value[0]; 2268 2269 return true; 2270 }; 2271 2272 function compress2(target, a, b, c) { 2273 if (!canCompress(a)) 2274 return; 2275 2276 if (!canCompress(b)) 2277 return; 2278 2279 if (!canCompress(c)) 2280 return; 2281 2282 // Compress 2283 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2284 delete styles[a]; 2285 delete styles[b]; 2286 delete styles[c]; 2287 }; 2288 2289 // Encodes the specified string by replacing all \" \' ; : with _<num> 2290 function encode(str) { 2291 isEncoded = true; 2292 2293 return encodingLookup[str]; 2294 }; 2295 2296 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2297 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2298 function decode(str, keep_slashes) { 2299 if (isEncoded) { 2300 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2301 return encodingLookup[str]; 2302 }); 2303 } 2304 2305 if (!keep_slashes) 2306 str = str.replace(/\\([\'\";:])/g, "$1"); 2307 2308 return str; 2309 }; 2310 2311 function processUrl(match, url, url2, url3, str, str2) { 2312 str = str || str2; 2313 2314 if (str) { 2315 str = decode(str); 2316 2317 // Force strings into single quote format 2318 return "'" + str.replace(/\'/g, "\\'") + "'"; 2319 } 2320 2321 url = decode(url || url2 || url3); 2322 2323 // Convert the URL to relative/absolute depending on config 2324 if (urlConverter) 2325 url = urlConverter.call(urlConverterScope, url, 'style'); 2326 2327 // Output new URL format 2328 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2329 }; 2330 2331 if (css) { 2332 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2333 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2334 return str.replace(/[;:]/g, encode); 2335 }); 2336 2337 // Parse styles 2338 while (matches = styleRegExp.exec(css)) { 2339 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2340 value = matches[2].replace(trimRightRegExp, ''); 2341 2342 if (name && value.length > 0) { 2343 // Opera will produce 700 instead of bold in their style values 2344 if (name === 'font-weight' && value === '700') 2345 value = 'bold'; 2346 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2347 value = value.toLowerCase(); 2348 2349 // Convert RGB colors to HEX 2350 value = value.replace(rgbRegExp, toHex); 2351 2352 // Convert URLs and force them into url('value') format 2353 value = value.replace(urlOrStrRegExp, processUrl); 2354 styles[name] = isEncoded ? decode(value, true) : value; 2355 } 2356 2357 styleRegExp.lastIndex = matches.index + matches[0].length; 2358 } 2359 2360 // Compress the styles to reduce it's size for example IE will expand styles 2361 compress("border", ""); 2362 compress("border", "-width"); 2363 compress("border", "-color"); 2364 compress("border", "-style"); 2365 compress("padding", ""); 2366 compress("margin", ""); 2367 compress2('border', 'border-width', 'border-style', 'border-color'); 2368 2369 // Remove pointless border, IE produces these 2370 if (styles.border === 'medium none') 2371 delete styles.border; 2372 } 2373 2374 return styles; 2375 }, 2376 2377 serialize : function(styles, element_name) { 2378 var css = '', name, value; 2379 2380 function serializeStyles(name) { 2381 var styleList, i, l, value; 2382 2383 styleList = schema.styles[name]; 2384 if (styleList) { 2385 for (i = 0, l = styleList.length; i < l; i++) { 2386 name = styleList[i]; 2387 value = styles[name]; 2388 2389 if (value !== undef && value.length > 0) 2390 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2391 } 2392 } 2393 }; 2394 2395 // Serialize styles according to schema 2396 if (element_name && schema && schema.styles) { 2397 // Serialize global styles and element specific styles 2398 serializeStyles('*'); 2399 serializeStyles(element_name); 2400 } else { 2401 // Output the styles in the order they are inside the object 2402 for (name in styles) { 2403 value = styles[name]; 2404 2405 if (value !== undef && value.length > 0) 2406 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2407 } 2408 } 2409 2410 return css; 2411 } 2412 }; 2413 }; 2414 (function(tinymce) { 2415 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2416 2417 function split(str, delim) { 2418 return str.split(delim || ','); 2419 }; 2420 2421 function unpack(lookup, data) { 2422 var key, elements = {}; 2423 2424 function replace(value) { 2425 return value.replace(/[A-Z]+/g, function(key) { 2426 return replace(lookup[key]); 2427 }); 2428 }; 2429 2430 // Unpack lookup 2431 for (key in lookup) { 2432 if (lookup.hasOwnProperty(key)) 2433 lookup[key] = replace(lookup[key]); 2434 } 2435 2436 // Unpack and parse data into object map 2437 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2438 attributes = split(attributes, '|'); 2439 2440 elements[name] = { 2441 attributes : makeMap(attributes), 2442 attributesOrder : attributes, 2443 children : makeMap(children, '|', {'#comment' : {}}) 2444 } 2445 }); 2446 2447 return elements; 2448 }; 2449 2450 function getHTML5() { 2451 var html5 = mapCache.html5; 2452 2453 if (!html5) { 2454 html5 = mapCache.html5 = unpack({ 2455 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2456 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|' + 2457 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2458 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|' + 2459 '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|' + 2460 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2461 }, 'html[A|manifest][body|head]' + 2462 'head[A][base|command|link|meta|noscript|script|style|title]' + 2463 'title[A][#]' + 2464 'base[A|href|target][]' + 2465 'link[A|href|rel|media|type|sizes][]' + 2466 'meta[A|http-equiv|name|content|charset][]' + 2467 'style[A|type|media|scoped][#]' + 2468 'script[A|charset|type|src|defer|async][#]' + 2469 'noscript[A][C]' + 2470 'body[A][C]' + 2471 'section[A][C]' + 2472 'nav[A][C]' + 2473 'article[A][C]' + 2474 'aside[A][C]' + 2475 'h1[A][B]' + 2476 'h2[A][B]' + 2477 'h3[A][B]' + 2478 'h4[A][B]' + 2479 'h5[A][B]' + 2480 'h6[A][B]' + 2481 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2482 'header[A][C]' + 2483 'footer[A][C]' + 2484 'address[A][C]' + 2485 'p[A][B]' + 2486 'br[A][]' + 2487 'pre[A][B]' + 2488 'dialog[A][dd|dt]' + 2489 'blockquote[A|cite][C]' + 2490 'ol[A|start|reversed][li]' + 2491 'ul[A][li]' + 2492 'li[A|value][C]' + 2493 'dl[A][dd|dt]' + 2494 'dt[A][B]' + 2495 'dd[A][C]' + 2496 'a[A|href|target|ping|rel|media|type][B]' + 2497 'em[A][B]' + 2498 'strong[A][B]' + 2499 'small[A][B]' + 2500 'cite[A][B]' + 2501 'q[A|cite][B]' + 2502 'dfn[A][B]' + 2503 'abbr[A][B]' + 2504 'code[A][B]' + 2505 'var[A][B]' + 2506 'samp[A][B]' + 2507 'kbd[A][B]' + 2508 'sub[A][B]' + 2509 'sup[A][B]' + 2510 'i[A][B]' + 2511 'b[A][B]' + 2512 'mark[A][B]' + 2513 'progress[A|value|max][B]' + 2514 'meter[A|value|min|max|low|high|optimum][B]' + 2515 'time[A|datetime][B]' + 2516 'ruby[A][B|rt|rp]' + 2517 'rt[A][B]' + 2518 'rp[A][B]' + 2519 'bdo[A][B]' + 2520 'span[A][B]' + 2521 'ins[A|cite|datetime][B]' + 2522 'del[A|cite|datetime][B]' + 2523 'figure[A][C|legend|figcaption]' + 2524 'figcaption[A][C]' + 2525 'img[A|alt|src|height|width|usemap|ismap][]' + 2526 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2527 'embed[A|src|height|width|type][]' + 2528 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2529 'param[A|name|value][]' + 2530 'details[A|open][C|legend]' + 2531 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2532 'menu[A|type|label][C|li]' + 2533 'legend[A][C|B]' + 2534 'div[A][C]' + 2535 'source[A|src|type|media][]' + 2536 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2537 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2538 'hr[A][]' + 2539 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2540 'fieldset[A|disabled|form|name][C|legend]' + 2541 'label[A|form|for][B]' + 2542 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2543 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2544 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2545 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2546 'datalist[A][B|option]' + 2547 'optgroup[A|disabled|label][option]' + 2548 'option[A|disabled|selected|label|value][]' + 2549 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2550 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2551 'output[A|for|form|name][B]' + 2552 'canvas[A|width|height][]' + 2553 'map[A|name][B|C]' + 2554 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2555 'mathml[A][]' + 2556 'svg[A][]' + 2557 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2558 'caption[A][C]' + 2559 'colgroup[A|span][col]' + 2560 'col[A|span][]' + 2561 'thead[A][tr]' + 2562 'tfoot[A][tr]' + 2563 'tbody[A][tr]' + 2564 'tr[A][th|td]' + 2565 'th[A|headers|rowspan|colspan|scope][B]' + 2566 'td[A|headers|rowspan|colspan][C]' + 2567 'wbr[A][]' 2568 ); 2569 } 2570 2571 return html5; 2572 }; 2573 2574 function getHTML4() { 2575 var html4 = mapCache.html4; 2576 2577 if (!html4) { 2578 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2579 html4 = mapCache.html4 = unpack({ 2580 Z : 'H|K|N|O|P', 2581 Y : 'X|form|R|Q', 2582 ZG : 'E|span|width|align|char|charoff|valign', 2583 X : 'p|T|div|U|W|isindex|fieldset|table', 2584 ZF : 'E|align|char|charoff|valign', 2585 W : 'pre|hr|blockquote|address|center|noframes', 2586 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2587 ZD : '[E][S]', 2588 U : 'ul|ol|dl|menu|dir', 2589 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2590 T : 'h1|h2|h3|h4|h5|h6', 2591 ZB : 'X|S|Q', 2592 S : 'R|P', 2593 ZA : 'a|G|J|M|O|P', 2594 R : 'a|H|K|N|O', 2595 Q : 'noscript|P', 2596 P : 'ins|del|script', 2597 O : 'input|select|textarea|label|button', 2598 N : 'M|L', 2599 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2600 L : 'sub|sup', 2601 K : 'J|I', 2602 J : 'tt|i|b|u|s|strike', 2603 I : 'big|small|font|basefont', 2604 H : 'G|F', 2605 G : 'br|span|bdo', 2606 F : 'object|applet|img|map|iframe', 2607 E : 'A|B|C', 2608 D : 'accesskey|tabindex|onfocus|onblur', 2609 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2610 B : 'lang|xml:lang|dir', 2611 A : 'id|class|style|title' 2612 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2613 'style[B|id|type|media|title|xml:space][]' + 2614 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2615 'param[id|name|value|valuetype|type][]' + 2616 'p[E|align][#|S]' + 2617 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2618 'br[A|clear][]' + 2619 'span[E][#|S]' + 2620 'bdo[A|C|B][#|S]' + 2621 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2622 'h1[E|align][#|S]' + 2623 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2624 'map[B|C|A|name][X|form|Q|area]' + 2625 'h2[E|align][#|S]' + 2626 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2627 'h3[E|align][#|S]' + 2628 'tt[E][#|S]' + 2629 'i[E][#|S]' + 2630 'b[E][#|S]' + 2631 'u[E][#|S]' + 2632 's[E][#|S]' + 2633 'strike[E][#|S]' + 2634 'big[E][#|S]' + 2635 'small[E][#|S]' + 2636 'font[A|B|size|color|face][#|S]' + 2637 'basefont[id|size|color|face][]' + 2638 'em[E][#|S]' + 2639 'strong[E][#|S]' + 2640 'dfn[E][#|S]' + 2641 'code[E][#|S]' + 2642 'q[E|cite][#|S]' + 2643 'samp[E][#|S]' + 2644 'kbd[E][#|S]' + 2645 'var[E][#|S]' + 2646 'cite[E][#|S]' + 2647 'abbr[E][#|S]' + 2648 'acronym[E][#|S]' + 2649 'sub[E][#|S]' + 2650 'sup[E][#|S]' + 2651 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2652 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2653 'optgroup[E|disabled|label][option]' + 2654 'option[E|selected|disabled|label|value][]' + 2655 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2656 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2657 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2658 'h4[E|align][#|S]' + 2659 'ins[E|cite|datetime][#|Y]' + 2660 'h5[E|align][#|S]' + 2661 'del[E|cite|datetime][#|Y]' + 2662 'h6[E|align][#|S]' + 2663 'div[E|align][#|Y]' + 2664 'ul[E|type|compact][li]' + 2665 'li[E|type|value][#|Y]' + 2666 'ol[E|type|compact|start][li]' + 2667 'dl[E|compact][dt|dd]' + 2668 'dt[E][#|S]' + 2669 'dd[E][#|Y]' + 2670 'menu[E|compact][li]' + 2671 'dir[E|compact][li]' + 2672 'pre[E|width|xml:space][#|ZA]' + 2673 'hr[E|align|noshade|size|width][]' + 2674 'blockquote[E|cite][#|Y]' + 2675 'address[E][#|S|p]' + 2676 'center[E][#|Y]' + 2677 'noframes[E][#|Y]' + 2678 'isindex[A|B|prompt][]' + 2679 'fieldset[E][#|legend|Y]' + 2680 'legend[E|accesskey|align][#|S]' + 2681 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2682 'caption[E|align][#|S]' + 2683 'col[ZG][]' + 2684 'colgroup[ZG][col]' + 2685 'thead[ZF][tr]' + 2686 'tr[ZF|bgcolor][th|td]' + 2687 'th[E|ZE][#|Y]' + 2688 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2689 'noscript[E][#|Y]' + 2690 'td[E|ZE][#|Y]' + 2691 'tfoot[ZF][tr]' + 2692 'tbody[ZF][tr]' + 2693 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2694 'base[id|href|target][]' + 2695 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2696 ); 2697 } 2698 2699 return html4; 2700 }; 2701 2702 tinymce.html.Schema = function(settings) { 2703 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2704 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2705 2706 // Creates an lookup table map object for the specified option or the default value 2707 function createLookupTable(option, default_value, extend) { 2708 var value = settings[option]; 2709 2710 if (!value) { 2711 // Get cached default map or make it if needed 2712 value = mapCache[option]; 2713 2714 if (!value) { 2715 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2716 value = tinymce.extend(value, extend); 2717 2718 mapCache[option] = value; 2719 } 2720 } else { 2721 // Create custom map 2722 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2723 } 2724 2725 return value; 2726 }; 2727 2728 settings = settings || {}; 2729 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2730 2731 // Allow all elements and attributes if verify_html is set to false 2732 if (settings.verify_html === false) 2733 settings.valid_elements = '*[*]'; 2734 2735 // Build styles list 2736 if (settings.valid_styles) { 2737 validStyles = {}; 2738 2739 // Convert styles into a rule list 2740 each(settings.valid_styles, function(value, key) { 2741 validStyles[key] = tinymce.explode(value); 2742 }); 2743 } 2744 2745 // Setup map objects 2746 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea'); 2747 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2748 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2749 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2750 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); 2751 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 2752 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 2753 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 2754 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap); 2755 2756 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2757 function patternToRegExp(str) { 2758 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2759 }; 2760 2761 // Parses the specified valid_elements string and adds to the current rules 2762 // This function is a bit hard to read since it's heavily optimized for speed 2763 function addValidElements(valid_elements) { 2764 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2765 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2766 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2767 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2768 hasPatternsRegExp = /[*?+]/; 2769 2770 if (valid_elements) { 2771 // Split valid elements into an array with rules 2772 valid_elements = split(valid_elements); 2773 2774 if (elements['@']) { 2775 globalAttributes = elements['@'].attributes; 2776 globalAttributesOrder = elements['@'].attributesOrder; 2777 } 2778 2779 // Loop all rules 2780 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2781 // Parse element rule 2782 matches = elementRuleRegExp.exec(valid_elements[ei]); 2783 if (matches) { 2784 // Setup local names for matches 2785 prefix = matches[1]; 2786 elementName = matches[2]; 2787 outputName = matches[3]; 2788 attrData = matches[4]; 2789 2790 // Create new attributes and attributesOrder 2791 attributes = {}; 2792 attributesOrder = []; 2793 2794 // Create the new element 2795 element = { 2796 attributes : attributes, 2797 attributesOrder : attributesOrder 2798 }; 2799 2800 // Padd empty elements prefix 2801 if (prefix === '#') 2802 element.paddEmpty = true; 2803 2804 // Remove empty elements prefix 2805 if (prefix === '-') 2806 element.removeEmpty = true; 2807 2808 // Copy attributes from global rule into current rule 2809 if (globalAttributes) { 2810 for (key in globalAttributes) 2811 attributes[key] = globalAttributes[key]; 2812 2813 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2814 } 2815 2816 // Attributes defined 2817 if (attrData) { 2818 attrData = split(attrData, '|'); 2819 for (ai = 0, al = attrData.length; ai < al; ai++) { 2820 matches = attrRuleRegExp.exec(attrData[ai]); 2821 if (matches) { 2822 attr = {}; 2823 attrType = matches[1]; 2824 attrName = matches[2].replace(/::/g, ':'); 2825 prefix = matches[3]; 2826 value = matches[4]; 2827 2828 // Required 2829 if (attrType === '!') { 2830 element.attributesRequired = element.attributesRequired || []; 2831 element.attributesRequired.push(attrName); 2832 attr.required = true; 2833 } 2834 2835 // Denied from global 2836 if (attrType === '-') { 2837 delete attributes[attrName]; 2838 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2839 continue; 2840 } 2841 2842 // Default value 2843 if (prefix) { 2844 // Default value 2845 if (prefix === '=') { 2846 element.attributesDefault = element.attributesDefault || []; 2847 element.attributesDefault.push({name: attrName, value: value}); 2848 attr.defaultValue = value; 2849 } 2850 2851 // Forced value 2852 if (prefix === ':') { 2853 element.attributesForced = element.attributesForced || []; 2854 element.attributesForced.push({name: attrName, value: value}); 2855 attr.forcedValue = value; 2856 } 2857 2858 // Required values 2859 if (prefix === '<') 2860 attr.validValues = makeMap(value, '?'); 2861 } 2862 2863 // Check for attribute patterns 2864 if (hasPatternsRegExp.test(attrName)) { 2865 element.attributePatterns = element.attributePatterns || []; 2866 attr.pattern = patternToRegExp(attrName); 2867 element.attributePatterns.push(attr); 2868 } else { 2869 // Add attribute to order list if it doesn't already exist 2870 if (!attributes[attrName]) 2871 attributesOrder.push(attrName); 2872 2873 attributes[attrName] = attr; 2874 } 2875 } 2876 } 2877 } 2878 2879 // Global rule, store away these for later usage 2880 if (!globalAttributes && elementName == '@') { 2881 globalAttributes = attributes; 2882 globalAttributesOrder = attributesOrder; 2883 } 2884 2885 // Handle substitute elements such as b/strong 2886 if (outputName) { 2887 element.outputName = elementName; 2888 elements[outputName] = element; 2889 } 2890 2891 // Add pattern or exact element 2892 if (hasPatternsRegExp.test(elementName)) { 2893 element.pattern = patternToRegExp(elementName); 2894 patternElements.push(element); 2895 } else 2896 elements[elementName] = element; 2897 } 2898 } 2899 } 2900 }; 2901 2902 function setValidElements(valid_elements) { 2903 elements = {}; 2904 patternElements = []; 2905 2906 addValidElements(valid_elements); 2907 2908 each(schemaItems, function(element, name) { 2909 children[name] = element.children; 2910 }); 2911 }; 2912 2913 // Adds custom non HTML elements to the schema 2914 function addCustomElements(custom_elements) { 2915 var customElementRegExp = /^(~)?(.+)$/; 2916 2917 if (custom_elements) { 2918 each(split(custom_elements), function(rule) { 2919 var matches = customElementRegExp.exec(rule), 2920 inline = matches[1] === '~', 2921 cloneName = inline ? 'span' : 'div', 2922 name = matches[2]; 2923 2924 children[name] = children[cloneName]; 2925 customElementsMap[name] = cloneName; 2926 2927 // If it's not marked as inline then add it to valid block elements 2928 if (!inline) { 2929 blockElementsMap[name.toUpperCase()] = {}; 2930 blockElementsMap[name] = {}; 2931 } 2932 2933 // Add elements clone if needed 2934 if (!elements[name]) { 2935 elements[name] = elements[cloneName]; 2936 } 2937 2938 // Add custom elements at span/div positions 2939 each(children, function(element, child) { 2940 if (element[cloneName]) 2941 element[name] = element[cloneName]; 2942 }); 2943 }); 2944 } 2945 }; 2946 2947 // Adds valid children to the schema object 2948 function addValidChildren(valid_children) { 2949 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2950 2951 if (valid_children) { 2952 each(split(valid_children), function(rule) { 2953 var matches = childRuleRegExp.exec(rule), parent, prefix; 2954 2955 if (matches) { 2956 prefix = matches[1]; 2957 2958 // Add/remove items from default 2959 if (prefix) 2960 parent = children[matches[2]]; 2961 else 2962 parent = children[matches[2]] = {'#comment' : {}}; 2963 2964 parent = children[matches[2]]; 2965 2966 each(split(matches[3], '|'), function(child) { 2967 if (prefix === '-') 2968 delete parent[child]; 2969 else 2970 parent[child] = {}; 2971 }); 2972 } 2973 }); 2974 } 2975 }; 2976 2977 function getElementRule(name) { 2978 var element = elements[name], i; 2979 2980 // Exact match found 2981 if (element) 2982 return element; 2983 2984 // No exact match then try the patterns 2985 i = patternElements.length; 2986 while (i--) { 2987 element = patternElements[i]; 2988 2989 if (element.pattern.test(name)) 2990 return element; 2991 } 2992 }; 2993 2994 if (!settings.valid_elements) { 2995 // No valid elements defined then clone the elements from the schema spec 2996 each(schemaItems, function(element, name) { 2997 elements[name] = { 2998 attributes : element.attributes, 2999 attributesOrder : element.attributesOrder 3000 }; 3001 3002 children[name] = element.children; 3003 }); 3004 3005 // Switch these on HTML4 3006 if (settings.schema != "html5") { 3007 each(split('strong/b,em/i'), function(item) { 3008 item = split(item, '/'); 3009 elements[item[1]].outputName = item[0]; 3010 }); 3011 } 3012 3013 // Add default alt attribute for images 3014 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 3015 3016 // Remove these if they are empty by default 3017 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 3018 if (elements[name]) { 3019 elements[name].removeEmpty = true; 3020 } 3021 }); 3022 3023 // Padd these by default 3024 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 3025 elements[name].paddEmpty = true; 3026 }); 3027 } else 3028 setValidElements(settings.valid_elements); 3029 3030 addCustomElements(settings.custom_elements); 3031 addValidChildren(settings.valid_children); 3032 addValidElements(settings.extended_valid_elements); 3033 3034 // Todo: Remove this when we fix list handling to be valid 3035 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 3036 3037 // Delete invalid elements 3038 if (settings.invalid_elements) { 3039 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 3040 if (elements[item]) 3041 delete elements[item]; 3042 }); 3043 } 3044 3045 // If the user didn't allow span only allow internal spans 3046 if (!getElementRule('span')) 3047 addValidElements('span[!data-mce-type|*]'); 3048 3049 self.children = children; 3050 3051 self.styles = validStyles; 3052 3053 self.getBoolAttrs = function() { 3054 return boolAttrMap; 3055 }; 3056 3057 self.getBlockElements = function() { 3058 return blockElementsMap; 3059 }; 3060 3061 self.getTextBlockElements = function() { 3062 return textBlockElementsMap; 3063 }; 3064 3065 self.getShortEndedElements = function() { 3066 return shortEndedElementsMap; 3067 }; 3068 3069 self.getSelfClosingElements = function() { 3070 return selfClosingElementsMap; 3071 }; 3072 3073 self.getNonEmptyElements = function() { 3074 return nonEmptyElementsMap; 3075 }; 3076 3077 self.getWhiteSpaceElements = function() { 3078 return whiteSpaceElementsMap; 3079 }; 3080 3081 self.isValidChild = function(name, child) { 3082 var parent = children[name]; 3083 3084 return !!(parent && parent[child]); 3085 }; 3086 3087 self.isValid = function(name, attr) { 3088 var attrPatterns, i, rule = getElementRule(name); 3089 3090 // Check if it's a valid element 3091 if (rule) { 3092 if (attr) { 3093 // Check if attribute name exists 3094 if (rule.attributes[attr]) { 3095 return true; 3096 } 3097 3098 // Check if attribute matches a regexp pattern 3099 attrPatterns = rule.attributePatterns; 3100 if (attrPatterns) { 3101 i = attrPatterns.length; 3102 while (i--) { 3103 if (attrPatterns[i].pattern.test(name)) { 3104 return true; 3105 } 3106 } 3107 } 3108 } else { 3109 return true; 3110 } 3111 } 3112 3113 // No match 3114 return false; 3115 }; 3116 3117 self.getElementRule = getElementRule; 3118 3119 self.getCustomElements = function() { 3120 return customElementsMap; 3121 }; 3122 3123 self.addValidElements = addValidElements; 3124 3125 self.setValidElements = setValidElements; 3126 3127 self.addCustomElements = addCustomElements; 3128 3129 self.addValidChildren = addValidChildren; 3130 3131 self.elements = elements; 3132 }; 3133 })(tinymce); 3134 (function(tinymce) { 3135 tinymce.html.SaxParser = function(settings, schema) { 3136 var self = this, noop = function() {}; 3137 3138 settings = settings || {}; 3139 self.schema = schema = schema || new tinymce.html.Schema(); 3140 3141 if (settings.fix_self_closing !== false) 3142 settings.fix_self_closing = true; 3143 3144 // Add handler functions from settings and setup default handlers 3145 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 3146 if (name) 3147 self[name] = settings[name] || noop; 3148 }); 3149 3150 self.parse = function(html) { 3151 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 3152 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 3153 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 3154 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 3155 3156 function processEndTag(name) { 3157 var pos, i; 3158 3159 // Find position of parent of the same type 3160 pos = stack.length; 3161 while (pos--) { 3162 if (stack[pos].name === name) 3163 break; 3164 } 3165 3166 // Found parent 3167 if (pos >= 0) { 3168 // Close all the open elements 3169 for (i = stack.length - 1; i >= pos; i--) { 3170 name = stack[i]; 3171 3172 if (name.valid) 3173 self.end(name.name); 3174 } 3175 3176 // Remove the open elements from the stack 3177 stack.length = pos; 3178 } 3179 }; 3180 3181 function parseAttribute(match, name, value, val2, val3) { 3182 var attrRule, i; 3183 3184 name = name.toLowerCase(); 3185 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 3186 3187 // Validate name and value 3188 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 3189 attrRule = validAttributesMap[name]; 3190 3191 // Find rule by pattern matching 3192 if (!attrRule && validAttributePatterns) { 3193 i = validAttributePatterns.length; 3194 while (i--) { 3195 attrRule = validAttributePatterns[i]; 3196 if (attrRule.pattern.test(name)) 3197 break; 3198 } 3199 3200 // No rule matched 3201 if (i === -1) 3202 attrRule = null; 3203 } 3204 3205 // No attribute rule found 3206 if (!attrRule) 3207 return; 3208 3209 // Validate value 3210 if (attrRule.validValues && !(value in attrRule.validValues)) 3211 return; 3212 } 3213 3214 // Add attribute to list and map 3215 attrList.map[name] = value; 3216 attrList.push({ 3217 name: name, 3218 value: value 3219 }); 3220 }; 3221 3222 // Precompile RegExps and map objects 3223 tokenRegExp = new RegExp('<(?:' + 3224 '(?:!--([\\w\\W]*?)-->)|' + // Comment 3225 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 3226 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 3227 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 3228 '(?:\\/([^>]+)>)|' + // End element 3229 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 3230 ')', 'g'); 3231 3232 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 3233 specialElements = { 3234 'script' : /<\/script[^>]*>/gi, 3235 'style' : /<\/style[^>]*>/gi, 3236 'noscript' : /<\/noscript[^>]*>/gi 3237 }; 3238 3239 // Setup lookup tables for empty elements and boolean attributes 3240 shortEndedElements = schema.getShortEndedElements(); 3241 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3242 fillAttrsMap = schema.getBoolAttrs(); 3243 validate = settings.validate; 3244 removeInternalElements = settings.remove_internals; 3245 fixSelfClosing = settings.fix_self_closing; 3246 isIE = tinymce.isIE; 3247 invalidPrefixRegExp = /^:/; 3248 3249 while (matches = tokenRegExp.exec(html)) { 3250 // Text 3251 if (index < matches.index) 3252 self.text(decode(html.substr(index, matches.index - index))); 3253 3254 if (value = matches[6]) { // End element 3255 value = value.toLowerCase(); 3256 3257 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3258 if (isIE && invalidPrefixRegExp.test(value)) 3259 value = value.substr(1); 3260 3261 processEndTag(value); 3262 } else if (value = matches[7]) { // Start element 3263 value = value.toLowerCase(); 3264 3265 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3266 if (isIE && invalidPrefixRegExp.test(value)) 3267 value = value.substr(1); 3268 3269 isShortEnded = value in shortEndedElements; 3270 3271 // Is self closing tag for example an <li> after an open <li> 3272 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3273 processEndTag(value); 3274 3275 // Validate element 3276 if (!validate || (elementRule = schema.getElementRule(value))) { 3277 isValidElement = true; 3278 3279 // Grab attributes map and patters when validation is enabled 3280 if (validate) { 3281 validAttributesMap = elementRule.attributes; 3282 validAttributePatterns = elementRule.attributePatterns; 3283 } 3284 3285 // Parse attributes 3286 if (attribsValue = matches[8]) { 3287 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3288 3289 // If the element has internal attributes then remove it if we are told to do so 3290 if (isInternalElement && removeInternalElements) 3291 isValidElement = false; 3292 3293 attrList = []; 3294 attrList.map = {}; 3295 3296 attribsValue.replace(attrRegExp, parseAttribute); 3297 } else { 3298 attrList = []; 3299 attrList.map = {}; 3300 } 3301 3302 // Process attributes if validation is enabled 3303 if (validate && !isInternalElement) { 3304 attributesRequired = elementRule.attributesRequired; 3305 attributesDefault = elementRule.attributesDefault; 3306 attributesForced = elementRule.attributesForced; 3307 3308 // Handle forced attributes 3309 if (attributesForced) { 3310 i = attributesForced.length; 3311 while (i--) { 3312 attr = attributesForced[i]; 3313 name = attr.name; 3314 attrValue = attr.value; 3315 3316 if (attrValue === '{$uid}') 3317 attrValue = 'mce_' + idCount++; 3318 3319 attrList.map[name] = attrValue; 3320 attrList.push({name: name, value: attrValue}); 3321 } 3322 } 3323 3324 // Handle default attributes 3325 if (attributesDefault) { 3326 i = attributesDefault.length; 3327 while (i--) { 3328 attr = attributesDefault[i]; 3329 name = attr.name; 3330 3331 if (!(name in attrList.map)) { 3332 attrValue = attr.value; 3333 3334 if (attrValue === '{$uid}') 3335 attrValue = 'mce_' + idCount++; 3336 3337 attrList.map[name] = attrValue; 3338 attrList.push({name: name, value: attrValue}); 3339 } 3340 } 3341 } 3342 3343 // Handle required attributes 3344 if (attributesRequired) { 3345 i = attributesRequired.length; 3346 while (i--) { 3347 if (attributesRequired[i] in attrList.map) 3348 break; 3349 } 3350 3351 // None of the required attributes where found 3352 if (i === -1) 3353 isValidElement = false; 3354 } 3355 3356 // Invalidate element if it's marked as bogus 3357 if (attrList.map['data-mce-bogus']) 3358 isValidElement = false; 3359 } 3360 3361 if (isValidElement) 3362 self.start(value, attrList, isShortEnded); 3363 } else 3364 isValidElement = false; 3365 3366 // Treat script, noscript and style a bit different since they may include code that looks like elements 3367 if (endRegExp = specialElements[value]) { 3368 endRegExp.lastIndex = index = matches.index + matches[0].length; 3369 3370 if (matches = endRegExp.exec(html)) { 3371 if (isValidElement) 3372 text = html.substr(index, matches.index - index); 3373 3374 index = matches.index + matches[0].length; 3375 } else { 3376 text = html.substr(index); 3377 index = html.length; 3378 } 3379 3380 if (isValidElement && text.length > 0) 3381 self.text(text, true); 3382 3383 if (isValidElement) 3384 self.end(value); 3385 3386 tokenRegExp.lastIndex = index; 3387 continue; 3388 } 3389 3390 // Push value on to stack 3391 if (!isShortEnded) { 3392 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3393 stack.push({name: value, valid: isValidElement}); 3394 else if (isValidElement) 3395 self.end(value); 3396 } 3397 } else if (value = matches[1]) { // Comment 3398 self.comment(value); 3399 } else if (value = matches[2]) { // CDATA 3400 self.cdata(value); 3401 } else if (value = matches[3]) { // DOCTYPE 3402 self.doctype(value); 3403 } else if (value = matches[4]) { // PI 3404 self.pi(value, matches[5]); 3405 } 3406 3407 index = matches.index + matches[0].length; 3408 } 3409 3410 // Text 3411 if (index < html.length) 3412 self.text(decode(html.substr(index))); 3413 3414 // Close any open elements 3415 for (i = stack.length - 1; i >= 0; i--) { 3416 value = stack[i]; 3417 3418 if (value.valid) 3419 self.end(value.name); 3420 } 3421 }; 3422 } 3423 })(tinymce); 3424 (function(tinymce) { 3425 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3426 '#text' : 3, 3427 '#comment' : 8, 3428 '#cdata' : 4, 3429 '#pi' : 7, 3430 '#doctype' : 10, 3431 '#document-fragment' : 11 3432 }; 3433 3434 // Walks the tree left/right 3435 function walk(node, root_node, prev) { 3436 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3437 3438 // Walk into nodes if it has a start 3439 if (node[startName]) 3440 return node[startName]; 3441 3442 // Return the sibling if it has one 3443 if (node !== root_node) { 3444 sibling = node[siblingName]; 3445 3446 if (sibling) 3447 return sibling; 3448 3449 // Walk up the parents to look for siblings 3450 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3451 sibling = parent[siblingName]; 3452 3453 if (sibling) 3454 return sibling; 3455 } 3456 } 3457 }; 3458 3459 function Node(name, type) { 3460 this.name = name; 3461 this.type = type; 3462 3463 if (type === 1) { 3464 this.attributes = []; 3465 this.attributes.map = {}; 3466 } 3467 } 3468 3469 tinymce.extend(Node.prototype, { 3470 replace : function(node) { 3471 var self = this; 3472 3473 if (node.parent) 3474 node.remove(); 3475 3476 self.insert(node, self); 3477 self.remove(); 3478 3479 return self; 3480 }, 3481 3482 attr : function(name, value) { 3483 var self = this, attrs, i, undef; 3484 3485 if (typeof name !== "string") { 3486 for (i in name) 3487 self.attr(i, name[i]); 3488 3489 return self; 3490 } 3491 3492 if (attrs = self.attributes) { 3493 if (value !== undef) { 3494 // Remove attribute 3495 if (value === null) { 3496 if (name in attrs.map) { 3497 delete attrs.map[name]; 3498 3499 i = attrs.length; 3500 while (i--) { 3501 if (attrs[i].name === name) { 3502 attrs = attrs.splice(i, 1); 3503 return self; 3504 } 3505 } 3506 } 3507 3508 return self; 3509 } 3510 3511 // Set attribute 3512 if (name in attrs.map) { 3513 // Set attribute 3514 i = attrs.length; 3515 while (i--) { 3516 if (attrs[i].name === name) { 3517 attrs[i].value = value; 3518 break; 3519 } 3520 } 3521 } else 3522 attrs.push({name: name, value: value}); 3523 3524 attrs.map[name] = value; 3525 3526 return self; 3527 } else { 3528 return attrs.map[name]; 3529 } 3530 } 3531 }, 3532 3533 clone : function() { 3534 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3535 3536 // Clone element attributes 3537 if (selfAttrs = self.attributes) { 3538 cloneAttrs = []; 3539 cloneAttrs.map = {}; 3540 3541 for (i = 0, l = selfAttrs.length; i < l; i++) { 3542 selfAttr = selfAttrs[i]; 3543 3544 // Clone everything except id 3545 if (selfAttr.name !== 'id') { 3546 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3547 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3548 } 3549 } 3550 3551 clone.attributes = cloneAttrs; 3552 } 3553 3554 clone.value = self.value; 3555 clone.shortEnded = self.shortEnded; 3556 3557 return clone; 3558 }, 3559 3560 wrap : function(wrapper) { 3561 var self = this; 3562 3563 self.parent.insert(wrapper, self); 3564 wrapper.append(self); 3565 3566 return self; 3567 }, 3568 3569 unwrap : function() { 3570 var self = this, node, next; 3571 3572 for (node = self.firstChild; node; ) { 3573 next = node.next; 3574 self.insert(node, self, true); 3575 node = next; 3576 } 3577 3578 self.remove(); 3579 }, 3580 3581 remove : function() { 3582 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3583 3584 if (parent) { 3585 if (parent.firstChild === self) { 3586 parent.firstChild = next; 3587 3588 if (next) 3589 next.prev = null; 3590 } else { 3591 prev.next = next; 3592 } 3593 3594 if (parent.lastChild === self) { 3595 parent.lastChild = prev; 3596 3597 if (prev) 3598 prev.next = null; 3599 } else { 3600 next.prev = prev; 3601 } 3602 3603 self.parent = self.next = self.prev = null; 3604 } 3605 3606 return self; 3607 }, 3608 3609 append : function(node) { 3610 var self = this, last; 3611 3612 if (node.parent) 3613 node.remove(); 3614 3615 last = self.lastChild; 3616 if (last) { 3617 last.next = node; 3618 node.prev = last; 3619 self.lastChild = node; 3620 } else 3621 self.lastChild = self.firstChild = node; 3622 3623 node.parent = self; 3624 3625 return node; 3626 }, 3627 3628 insert : function(node, ref_node, before) { 3629 var parent; 3630 3631 if (node.parent) 3632 node.remove(); 3633 3634 parent = ref_node.parent || this; 3635 3636 if (before) { 3637 if (ref_node === parent.firstChild) 3638 parent.firstChild = node; 3639 else 3640 ref_node.prev.next = node; 3641 3642 node.prev = ref_node.prev; 3643 node.next = ref_node; 3644 ref_node.prev = node; 3645 } else { 3646 if (ref_node === parent.lastChild) 3647 parent.lastChild = node; 3648 else 3649 ref_node.next.prev = node; 3650 3651 node.next = ref_node.next; 3652 node.prev = ref_node; 3653 ref_node.next = node; 3654 } 3655 3656 node.parent = parent; 3657 3658 return node; 3659 }, 3660 3661 getAll : function(name) { 3662 var self = this, node, collection = []; 3663 3664 for (node = self.firstChild; node; node = walk(node, self)) { 3665 if (node.name === name) 3666 collection.push(node); 3667 } 3668 3669 return collection; 3670 }, 3671 3672 empty : function() { 3673 var self = this, nodes, i, node; 3674 3675 // Remove all children 3676 if (self.firstChild) { 3677 nodes = []; 3678 3679 // Collect the children 3680 for (node = self.firstChild; node; node = walk(node, self)) 3681 nodes.push(node); 3682 3683 // Remove the children 3684 i = nodes.length; 3685 while (i--) { 3686 node = nodes[i]; 3687 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3688 } 3689 } 3690 3691 self.firstChild = self.lastChild = null; 3692 3693 return self; 3694 }, 3695 3696 isEmpty : function(elements) { 3697 var self = this, node = self.firstChild, i, name; 3698 3699 if (node) { 3700 do { 3701 if (node.type === 1) { 3702 // Ignore bogus elements 3703 if (node.attributes.map['data-mce-bogus']) 3704 continue; 3705 3706 // Keep empty elements like <img /> 3707 if (elements[node.name]) 3708 return false; 3709 3710 // Keep elements with data attributes or name attribute like <a name="1"></a> 3711 i = node.attributes.length; 3712 while (i--) { 3713 name = node.attributes[i].name; 3714 if (name === "name" || name.indexOf('data-mce-') === 0) 3715 return false; 3716 } 3717 } 3718 3719 // Keep comments 3720 if (node.type === 8) 3721 return false; 3722 3723 // Keep non whitespace text nodes 3724 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3725 return false; 3726 } while (node = walk(node, self)); 3727 } 3728 3729 return true; 3730 }, 3731 3732 walk : function(prev) { 3733 return walk(this, null, prev); 3734 } 3735 }); 3736 3737 tinymce.extend(Node, { 3738 create : function(name, attrs) { 3739 var node, attrName; 3740 3741 // Create node 3742 node = new Node(name, typeLookup[name] || 1); 3743 3744 // Add attributes if needed 3745 if (attrs) { 3746 for (attrName in attrs) 3747 node.attr(attrName, attrs[attrName]); 3748 } 3749 3750 return node; 3751 } 3752 }); 3753 3754 tinymce.html.Node = Node; 3755 })(tinymce); 3756 (function(tinymce) { 3757 var Node = tinymce.html.Node; 3758 3759 tinymce.html.DomParser = function(settings, schema) { 3760 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3761 3762 settings = settings || {}; 3763 settings.validate = "validate" in settings ? settings.validate : true; 3764 settings.root_name = settings.root_name || 'body'; 3765 self.schema = schema = schema || new tinymce.html.Schema(); 3766 3767 function fixInvalidChildren(nodes) { 3768 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3769 childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; 3770 3771 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3772 nonEmptyElements = schema.getNonEmptyElements(); 3773 textBlockElements = schema.getTextBlockElements(); 3774 3775 for (ni = 0; ni < nodes.length; ni++) { 3776 node = nodes[ni]; 3777 3778 // Already removed or fixed 3779 if (!node.parent || node.fixed) 3780 continue; 3781 3782 // If the invalid element is a text block and the text block is within a parent LI element 3783 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 3784 if (textBlockElements[node.name] && node.parent.name == 'li') { 3785 // Move sibling text blocks after LI element 3786 sibling = node.next; 3787 while (sibling) { 3788 if (textBlockElements[sibling.name]) { 3789 sibling.name = 'li'; 3790 sibling.fixed = true; 3791 node.parent.insert(sibling, node.parent); 3792 } else { 3793 break; 3794 } 3795 3796 sibling = sibling.next; 3797 } 3798 3799 // Unwrap current text block 3800 node.unwrap(node); 3801 continue; 3802 } 3803 3804 // Get list of all parent nodes until we find a valid parent to stick the child into 3805 parents = [node]; 3806 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3807 parents.push(parent); 3808 3809 // Found a suitable parent 3810 if (parent && parents.length > 1) { 3811 // Reverse the array since it makes looping easier 3812 parents.reverse(); 3813 3814 // Clone the related parent and insert that after the moved node 3815 newParent = currentNode = self.filterNode(parents[0].clone()); 3816 3817 // Start cloning and moving children on the left side of the target node 3818 for (i = 0; i < parents.length - 1; i++) { 3819 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3820 tempNode = self.filterNode(parents[i].clone()); 3821 currentNode.append(tempNode); 3822 } else 3823 tempNode = currentNode; 3824 3825 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3826 nextNode = childNode.next; 3827 tempNode.append(childNode); 3828 childNode = nextNode; 3829 } 3830 3831 currentNode = tempNode; 3832 } 3833 3834 if (!newParent.isEmpty(nonEmptyElements)) { 3835 parent.insert(newParent, parents[0], true); 3836 parent.insert(node, newParent); 3837 } else { 3838 parent.insert(node, parents[0], true); 3839 } 3840 3841 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3842 parent = parents[0]; 3843 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3844 parent.empty().remove(); 3845 } 3846 } else if (node.parent) { 3847 // If it's an LI try to find a UL/OL for it or wrap it 3848 if (node.name === 'li') { 3849 sibling = node.prev; 3850 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3851 sibling.append(node); 3852 continue; 3853 } 3854 3855 sibling = node.next; 3856 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3857 sibling.insert(node, sibling.firstChild, true); 3858 continue; 3859 } 3860 3861 node.wrap(self.filterNode(new Node('ul', 1))); 3862 continue; 3863 } 3864 3865 // Try wrapping the element in a DIV 3866 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3867 node.wrap(self.filterNode(new Node('div', 1))); 3868 } else { 3869 // We failed wrapping it, then remove or unwrap it 3870 if (node.name === 'style' || node.name === 'script') 3871 node.empty().remove(); 3872 else 3873 node.unwrap(); 3874 } 3875 } 3876 } 3877 }; 3878 3879 self.filterNode = function(node) { 3880 var i, name, list; 3881 3882 // Run element filters 3883 if (name in nodeFilters) { 3884 list = matchedNodes[name]; 3885 3886 if (list) 3887 list.push(node); 3888 else 3889 matchedNodes[name] = [node]; 3890 } 3891 3892 // Run attribute filters 3893 i = attributeFilters.length; 3894 while (i--) { 3895 name = attributeFilters[i].name; 3896 3897 if (name in node.attributes.map) { 3898 list = matchedAttributes[name]; 3899 3900 if (list) 3901 list.push(node); 3902 else 3903 matchedAttributes[name] = [node]; 3904 } 3905 } 3906 3907 return node; 3908 }; 3909 3910 self.addNodeFilter = function(name, callback) { 3911 tinymce.each(tinymce.explode(name), function(name) { 3912 var list = nodeFilters[name]; 3913 3914 if (!list) 3915 nodeFilters[name] = list = []; 3916 3917 list.push(callback); 3918 }); 3919 }; 3920 3921 self.addAttributeFilter = function(name, callback) { 3922 tinymce.each(tinymce.explode(name), function(name) { 3923 var i; 3924 3925 for (i = 0; i < attributeFilters.length; i++) { 3926 if (attributeFilters[i].name === name) { 3927 attributeFilters[i].callbacks.push(callback); 3928 return; 3929 } 3930 } 3931 3932 attributeFilters.push({name: name, callbacks: [callback]}); 3933 }); 3934 }; 3935 3936 self.parse = function(html, args) { 3937 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3938 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3939 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3940 3941 args = args || {}; 3942 matchedNodes = {}; 3943 matchedAttributes = {}; 3944 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3945 nonEmptyElements = schema.getNonEmptyElements(); 3946 children = schema.children; 3947 validate = settings.validate; 3948 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3949 3950 whiteSpaceElements = schema.getWhiteSpaceElements(); 3951 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3952 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3953 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3954 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3955 3956 function addRootBlocks() { 3957 var node = rootNode.firstChild, next, rootBlockNode; 3958 3959 while (node) { 3960 next = node.next; 3961 3962 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3963 if (!rootBlockNode) { 3964 // Create a new root block element 3965 rootBlockNode = createNode(rootBlockName, 1); 3966 rootNode.insert(rootBlockNode, node); 3967 rootBlockNode.append(node); 3968 } else 3969 rootBlockNode.append(node); 3970 } else { 3971 rootBlockNode = null; 3972 } 3973 3974 node = next; 3975 }; 3976 }; 3977 3978 function createNode(name, type) { 3979 var node = new Node(name, type), list; 3980 3981 if (name in nodeFilters) { 3982 list = matchedNodes[name]; 3983 3984 if (list) 3985 list.push(node); 3986 else 3987 matchedNodes[name] = [node]; 3988 } 3989 3990 return node; 3991 }; 3992 3993 function removeWhitespaceBefore(node) { 3994 var textNode, textVal, sibling; 3995 3996 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3997 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3998 3999 if (textVal.length > 0) { 4000 textNode.value = textVal; 4001 textNode = textNode.prev; 4002 } else { 4003 sibling = textNode.prev; 4004 textNode.remove(); 4005 textNode = sibling; 4006 } 4007 } 4008 }; 4009 4010 function cloneAndExcludeBlocks(input) { 4011 var name, output = {}; 4012 4013 for (name in input) { 4014 if (name !== 'li' && name != 'p') { 4015 output[name] = input[name]; 4016 } 4017 } 4018 4019 return output; 4020 }; 4021 4022 parser = new tinymce.html.SaxParser({ 4023 validate : validate, 4024 4025 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 4026 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 4027 4028 cdata: function(text) { 4029 node.append(createNode('#cdata', 4)).value = text; 4030 }, 4031 4032 text: function(text, raw) { 4033 var textNode; 4034 4035 // Trim all redundant whitespace on non white space elements 4036 if (!isInWhiteSpacePreservedElement) { 4037 text = text.replace(allWhiteSpaceRegExp, ' '); 4038 4039 if (node.lastChild && blockElements[node.lastChild.name]) 4040 text = text.replace(startWhiteSpaceRegExp, ''); 4041 } 4042 4043 // Do we need to create the node 4044 if (text.length !== 0) { 4045 textNode = createNode('#text', 3); 4046 textNode.raw = !!raw; 4047 node.append(textNode).value = text; 4048 } 4049 }, 4050 4051 comment: function(text) { 4052 node.append(createNode('#comment', 8)).value = text; 4053 }, 4054 4055 pi: function(name, text) { 4056 node.append(createNode(name, 7)).value = text; 4057 removeWhitespaceBefore(node); 4058 }, 4059 4060 doctype: function(text) { 4061 var newNode; 4062 4063 newNode = node.append(createNode('#doctype', 10)); 4064 newNode.value = text; 4065 removeWhitespaceBefore(node); 4066 }, 4067 4068 start: function(name, attrs, empty) { 4069 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 4070 4071 elementRule = validate ? schema.getElementRule(name) : {}; 4072 if (elementRule) { 4073 newNode = createNode(elementRule.outputName || name, 1); 4074 newNode.attributes = attrs; 4075 newNode.shortEnded = empty; 4076 4077 node.append(newNode); 4078 4079 // Check if node is valid child of the parent node is the child is 4080 // unknown we don't collect it since it's probably a custom element 4081 parent = children[node.name]; 4082 if (parent && children[newNode.name] && !parent[newNode.name]) 4083 invalidChildren.push(newNode); 4084 4085 attrFiltersLen = attributeFilters.length; 4086 while (attrFiltersLen--) { 4087 attrName = attributeFilters[attrFiltersLen].name; 4088 4089 if (attrName in attrs.map) { 4090 list = matchedAttributes[attrName]; 4091 4092 if (list) 4093 list.push(newNode); 4094 else 4095 matchedAttributes[attrName] = [newNode]; 4096 } 4097 } 4098 4099 // Trim whitespace before block 4100 if (blockElements[name]) 4101 removeWhitespaceBefore(newNode); 4102 4103 // Change current node if the element wasn't empty i.e not <br /> or <img /> 4104 if (!empty) 4105 node = newNode; 4106 4107 // Check if we are inside a whitespace preserved element 4108 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4109 isInWhiteSpacePreservedElement = true; 4110 } 4111 } 4112 }, 4113 4114 end: function(name) { 4115 var textNode, elementRule, text, sibling, tempNode; 4116 4117 elementRule = validate ? schema.getElementRule(name) : {}; 4118 if (elementRule) { 4119 if (blockElements[name]) { 4120 if (!isInWhiteSpacePreservedElement) { 4121 // Trim whitespace of the first node in a block 4122 textNode = node.firstChild; 4123 if (textNode && textNode.type === 3) { 4124 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4125 4126 // Any characters left after trim or should we remove it 4127 if (text.length > 0) { 4128 textNode.value = text; 4129 textNode = textNode.next; 4130 } else { 4131 sibling = textNode.next; 4132 textNode.remove(); 4133 textNode = sibling; 4134 } 4135 4136 // Remove any pure whitespace siblings 4137 while (textNode && textNode.type === 3) { 4138 text = textNode.value; 4139 sibling = textNode.next; 4140 4141 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4142 textNode.remove(); 4143 textNode = sibling; 4144 } 4145 4146 textNode = sibling; 4147 } 4148 } 4149 4150 // Trim whitespace of the last node in a block 4151 textNode = node.lastChild; 4152 if (textNode && textNode.type === 3) { 4153 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 4154 4155 // Any characters left after trim or should we remove it 4156 if (text.length > 0) { 4157 textNode.value = text; 4158 textNode = textNode.prev; 4159 } else { 4160 sibling = textNode.prev; 4161 textNode.remove(); 4162 textNode = sibling; 4163 } 4164 4165 // Remove any pure whitespace siblings 4166 while (textNode && textNode.type === 3) { 4167 text = textNode.value; 4168 sibling = textNode.prev; 4169 4170 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4171 textNode.remove(); 4172 textNode = sibling; 4173 } 4174 4175 textNode = sibling; 4176 } 4177 } 4178 } 4179 4180 // Trim start white space 4181 // Removed due to: #5424 4182 /*textNode = node.prev; 4183 if (textNode && textNode.type === 3) { 4184 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4185 4186 if (text.length > 0) 4187 textNode.value = text; 4188 else 4189 textNode.remove(); 4190 }*/ 4191 } 4192 4193 // Check if we exited a whitespace preserved element 4194 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4195 isInWhiteSpacePreservedElement = false; 4196 } 4197 4198 // Handle empty nodes 4199 if (elementRule.removeEmpty || elementRule.paddEmpty) { 4200 if (node.isEmpty(nonEmptyElements)) { 4201 if (elementRule.paddEmpty) 4202 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 4203 else { 4204 // Leave nodes that have a name like <a name="name"> 4205 if (!node.attributes.map.name && !node.attributes.map.id) { 4206 tempNode = node.parent; 4207 node.empty().remove(); 4208 node = tempNode; 4209 return; 4210 } 4211 } 4212 } 4213 } 4214 4215 node = node.parent; 4216 } 4217 } 4218 }, schema); 4219 4220 rootNode = node = new Node(args.context || settings.root_name, 11); 4221 4222 parser.parse(html); 4223 4224 // Fix invalid children or report invalid children in a contextual parsing 4225 if (validate && invalidChildren.length) { 4226 if (!args.context) 4227 fixInvalidChildren(invalidChildren); 4228 else 4229 args.invalid = true; 4230 } 4231 4232 // Wrap nodes in the root into block elements if the root is body 4233 if (rootBlockName && rootNode.name == 'body') 4234 addRootBlocks(); 4235 4236 // Run filters only when the contents is valid 4237 if (!args.invalid) { 4238 // Run node filters 4239 for (name in matchedNodes) { 4240 list = nodeFilters[name]; 4241 nodes = matchedNodes[name]; 4242 4243 // Remove already removed children 4244 fi = nodes.length; 4245 while (fi--) { 4246 if (!nodes[fi].parent) 4247 nodes.splice(fi, 1); 4248 } 4249 4250 for (i = 0, l = list.length; i < l; i++) 4251 list[i](nodes, name, args); 4252 } 4253 4254 // Run attribute filters 4255 for (i = 0, l = attributeFilters.length; i < l; i++) { 4256 list = attributeFilters[i]; 4257 4258 if (list.name in matchedAttributes) { 4259 nodes = matchedAttributes[list.name]; 4260 4261 // Remove already removed children 4262 fi = nodes.length; 4263 while (fi--) { 4264 if (!nodes[fi].parent) 4265 nodes.splice(fi, 1); 4266 } 4267 4268 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4269 list.callbacks[fi](nodes, list.name, args); 4270 } 4271 } 4272 } 4273 4274 return rootNode; 4275 }; 4276 4277 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4278 // make it possible to place the caret inside empty blocks. This logic tries to remove 4279 // these elements and keep br elements that where intended to be there intact 4280 if (settings.remove_trailing_brs) { 4281 self.addNodeFilter('br', function(nodes, name) { 4282 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4283 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4284 4285 // Remove brs from body element as well 4286 blockElements.body = 1; 4287 4288 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4289 for (i = 0; i < l; i++) { 4290 node = nodes[i]; 4291 parent = node.parent; 4292 4293 if (blockElements[node.parent.name] && node === parent.lastChild) { 4294 // Loop all nodes to the left of the current node and check for other BR elements 4295 // excluding bookmarks since they are invisible 4296 prev = node.prev; 4297 while (prev) { 4298 prevName = prev.name; 4299 4300 // Ignore bookmarks 4301 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4302 // Found a non BR element 4303 if (prevName !== "br") 4304 break; 4305 4306 // Found another br it's a <br><br> structure then don't remove anything 4307 if (prevName === 'br') { 4308 node = null; 4309 break; 4310 } 4311 } 4312 4313 prev = prev.prev; 4314 } 4315 4316 if (node) { 4317 node.remove(); 4318 4319 // Is the parent to be considered empty after we removed the BR 4320 if (parent.isEmpty(nonEmptyElements)) { 4321 elementRule = schema.getElementRule(parent.name); 4322 4323 // Remove or padd the element depending on schema rule 4324 if (elementRule) { 4325 if (elementRule.removeEmpty) 4326 parent.remove(); 4327 else if (elementRule.paddEmpty) 4328 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4329 } 4330 } 4331 } 4332 } else { 4333 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4334 lastParent = node; 4335 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4336 lastParent = parent; 4337 4338 if (blockElements[parent.name]) { 4339 break; 4340 } 4341 4342 parent = parent.parent; 4343 } 4344 4345 if (lastParent === parent) { 4346 textNode = new tinymce.html.Node('#text', 3); 4347 textNode.value = '\u00a0'; 4348 node.replace(textNode); 4349 } 4350 } 4351 } 4352 }); 4353 } 4354 4355 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4356 if (!settings.allow_html_in_named_anchor) { 4357 self.addAttributeFilter('id,name', function(nodes, name) { 4358 var i = nodes.length, sibling, prevSibling, parent, node; 4359 4360 while (i--) { 4361 node = nodes[i]; 4362 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4363 parent = node.parent; 4364 4365 // Move children after current node 4366 sibling = node.lastChild; 4367 do { 4368 prevSibling = sibling.prev; 4369 parent.insert(sibling, node); 4370 sibling = prevSibling; 4371 } while (sibling); 4372 } 4373 } 4374 }); 4375 } 4376 } 4377 })(tinymce); 4378 tinymce.html.Writer = function(settings) { 4379 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4380 4381 settings = settings || {}; 4382 indent = settings.indent; 4383 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4384 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4385 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4386 htmlOutput = settings.element_format == "html"; 4387 4388 return { 4389 start: function(name, attrs, empty) { 4390 var i, l, attr, value; 4391 4392 if (indent && indentBefore[name] && html.length > 0) { 4393 value = html[html.length - 1]; 4394 4395 if (value.length > 0 && value !== '\n') 4396 html.push('\n'); 4397 } 4398 4399 html.push('<', name); 4400 4401 if (attrs) { 4402 for (i = 0, l = attrs.length; i < l; i++) { 4403 attr = attrs[i]; 4404 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4405 } 4406 } 4407 4408 if (!empty || htmlOutput) 4409 html[html.length] = '>'; 4410 else 4411 html[html.length] = ' />'; 4412 4413 if (empty && indent && indentAfter[name] && html.length > 0) { 4414 value = html[html.length - 1]; 4415 4416 if (value.length > 0 && value !== '\n') 4417 html.push('\n'); 4418 } 4419 }, 4420 4421 end: function(name) { 4422 var value; 4423 4424 /*if (indent && indentBefore[name] && html.length > 0) { 4425 value = html[html.length - 1]; 4426 4427 if (value.length > 0 && value !== '\n') 4428 html.push('\n'); 4429 }*/ 4430 4431 html.push('</', name, '>'); 4432 4433 if (indent && indentAfter[name] && html.length > 0) { 4434 value = html[html.length - 1]; 4435 4436 if (value.length > 0 && value !== '\n') 4437 html.push('\n'); 4438 } 4439 }, 4440 4441 text: function(text, raw) { 4442 if (text.length > 0) 4443 html[html.length] = raw ? text : encode(text); 4444 }, 4445 4446 cdata: function(text) { 4447 html.push('<![CDATA[', text, ']]>'); 4448 }, 4449 4450 comment: function(text) { 4451 html.push('<!--', text, '-->'); 4452 }, 4453 4454 pi: function(name, text) { 4455 if (text) 4456 html.push('<?', name, ' ', text, '?>'); 4457 else 4458 html.push('<?', name, '?>'); 4459 4460 if (indent) 4461 html.push('\n'); 4462 }, 4463 4464 doctype: function(text) { 4465 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4466 }, 4467 4468 reset: function() { 4469 html.length = 0; 4470 }, 4471 4472 getContent: function() { 4473 return html.join('').replace(/\n$/, ''); 4474 } 4475 }; 4476 }; 4477 (function(tinymce) { 4478 tinymce.html.Serializer = function(settings, schema) { 4479 var self = this, writer = new tinymce.html.Writer(settings); 4480 4481 settings = settings || {}; 4482 settings.validate = "validate" in settings ? settings.validate : true; 4483 4484 self.schema = schema = schema || new tinymce.html.Schema(); 4485 self.writer = writer; 4486 4487 self.serialize = function(node) { 4488 var handlers, validate; 4489 4490 validate = settings.validate; 4491 4492 handlers = { 4493 // #text 4494 3: function(node, raw) { 4495 writer.text(node.value, node.raw); 4496 }, 4497 4498 // #comment 4499 8: function(node) { 4500 writer.comment(node.value); 4501 }, 4502 4503 // Processing instruction 4504 7: function(node) { 4505 writer.pi(node.name, node.value); 4506 }, 4507 4508 // Doctype 4509 10: function(node) { 4510 writer.doctype(node.value); 4511 }, 4512 4513 // CDATA 4514 4: function(node) { 4515 writer.cdata(node.value); 4516 }, 4517 4518 // Document fragment 4519 11: function(node) { 4520 if ((node = node.firstChild)) { 4521 do { 4522 walk(node); 4523 } while (node = node.next); 4524 } 4525 } 4526 }; 4527 4528 writer.reset(); 4529 4530 function walk(node) { 4531 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4532 4533 if (!handler) { 4534 name = node.name; 4535 isEmpty = node.shortEnded; 4536 attrs = node.attributes; 4537 4538 // Sort attributes 4539 if (validate && attrs && attrs.length > 1) { 4540 sortedAttrs = []; 4541 sortedAttrs.map = {}; 4542 4543 elementRule = schema.getElementRule(node.name); 4544 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4545 attrName = elementRule.attributesOrder[i]; 4546 4547 if (attrName in attrs.map) { 4548 attrValue = attrs.map[attrName]; 4549 sortedAttrs.map[attrName] = attrValue; 4550 sortedAttrs.push({name: attrName, value: attrValue}); 4551 } 4552 } 4553 4554 for (i = 0, l = attrs.length; i < l; i++) { 4555 attrName = attrs[i].name; 4556 4557 if (!(attrName in sortedAttrs.map)) { 4558 attrValue = attrs.map[attrName]; 4559 sortedAttrs.map[attrName] = attrValue; 4560 sortedAttrs.push({name: attrName, value: attrValue}); 4561 } 4562 } 4563 4564 attrs = sortedAttrs; 4565 } 4566 4567 writer.start(node.name, attrs, isEmpty); 4568 4569 if (!isEmpty) { 4570 if ((node = node.firstChild)) { 4571 do { 4572 walk(node); 4573 } while (node = node.next); 4574 } 4575 4576 writer.end(name); 4577 } 4578 } else 4579 handler(node); 4580 } 4581 4582 // Serialize element and treat all non elements as fragments 4583 if (node.type == 1 && !settings.inner) 4584 walk(node); 4585 else 4586 handlers[11](node); 4587 4588 return writer.getContent(); 4589 }; 4590 } 4591 })(tinymce); 4592 // JSLint defined globals 4593 /*global tinymce:false, window:false */ 4594 4595 tinymce.dom = {}; 4596 4597 (function(namespace, expando) { 4598 var w3cEventModel = !!document.addEventListener; 4599 4600 function addEvent(target, name, callback, capture) { 4601 if (target.addEventListener) { 4602 target.addEventListener(name, callback, capture || false); 4603 } else if (target.attachEvent) { 4604 target.attachEvent('on' + name, callback); 4605 } 4606 } 4607 4608 function removeEvent(target, name, callback, capture) { 4609 if (target.removeEventListener) { 4610 target.removeEventListener(name, callback, capture || false); 4611 } else if (target.detachEvent) { 4612 target.detachEvent('on' + name, callback); 4613 } 4614 } 4615 4616 function fix(original_event, data) { 4617 var name, event = data || {}; 4618 4619 // Dummy function that gets replaced on the delegation state functions 4620 function returnFalse() { 4621 return false; 4622 } 4623 4624 // Dummy function that gets replaced on the delegation state functions 4625 function returnTrue() { 4626 return true; 4627 } 4628 4629 // Copy all properties from the original event 4630 for (name in original_event) { 4631 // layerX/layerY is deprecated in Chrome and produces a warning 4632 if (name !== "layerX" && name !== "layerY") { 4633 event[name] = original_event[name]; 4634 } 4635 } 4636 4637 // Normalize target IE uses srcElement 4638 if (!event.target) { 4639 event.target = event.srcElement || document; 4640 } 4641 4642 // Add preventDefault method 4643 event.preventDefault = function() { 4644 event.isDefaultPrevented = returnTrue; 4645 4646 // Execute preventDefault on the original event object 4647 if (original_event) { 4648 if (original_event.preventDefault) { 4649 original_event.preventDefault(); 4650 } else { 4651 original_event.returnValue = false; // IE 4652 } 4653 } 4654 }; 4655 4656 // Add stopPropagation 4657 event.stopPropagation = function() { 4658 event.isPropagationStopped = returnTrue; 4659 4660 // Execute stopPropagation on the original event object 4661 if (original_event) { 4662 if (original_event.stopPropagation) { 4663 original_event.stopPropagation(); 4664 } else { 4665 original_event.cancelBubble = true; // IE 4666 } 4667 } 4668 }; 4669 4670 // Add stopImmediatePropagation 4671 event.stopImmediatePropagation = function() { 4672 event.isImmediatePropagationStopped = returnTrue; 4673 event.stopPropagation(); 4674 }; 4675 4676 // Add event delegation states 4677 if (!event.isDefaultPrevented) { 4678 event.isDefaultPrevented = returnFalse; 4679 event.isPropagationStopped = returnFalse; 4680 event.isImmediatePropagationStopped = returnFalse; 4681 } 4682 4683 return event; 4684 } 4685 4686 function bindOnReady(win, callback, event_utils) { 4687 var doc = win.document, event = {type: 'ready'}; 4688 4689 // Gets called when the DOM is ready 4690 function readyHandler() { 4691 if (!event_utils.domLoaded) { 4692 event_utils.domLoaded = true; 4693 callback(event); 4694 } 4695 } 4696 4697 // Page already loaded then fire it directly 4698 if (doc.readyState == "complete") { 4699 readyHandler(); 4700 return; 4701 } 4702 4703 // Use W3C method 4704 if (w3cEventModel) { 4705 addEvent(win, 'DOMContentLoaded', readyHandler); 4706 } else { 4707 // Use IE method 4708 addEvent(doc, "readystatechange", function() { 4709 if (doc.readyState === "complete") { 4710 removeEvent(doc, "readystatechange", arguments.callee); 4711 readyHandler(); 4712 } 4713 }); 4714 4715 // Wait until we can scroll, when we can the DOM is initialized 4716 if (doc.documentElement.doScroll && win === win.top) { 4717 (function() { 4718 try { 4719 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4720 // http://javascript.nwbox.com/IEContentLoaded/ 4721 doc.documentElement.doScroll("left"); 4722 } catch (ex) { 4723 setTimeout(arguments.callee, 0); 4724 return; 4725 } 4726 4727 readyHandler(); 4728 })(); 4729 } 4730 } 4731 4732 // Fallback if any of the above methods should fail for some odd reason 4733 addEvent(win, 'load', readyHandler); 4734 } 4735 4736 function EventUtils(proxy) { 4737 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4738 4739 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4740 hasFocusIn = "onfocusin" in document.documentElement; 4741 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4742 count = 1; 4743 4744 // State if the DOMContentLoaded was executed or not 4745 self.domLoaded = false; 4746 self.events = events; 4747 4748 function executeHandlers(evt, id) { 4749 var callbackList, i, l, callback; 4750 4751 callbackList = events[id][evt.type]; 4752 if (callbackList) { 4753 for (i = 0, l = callbackList.length; i < l; i++) { 4754 callback = callbackList[i]; 4755 4756 // Check if callback exists might be removed if a unbind is called inside the callback 4757 if (callback && callback.func.call(callback.scope, evt) === false) { 4758 evt.preventDefault(); 4759 } 4760 4761 // Should we stop propagation to immediate listeners 4762 if (evt.isImmediatePropagationStopped()) { 4763 return; 4764 } 4765 } 4766 } 4767 } 4768 4769 self.bind = function(target, names, callback, scope) { 4770 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4771 4772 // Native event handler function patches the event and executes the callbacks for the expando 4773 function defaultNativeHandler(evt) { 4774 executeHandlers(fix(evt || win.event), id); 4775 } 4776 4777 // Don't bind to text nodes or comments 4778 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4779 return; 4780 } 4781 4782 // Create or get events id for the target 4783 if (!target[expando]) { 4784 id = count++; 4785 target[expando] = id; 4786 events[id] = {}; 4787 } else { 4788 id = target[expando]; 4789 4790 if (!events[id]) { 4791 events[id] = {}; 4792 } 4793 } 4794 4795 // Setup the specified scope or use the target as a default 4796 scope = scope || target; 4797 4798 // Split names and bind each event, enables you to bind multiple events with one call 4799 names = names.split(' '); 4800 i = names.length; 4801 while (i--) { 4802 name = names[i]; 4803 nativeHandler = defaultNativeHandler; 4804 fakeName = capture = false; 4805 4806 // Use ready instead of DOMContentLoaded 4807 if (name === "DOMContentLoaded") { 4808 name = "ready"; 4809 } 4810 4811 // DOM is already ready 4812 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4813 self.domLoaded = true; 4814 callback.call(scope, fix({type: name})); 4815 continue; 4816 } 4817 4818 // Handle mouseenter/mouseleaver 4819 if (!hasMouseEnterLeave) { 4820 fakeName = mouseEnterLeave[name]; 4821 4822 if (fakeName) { 4823 nativeHandler = function(evt) { 4824 var current, related; 4825 4826 current = evt.currentTarget; 4827 related = evt.relatedTarget; 4828 4829 // 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 4830 if (related && current.contains) { 4831 // Use contains for performance 4832 related = current.contains(related); 4833 } else { 4834 while (related && related !== current) { 4835 related = related.parentNode; 4836 } 4837 } 4838 4839 // Fire fake event 4840 if (!related) { 4841 evt = fix(evt || win.event); 4842 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4843 evt.target = current; 4844 executeHandlers(evt, id); 4845 } 4846 }; 4847 } 4848 } 4849 4850 // Fake bubbeling of focusin/focusout 4851 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4852 capture = true; 4853 fakeName = name === "focusin" ? "focus" : "blur"; 4854 nativeHandler = function(evt) { 4855 evt = fix(evt || win.event); 4856 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4857 executeHandlers(evt, id); 4858 }; 4859 } 4860 4861 // Setup callback list and bind native event 4862 callbackList = events[id][name]; 4863 if (!callbackList) { 4864 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4865 callbackList.fakeName = fakeName; 4866 callbackList.capture = capture; 4867 4868 // Add the nativeHandler to the callback list so that we can later unbind it 4869 callbackList.nativeHandler = nativeHandler; 4870 if (!w3cEventModel) { 4871 callbackList.proxyHandler = proxy(id); 4872 } 4873 4874 // Check if the target has native events support 4875 if (name === "ready") { 4876 bindOnReady(target, nativeHandler, self); 4877 } else { 4878 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4879 } 4880 } else { 4881 // If it already has an native handler then just push the callback 4882 callbackList.push({func: callback, scope: scope}); 4883 } 4884 } 4885 4886 target = callbackList = 0; // Clean memory for IE 4887 4888 return callback; 4889 }; 4890 4891 self.unbind = function(target, names, callback) { 4892 var id, callbackList, i, ci, name, eventMap; 4893 4894 // Don't bind to text nodes or comments 4895 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4896 return self; 4897 } 4898 4899 // Unbind event or events if the target has the expando 4900 id = target[expando]; 4901 if (id) { 4902 eventMap = events[id]; 4903 4904 // Specific callback 4905 if (names) { 4906 names = names.split(' '); 4907 i = names.length; 4908 while (i--) { 4909 name = names[i]; 4910 callbackList = eventMap[name]; 4911 4912 // Unbind the event if it exists in the map 4913 if (callbackList) { 4914 // Remove specified callback 4915 if (callback) { 4916 ci = callbackList.length; 4917 while (ci--) { 4918 if (callbackList[ci].func === callback) { 4919 callbackList.splice(ci, 1); 4920 } 4921 } 4922 } 4923 4924 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4925 if (!callback || callbackList.length === 0) { 4926 delete eventMap[name]; 4927 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4928 } 4929 } 4930 } 4931 } else { 4932 // All events for a specific element 4933 for (name in eventMap) { 4934 callbackList = eventMap[name]; 4935 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4936 } 4937 4938 eventMap = {}; 4939 } 4940 4941 // Check if object is empty, if it isn't then we won't remove the expando map 4942 for (name in eventMap) { 4943 return self; 4944 } 4945 4946 // Delete event object 4947 delete events[id]; 4948 4949 // Remove expando from target 4950 try { 4951 // IE will fail here since it can't delete properties from window 4952 delete target[expando]; 4953 } catch (ex) { 4954 // IE will set it to null 4955 target[expando] = null; 4956 } 4957 } 4958 4959 return self; 4960 }; 4961 4962 self.fire = function(target, name, args) { 4963 var id, event; 4964 4965 // Don't bind to text nodes or comments 4966 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4967 return self; 4968 } 4969 4970 // Build event object by patching the args 4971 event = fix(null, args); 4972 event.type = name; 4973 4974 do { 4975 // Found an expando that means there is listeners to execute 4976 id = target[expando]; 4977 if (id) { 4978 executeHandlers(event, id); 4979 } 4980 4981 // Walk up the DOM 4982 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4983 } while (target && !event.isPropagationStopped()); 4984 4985 return self; 4986 }; 4987 4988 self.clean = function(target) { 4989 var i, children, unbind = self.unbind; 4990 4991 // Don't bind to text nodes or comments 4992 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4993 return self; 4994 } 4995 4996 // Unbind any element on the specificed target 4997 if (target[expando]) { 4998 unbind(target); 4999 } 5000 5001 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 5002 if (!target.getElementsByTagName) { 5003 target = target.document; 5004 } 5005 5006 // Remove events from each child element 5007 if (target && target.getElementsByTagName) { 5008 unbind(target); 5009 5010 children = target.getElementsByTagName('*'); 5011 i = children.length; 5012 while (i--) { 5013 target = children[i]; 5014 5015 if (target[expando]) { 5016 unbind(target); 5017 } 5018 } 5019 } 5020 5021 return self; 5022 }; 5023 5024 self.callNativeHandler = function(id, evt) { 5025 if (events) { 5026 events[id][evt.type].nativeHandler(evt); 5027 } 5028 }; 5029 5030 self.destory = function() { 5031 events = {}; 5032 }; 5033 5034 // Legacy function calls 5035 5036 self.add = function(target, events, func, scope) { 5037 // Old API supported direct ID assignment 5038 if (typeof(target) === "string") { 5039 target = document.getElementById(target); 5040 } 5041 5042 // Old API supported multiple targets 5043 if (target && target instanceof Array) { 5044 var i = target.length; 5045 5046 while (i--) { 5047 self.add(target[i], events, func, scope); 5048 } 5049 5050 return; 5051 } 5052 5053 // Old API called ready init 5054 if (events === "init") { 5055 events = "ready"; 5056 } 5057 5058 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 5059 }; 5060 5061 self.remove = function(target, events, func, scope) { 5062 if (!target) { 5063 return self; 5064 } 5065 5066 // Old API supported direct ID assignment 5067 if (typeof(target) === "string") { 5068 target = document.getElementById(target); 5069 } 5070 5071 // Old API supported multiple targets 5072 if (target instanceof Array) { 5073 var i = target.length; 5074 5075 while (i--) { 5076 self.remove(target[i], events, func, scope); 5077 } 5078 5079 return self; 5080 } 5081 5082 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 5083 }; 5084 5085 self.clear = function(target) { 5086 // Old API supported direct ID assignment 5087 if (typeof(target) === "string") { 5088 target = document.getElementById(target); 5089 } 5090 5091 return self.clean(target); 5092 }; 5093 5094 self.cancel = function(e) { 5095 if (e) { 5096 self.prevent(e); 5097 self.stop(e); 5098 } 5099 5100 return false; 5101 }; 5102 5103 self.prevent = function(e) { 5104 if (!e.preventDefault) { 5105 e = fix(e); 5106 } 5107 5108 e.preventDefault(); 5109 5110 return false; 5111 }; 5112 5113 self.stop = function(e) { 5114 if (!e.stopPropagation) { 5115 e = fix(e); 5116 } 5117 5118 e.stopPropagation(); 5119 5120 return false; 5121 }; 5122 } 5123 5124 namespace.EventUtils = EventUtils; 5125 5126 namespace.Event = new EventUtils(function(id) { 5127 return function(evt) { 5128 tinymce.dom.Event.callNativeHandler(id, evt); 5129 }; 5130 }); 5131 5132 // Bind ready event when tinymce script is loaded 5133 namespace.Event.bind(window, 'ready', function() {}); 5134 5135 namespace = 0; 5136 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 5137 tinymce.dom.TreeWalker = function(start_node, root_node) { 5138 var node = start_node; 5139 5140 function findSibling(node, start_name, sibling_name, shallow) { 5141 var sibling, parent; 5142 5143 if (node) { 5144 // Walk into nodes if it has a start 5145 if (!shallow && node[start_name]) 5146 return node[start_name]; 5147 5148 // Return the sibling if it has one 5149 if (node != root_node) { 5150 sibling = node[sibling_name]; 5151 if (sibling) 5152 return sibling; 5153 5154 // Walk up the parents to look for siblings 5155 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 5156 sibling = parent[sibling_name]; 5157 if (sibling) 5158 return sibling; 5159 } 5160 } 5161 } 5162 }; 5163 5164 this.current = function() { 5165 return node; 5166 }; 5167 5168 this.next = function(shallow) { 5169 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 5170 }; 5171 5172 this.prev = function(shallow) { 5173 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 5174 }; 5175 }; 5176 (function(tinymce) { 5177 // Shorten names 5178 var each = tinymce.each, 5179 is = tinymce.is, 5180 isWebKit = tinymce.isWebKit, 5181 isIE = tinymce.isIE, 5182 Entities = tinymce.html.Entities, 5183 simpleSelectorRe = /^([a-z0-9],?)+$/i, 5184 whiteSpaceRegExp = /^[ \t\r\n]*$/; 5185 5186 tinymce.create('tinymce.dom.DOMUtils', { 5187 doc : null, 5188 root : null, 5189 files : null, 5190 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 5191 props : { 5192 "for" : "htmlFor", 5193 "class" : "className", 5194 className : "className", 5195 checked : "checked", 5196 disabled : "disabled", 5197 maxlength : "maxLength", 5198 readonly : "readOnly", 5199 selected : "selected", 5200 value : "value", 5201 id : "id", 5202 name : "name", 5203 type : "type" 5204 }, 5205 5206 DOMUtils : function(d, s) { 5207 var t = this, globalStyle, name, blockElementsMap; 5208 5209 t.doc = d; 5210 t.win = window; 5211 t.files = {}; 5212 t.cssFlicker = false; 5213 t.counter = 0; 5214 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 5215 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 5216 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 5217 5218 t.settings = s = tinymce.extend({ 5219 keep_values : false, 5220 hex_colors : 1 5221 }, s); 5222 5223 t.schema = s.schema; 5224 t.styles = new tinymce.html.Styles({ 5225 url_converter : s.url_converter, 5226 url_converter_scope : s.url_converter_scope 5227 }, s.schema); 5228 5229 // Fix IE6SP2 flicker and check it failed for pre SP2 5230 if (tinymce.isIE6) { 5231 try { 5232 d.execCommand('BackgroundImageCache', false, true); 5233 } catch (e) { 5234 t.cssFlicker = true; 5235 } 5236 } 5237 5238 t.fixDoc(d); 5239 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 5240 tinymce.addUnload(t.destroy, t); 5241 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 5242 5243 t.isBlock = function(node) { 5244 // Fix for #5446 5245 if (!node) { 5246 return false; 5247 } 5248 5249 // This function is called in module pattern style since it might be executed with the wrong this scope 5250 var type = node.nodeType; 5251 5252 // If it's a node then check the type and use the nodeName 5253 if (type) 5254 return !!(type === 1 && blockElementsMap[node.nodeName]); 5255 5256 return !!blockElementsMap[node]; 5257 }; 5258 }, 5259 5260 fixDoc: function(doc) { 5261 var settings = this.settings, name; 5262 5263 if (isIE && !tinymce.isIE11 && settings.schema) { 5264 // Add missing HTML 4/5 elements to IE 5265 ('abbr article aside audio canvas ' + 5266 'details figcaption figure footer ' + 5267 'header hgroup mark menu meter nav ' + 5268 'output progress section summary ' + 5269 'time video').replace(/\w+/g, function(name) { 5270 doc.createElement(name); 5271 }); 5272 5273 // Create all custom elements 5274 for (name in settings.schema.getCustomElements()) { 5275 doc.createElement(name); 5276 } 5277 } 5278 }, 5279 5280 clone: function(node, deep) { 5281 var self = this, clone, doc; 5282 5283 // TODO: Add feature detection here in the future 5284 if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) { 5285 return node.cloneNode(deep); 5286 } 5287 5288 doc = self.doc; 5289 5290 // Make a HTML5 safe shallow copy 5291 if (!deep) { 5292 clone = doc.createElement(node.nodeName); 5293 5294 // Copy attribs 5295 each(self.getAttribs(node), function(attr) { 5296 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5297 }); 5298 5299 return clone; 5300 } 5301 /* 5302 // Setup HTML5 patched document fragment 5303 if (!self.frag) { 5304 self.frag = doc.createDocumentFragment(); 5305 self.fixDoc(self.frag); 5306 } 5307 5308 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5309 clone = doc.createElement('div'); 5310 self.frag.appendChild(clone); 5311 clone.innerHTML = node.outerHTML; 5312 self.frag.removeChild(clone); 5313 */ 5314 return clone.firstChild; 5315 }, 5316 5317 getRoot : function() { 5318 var t = this, s = t.settings; 5319 5320 return (s && t.get(s.root_element)) || t.doc.body; 5321 }, 5322 5323 getViewPort : function(w) { 5324 var d, b; 5325 5326 w = !w ? this.win : w; 5327 d = w.document; 5328 b = this.boxModel ? d.documentElement : d.body; 5329 5330 // Returns viewport size excluding scrollbars 5331 return { 5332 x : w.pageXOffset || b.scrollLeft, 5333 y : w.pageYOffset || b.scrollTop, 5334 w : w.innerWidth || b.clientWidth, 5335 h : w.innerHeight || b.clientHeight 5336 }; 5337 }, 5338 5339 getRect : function(e) { 5340 var p, t = this, sr; 5341 5342 e = t.get(e); 5343 p = t.getPos(e); 5344 sr = t.getSize(e); 5345 5346 return { 5347 x : p.x, 5348 y : p.y, 5349 w : sr.w, 5350 h : sr.h 5351 }; 5352 }, 5353 5354 getSize : function(e) { 5355 var t = this, w, h; 5356 5357 e = t.get(e); 5358 w = t.getStyle(e, 'width'); 5359 h = t.getStyle(e, 'height'); 5360 5361 // Non pixel value, then force offset/clientWidth 5362 if (w.indexOf('px') === -1) 5363 w = 0; 5364 5365 // Non pixel value, then force offset/clientWidth 5366 if (h.indexOf('px') === -1) 5367 h = 0; 5368 5369 return { 5370 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5371 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5372 }; 5373 }, 5374 5375 getParent : function(n, f, r) { 5376 return this.getParents(n, f, r, false); 5377 }, 5378 5379 getParents : function(n, f, r, c) { 5380 var t = this, na, se = t.settings, o = []; 5381 5382 n = t.get(n); 5383 c = c === undefined; 5384 5385 if (se.strict_root) 5386 r = r || t.getRoot(); 5387 5388 // Wrap node name as func 5389 if (is(f, 'string')) { 5390 na = f; 5391 5392 if (f === '*') { 5393 f = function(n) {return n.nodeType == 1;}; 5394 } else { 5395 f = function(n) { 5396 return t.is(n, na); 5397 }; 5398 } 5399 } 5400 5401 while (n) { 5402 if (n == r || !n.nodeType || n.nodeType === 9) 5403 break; 5404 5405 if (!f || f(n)) { 5406 if (c) 5407 o.push(n); 5408 else 5409 return n; 5410 } 5411 5412 n = n.parentNode; 5413 } 5414 5415 return c ? o : null; 5416 }, 5417 5418 get : function(e) { 5419 var n; 5420 5421 if (e && this.doc && typeof(e) == 'string') { 5422 n = e; 5423 e = this.doc.getElementById(e); 5424 5425 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5426 if (e && e.id !== n) 5427 return this.doc.getElementsByName(n)[1]; 5428 } 5429 5430 return e; 5431 }, 5432 5433 getNext : function(node, selector) { 5434 return this._findSib(node, selector, 'nextSibling'); 5435 }, 5436 5437 getPrev : function(node, selector) { 5438 return this._findSib(node, selector, 'previousSibling'); 5439 }, 5440 5441 5442 select : function(pa, s) { 5443 var t = this; 5444 5445 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5446 }, 5447 5448 is : function(n, selector) { 5449 var i; 5450 5451 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5452 if (n.length === undefined) { 5453 // Simple all selector 5454 if (selector === '*') 5455 return n.nodeType == 1; 5456 5457 // Simple selector just elements 5458 if (simpleSelectorRe.test(selector)) { 5459 selector = selector.toLowerCase().split(/,/); 5460 n = n.nodeName.toLowerCase(); 5461 5462 for (i = selector.length - 1; i >= 0; i--) { 5463 if (selector[i] == n) 5464 return true; 5465 } 5466 5467 return false; 5468 } 5469 } 5470 5471 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5472 }, 5473 5474 5475 add : function(p, n, a, h, c) { 5476 var t = this; 5477 5478 return this.run(p, function(p) { 5479 var e, k; 5480 5481 e = is(n, 'string') ? t.doc.createElement(n) : n; 5482 t.setAttribs(e, a); 5483 5484 if (h) { 5485 if (h.nodeType) 5486 e.appendChild(h); 5487 else 5488 t.setHTML(e, h); 5489 } 5490 5491 return !c ? p.appendChild(e) : e; 5492 }); 5493 }, 5494 5495 create : function(n, a, h) { 5496 return this.add(this.doc.createElement(n), n, a, h, 1); 5497 }, 5498 5499 createHTML : function(n, a, h) { 5500 var o = '', t = this, k; 5501 5502 o += '<' + n; 5503 5504 for (k in a) { 5505 if (a.hasOwnProperty(k)) 5506 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5507 } 5508 5509 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5510 if (typeof(h) != "undefined") 5511 return o + '>' + h + '</' + n + '>'; 5512 5513 return o + ' />'; 5514 }, 5515 5516 remove : function(node, keep_children) { 5517 return this.run(node, function(node) { 5518 var child, parent = node.parentNode; 5519 5520 if (!parent) 5521 return null; 5522 5523 if (keep_children) { 5524 while (child = node.firstChild) { 5525 // IE 8 will crash if you don't remove completely empty text nodes 5526 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5527 parent.insertBefore(child, node); 5528 else 5529 node.removeChild(child); 5530 } 5531 } 5532 5533 return parent.removeChild(node); 5534 }); 5535 }, 5536 5537 setStyle : function(n, na, v) { 5538 var t = this; 5539 5540 return t.run(n, function(e) { 5541 var s, i; 5542 5543 s = e.style; 5544 5545 // Camelcase it, if needed 5546 na = na.replace(/-(\D)/g, function(a, b){ 5547 return b.toUpperCase(); 5548 }); 5549 5550 // Default px suffix on these 5551 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5552 v += 'px'; 5553 5554 switch (na) { 5555 case 'opacity': 5556 // IE specific opacity 5557 if (isIE && ! tinymce.isIE11) { 5558 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5559 5560 if (!n.currentStyle || !n.currentStyle.hasLayout) 5561 s.display = 'inline-block'; 5562 } 5563 5564 // Fix for older browsers 5565 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5566 break; 5567 5568 case 'float': 5569 (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v; 5570 break; 5571 5572 default: 5573 s[na] = v || ''; 5574 } 5575 5576 // Force update of the style data 5577 if (t.settings.update_styles) 5578 t.setAttrib(e, 'data-mce-style'); 5579 }); 5580 }, 5581 5582 getStyle : function(n, na, c) { 5583 n = this.get(n); 5584 5585 if (!n) 5586 return; 5587 5588 // Gecko 5589 if (this.doc.defaultView && c) { 5590 // Remove camelcase 5591 na = na.replace(/[A-Z]/g, function(a){ 5592 return '-' + a; 5593 }); 5594 5595 try { 5596 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5597 } catch (ex) { 5598 // Old safari might fail 5599 return null; 5600 } 5601 } 5602 5603 // Camelcase it, if needed 5604 na = na.replace(/-(\D)/g, function(a, b){ 5605 return b.toUpperCase(); 5606 }); 5607 5608 if (na == 'float') 5609 na = isIE ? 'styleFloat' : 'cssFloat'; 5610 5611 // IE & Opera 5612 if (n.currentStyle && c) 5613 return n.currentStyle[na]; 5614 5615 return n.style ? n.style[na] : undefined; 5616 }, 5617 5618 setStyles : function(e, o) { 5619 var t = this, s = t.settings, ol; 5620 5621 ol = s.update_styles; 5622 s.update_styles = 0; 5623 5624 each(o, function(v, n) { 5625 t.setStyle(e, n, v); 5626 }); 5627 5628 // Update style info 5629 s.update_styles = ol; 5630 if (s.update_styles) 5631 t.setAttrib(e, s.cssText); 5632 }, 5633 5634 removeAllAttribs: function(e) { 5635 return this.run(e, function(e) { 5636 var i, attrs = e.attributes; 5637 for (i = attrs.length - 1; i >= 0; i--) { 5638 e.removeAttributeNode(attrs.item(i)); 5639 } 5640 }); 5641 }, 5642 5643 setAttrib : function(e, n, v) { 5644 var t = this; 5645 5646 // Whats the point 5647 if (!e || !n) 5648 return; 5649 5650 // Strict XML mode 5651 if (t.settings.strict) 5652 n = n.toLowerCase(); 5653 5654 return this.run(e, function(e) { 5655 var s = t.settings; 5656 var originalValue = e.getAttribute(n); 5657 if (v !== null) { 5658 switch (n) { 5659 case "style": 5660 if (!is(v, 'string')) { 5661 each(v, function(v, n) { 5662 t.setStyle(e, n, v); 5663 }); 5664 5665 return; 5666 } 5667 5668 // No mce_style for elements with these since they might get resized by the user 5669 if (s.keep_values) { 5670 if (v && !t._isRes(v)) 5671 e.setAttribute('data-mce-style', v, 2); 5672 else 5673 e.removeAttribute('data-mce-style', 2); 5674 } 5675 5676 e.style.cssText = v; 5677 break; 5678 5679 case "class": 5680 e.className = v || ''; // Fix IE null bug 5681 break; 5682 5683 case "src": 5684 case "href": 5685 if (s.keep_values) { 5686 if (s.url_converter) 5687 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5688 5689 t.setAttrib(e, 'data-mce-' + n, v, 2); 5690 } 5691 5692 break; 5693 5694 case "shape": 5695 e.setAttribute('data-mce-style', v); 5696 break; 5697 } 5698 } 5699 if (is(v) && v !== null && v.length !== 0) 5700 e.setAttribute(n, '' + v, 2); 5701 else 5702 e.removeAttribute(n, 2); 5703 5704 // fire onChangeAttrib event for attributes that have changed 5705 if (tinyMCE.activeEditor && originalValue != v) { 5706 var ed = tinyMCE.activeEditor; 5707 ed.onSetAttrib.dispatch(ed, e, n, v); 5708 } 5709 }); 5710 }, 5711 5712 setAttribs : function(e, o) { 5713 var t = this; 5714 5715 return this.run(e, function(e) { 5716 each(o, function(v, n) { 5717 t.setAttrib(e, n, v); 5718 }); 5719 }); 5720 }, 5721 5722 getAttrib : function(e, n, dv) { 5723 var v, t = this, undef; 5724 5725 e = t.get(e); 5726 5727 if (!e || e.nodeType !== 1) 5728 return dv === undef ? false : dv; 5729 5730 if (!is(dv)) 5731 dv = ''; 5732 5733 // Try the mce variant for these 5734 if (/^(src|href|style|coords|shape)$/.test(n)) { 5735 v = e.getAttribute("data-mce-" + n); 5736 5737 if (v) 5738 return v; 5739 } 5740 5741 if (isIE && t.props[n]) { 5742 v = e[t.props[n]]; 5743 v = v && v.nodeValue ? v.nodeValue : v; 5744 } 5745 5746 if (!v) 5747 v = e.getAttribute(n, 2); 5748 5749 // Check boolean attribs 5750 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5751 if (e[t.props[n]] === true && v === '') 5752 return n; 5753 5754 return v ? n : ''; 5755 } 5756 5757 // Inner input elements will override attributes on form elements 5758 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5759 return e.getAttributeNode(n).nodeValue; 5760 5761 if (n === 'style') { 5762 v = v || e.style.cssText; 5763 5764 if (v) { 5765 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5766 5767 if (t.settings.keep_values && !t._isRes(v)) 5768 e.setAttribute('data-mce-style', v); 5769 } 5770 } 5771 5772 // Remove Apple and WebKit stuff 5773 if (isWebKit && n === "class" && v) 5774 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5775 5776 // Handle IE issues 5777 if (isIE) { 5778 switch (n) { 5779 case 'rowspan': 5780 case 'colspan': 5781 // IE returns 1 as default value 5782 if (v === 1) 5783 v = ''; 5784 5785 break; 5786 5787 case 'size': 5788 // IE returns +0 as default value for size 5789 if (v === '+0' || v === 20 || v === 0) 5790 v = ''; 5791 5792 break; 5793 5794 case 'width': 5795 case 'height': 5796 case 'vspace': 5797 case 'checked': 5798 case 'disabled': 5799 case 'readonly': 5800 if (v === 0) 5801 v = ''; 5802 5803 break; 5804 5805 case 'hspace': 5806 // IE returns -1 as default value 5807 if (v === -1) 5808 v = ''; 5809 5810 break; 5811 5812 case 'maxlength': 5813 case 'tabindex': 5814 // IE returns default value 5815 if (v === 32768 || v === 2147483647 || v === '32768') 5816 v = ''; 5817 5818 break; 5819 5820 case 'multiple': 5821 case 'compact': 5822 case 'noshade': 5823 case 'nowrap': 5824 if (v === 65535) 5825 return n; 5826 5827 return dv; 5828 5829 case 'shape': 5830 v = v.toLowerCase(); 5831 break; 5832 5833 default: 5834 // IE has odd anonymous function for event attributes 5835 if (n.indexOf('on') === 0 && v) 5836 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5837 } 5838 } 5839 5840 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5841 }, 5842 5843 getPos : function(n, ro) { 5844 var t = this, x = 0, y = 0, e, d = t.doc, r; 5845 5846 n = t.get(n); 5847 ro = ro || d.body; 5848 5849 if (n) { 5850 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5851 if (n.getBoundingClientRect) { 5852 n = n.getBoundingClientRect(); 5853 e = t.boxModel ? d.documentElement : d.body; 5854 5855 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5856 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5857 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5858 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5859 5860 return {x : x, y : y}; 5861 } 5862 5863 r = n; 5864 while (r && r != ro && r.nodeType) { 5865 x += r.offsetLeft || 0; 5866 y += r.offsetTop || 0; 5867 r = r.offsetParent; 5868 } 5869 5870 r = n.parentNode; 5871 while (r && r != ro && r.nodeType) { 5872 x -= r.scrollLeft || 0; 5873 y -= r.scrollTop || 0; 5874 r = r.parentNode; 5875 } 5876 } 5877 5878 return {x : x, y : y}; 5879 }, 5880 5881 parseStyle : function(st) { 5882 return this.styles.parse(st); 5883 }, 5884 5885 serializeStyle : function(o, name) { 5886 return this.styles.serialize(o, name); 5887 }, 5888 5889 addStyle: function(cssText) { 5890 var doc = this.doc, head; 5891 5892 // Create style element if needed 5893 styleElm = doc.getElementById('mceDefaultStyles'); 5894 if (!styleElm) { 5895 styleElm = doc.createElement('style'), 5896 styleElm.id = 'mceDefaultStyles'; 5897 styleElm.type = 'text/css'; 5898 5899 head = doc.getElementsByTagName('head')[0]; 5900 if (head.firstChild) { 5901 head.insertBefore(styleElm, head.firstChild); 5902 } else { 5903 head.appendChild(styleElm); 5904 } 5905 } 5906 5907 // Append style data to old or new style element 5908 if (styleElm.styleSheet) { 5909 styleElm.styleSheet.cssText += cssText; 5910 } else { 5911 styleElm.appendChild(doc.createTextNode(cssText)); 5912 } 5913 }, 5914 5915 loadCSS : function(u) { 5916 var t = this, d = t.doc, head; 5917 5918 if (!u) 5919 u = ''; 5920 5921 head = d.getElementsByTagName('head')[0]; 5922 5923 each(u.split(','), function(u) { 5924 var link; 5925 5926 if (t.files[u]) 5927 return; 5928 5929 t.files[u] = true; 5930 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5931 5932 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5933 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5934 // It's ugly but it seems to work fine. 5935 if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) { 5936 link.onload = function() { 5937 if (d.recalc) 5938 d.recalc(); 5939 5940 link.onload = null; 5941 }; 5942 } 5943 5944 head.appendChild(link); 5945 }); 5946 }, 5947 5948 addClass : function(e, c) { 5949 return this.run(e, function(e) { 5950 var o; 5951 5952 if (!c) 5953 return 0; 5954 5955 if (this.hasClass(e, c)) 5956 return e.className; 5957 5958 o = this.removeClass(e, c); 5959 5960 return e.className = (o != '' ? (o + ' ') : '') + c; 5961 }); 5962 }, 5963 5964 removeClass : function(e, c) { 5965 var t = this, re; 5966 5967 return t.run(e, function(e) { 5968 var v; 5969 5970 if (t.hasClass(e, c)) { 5971 if (!re) 5972 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5973 5974 v = e.className.replace(re, ' '); 5975 v = tinymce.trim(v != ' ' ? v : ''); 5976 5977 e.className = v; 5978 5979 // Empty class attr 5980 if (!v) { 5981 e.removeAttribute('class'); 5982 e.removeAttribute('className'); 5983 } 5984 5985 return v; 5986 } 5987 5988 return e.className; 5989 }); 5990 }, 5991 5992 hasClass : function(n, c) { 5993 n = this.get(n); 5994 5995 if (!n || !c) 5996 return false; 5997 5998 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5999 }, 6000 6001 show : function(e) { 6002 return this.setStyle(e, 'display', 'block'); 6003 }, 6004 6005 hide : function(e) { 6006 return this.setStyle(e, 'display', 'none'); 6007 }, 6008 6009 isHidden : function(e) { 6010 e = this.get(e); 6011 6012 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 6013 }, 6014 6015 uniqueId : function(p) { 6016 return (!p ? 'mce_' : p) + (this.counter++); 6017 }, 6018 6019 setHTML : function(element, html) { 6020 var self = this; 6021 6022 return self.run(element, function(element) { 6023 if (isIE) { 6024 // Remove all child nodes, IE keeps empty text nodes in DOM 6025 while (element.firstChild) 6026 element.removeChild(element.firstChild); 6027 6028 try { 6029 // IE will remove comments from the beginning 6030 // unless you padd the contents with something 6031 element.innerHTML = '<br />' + html; 6032 element.removeChild(element.firstChild); 6033 } catch (ex) { 6034 // 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 6035 // This seems to fix this problem 6036 6037 // Create new div with HTML contents and a BR infront to keep comments 6038 var newElement = self.create('div'); 6039 newElement.innerHTML = '<br />' + html; 6040 6041 // Add all children from div to target 6042 each (tinymce.grep(newElement.childNodes), function(node, i) { 6043 // Skip br element 6044 if (i && element.canHaveHTML) 6045 element.appendChild(node); 6046 }); 6047 } 6048 } else 6049 element.innerHTML = html; 6050 6051 return html; 6052 }); 6053 }, 6054 6055 getOuterHTML : function(elm) { 6056 var doc, self = this; 6057 6058 elm = self.get(elm); 6059 6060 if (!elm) 6061 return null; 6062 6063 if (elm.nodeType === 1 && self.hasOuterHTML) 6064 return elm.outerHTML; 6065 6066 doc = (elm.ownerDocument || self.doc).createElement("body"); 6067 doc.appendChild(elm.cloneNode(true)); 6068 6069 return doc.innerHTML; 6070 }, 6071 6072 setOuterHTML : function(e, h, d) { 6073 var t = this; 6074 6075 function setHTML(e, h, d) { 6076 var n, tp; 6077 6078 tp = d.createElement("body"); 6079 tp.innerHTML = h; 6080 6081 n = tp.lastChild; 6082 while (n) { 6083 t.insertAfter(n.cloneNode(true), e); 6084 n = n.previousSibling; 6085 } 6086 6087 t.remove(e); 6088 }; 6089 6090 return this.run(e, function(e) { 6091 e = t.get(e); 6092 6093 // Only set HTML on elements 6094 if (e.nodeType == 1) { 6095 d = d || e.ownerDocument || t.doc; 6096 6097 if (isIE) { 6098 try { 6099 // Try outerHTML for IE it sometimes produces an unknown runtime error 6100 if (isIE && e.nodeType == 1) 6101 e.outerHTML = h; 6102 else 6103 setHTML(e, h, d); 6104 } catch (ex) { 6105 // Fix for unknown runtime error 6106 setHTML(e, h, d); 6107 } 6108 } else 6109 setHTML(e, h, d); 6110 } 6111 }); 6112 }, 6113 6114 decode : Entities.decode, 6115 6116 encode : Entities.encodeAllRaw, 6117 6118 insertAfter : function(node, reference_node) { 6119 reference_node = this.get(reference_node); 6120 6121 return this.run(node, function(node) { 6122 var parent, nextSibling; 6123 6124 parent = reference_node.parentNode; 6125 nextSibling = reference_node.nextSibling; 6126 6127 if (nextSibling) 6128 parent.insertBefore(node, nextSibling); 6129 else 6130 parent.appendChild(node); 6131 6132 return node; 6133 }); 6134 }, 6135 6136 replace : function(n, o, k) { 6137 var t = this; 6138 6139 if (is(o, 'array')) 6140 n = n.cloneNode(true); 6141 6142 return t.run(o, function(o) { 6143 if (k) { 6144 each(tinymce.grep(o.childNodes), function(c) { 6145 n.appendChild(c); 6146 }); 6147 } 6148 6149 return o.parentNode.replaceChild(n, o); 6150 }); 6151 }, 6152 6153 rename : function(elm, name) { 6154 var t = this, newElm; 6155 6156 if (elm.nodeName != name.toUpperCase()) { 6157 // Rename block element 6158 newElm = t.create(name); 6159 6160 // Copy attribs to new block 6161 each(t.getAttribs(elm), function(attr_node) { 6162 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 6163 }); 6164 6165 // Replace block 6166 t.replace(newElm, elm, 1); 6167 } 6168 6169 return newElm || elm; 6170 }, 6171 6172 findCommonAncestor : function(a, b) { 6173 var ps = a, pe; 6174 6175 while (ps) { 6176 pe = b; 6177 6178 while (pe && ps != pe) 6179 pe = pe.parentNode; 6180 6181 if (ps == pe) 6182 break; 6183 6184 ps = ps.parentNode; 6185 } 6186 6187 if (!ps && a.ownerDocument) 6188 return a.ownerDocument.documentElement; 6189 6190 return ps; 6191 }, 6192 6193 toHex : function(s) { 6194 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 6195 6196 function hex(s) { 6197 s = parseInt(s, 10).toString(16); 6198 6199 return s.length > 1 ? s : '0' + s; // 0 -> 00 6200 }; 6201 6202 if (c) { 6203 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 6204 6205 return s; 6206 } 6207 6208 return s; 6209 }, 6210 6211 getClasses : function() { 6212 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 6213 6214 if (t.classes) 6215 return t.classes; 6216 6217 function addClasses(s) { 6218 // IE style imports 6219 each(s.imports, function(r) { 6220 addClasses(r); 6221 }); 6222 6223 each(s.cssRules || s.rules, function(r) { 6224 // Real type or fake it on IE 6225 switch (r.type || 1) { 6226 // Rule 6227 case 1: 6228 if (r.selectorText) { 6229 each(r.selectorText.split(','), function(v) { 6230 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 6231 6232 // Is internal or it doesn't contain a class 6233 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 6234 return; 6235 6236 // Remove everything but class name 6237 ov = v; 6238 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 6239 6240 // Filter classes 6241 if (f && !(v = f(v, ov))) 6242 return; 6243 6244 if (!lo[v]) { 6245 cl.push({'class' : v}); 6246 lo[v] = 1; 6247 } 6248 }); 6249 } 6250 break; 6251 6252 // Import 6253 case 3: 6254 try { 6255 addClasses(r.styleSheet); 6256 } catch (ex) { 6257 // Ignore 6258 } 6259 6260 break; 6261 } 6262 }); 6263 }; 6264 6265 try { 6266 each(t.doc.styleSheets, addClasses); 6267 } catch (ex) { 6268 // Ignore 6269 } 6270 6271 if (cl.length > 0) 6272 t.classes = cl; 6273 6274 return cl; 6275 }, 6276 6277 run : function(e, f, s) { 6278 var t = this, o; 6279 6280 if (t.doc && typeof(e) === 'string') 6281 e = t.get(e); 6282 6283 if (!e) 6284 return false; 6285 6286 s = s || this; 6287 if (!e.nodeType && (e.length || e.length === 0)) { 6288 o = []; 6289 6290 each(e, function(e, i) { 6291 if (e) { 6292 if (typeof(e) == 'string') 6293 e = t.doc.getElementById(e); 6294 6295 o.push(f.call(s, e, i)); 6296 } 6297 }); 6298 6299 return o; 6300 } 6301 6302 return f.call(s, e); 6303 }, 6304 6305 getAttribs : function(n) { 6306 var o; 6307 6308 n = this.get(n); 6309 6310 if (!n) 6311 return []; 6312 6313 if (isIE) { 6314 o = []; 6315 6316 // Object will throw exception in IE 6317 if (n.nodeName == 'OBJECT') 6318 return n.attributes; 6319 6320 // IE doesn't keep the selected attribute if you clone option elements 6321 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6322 o.push({specified : 1, nodeName : 'selected'}); 6323 6324 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6325 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6326 o.push({specified : 1, nodeName : a}); 6327 }); 6328 6329 return o; 6330 } 6331 6332 return n.attributes; 6333 }, 6334 6335 isEmpty : function(node, elements) { 6336 var self = this, i, attributes, type, walker, name, brCount = 0; 6337 6338 node = node.firstChild; 6339 if (node) { 6340 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6341 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6342 6343 do { 6344 type = node.nodeType; 6345 6346 if (type === 1) { 6347 // Ignore bogus elements 6348 if (node.getAttribute('data-mce-bogus')) 6349 continue; 6350 6351 // Keep empty elements like <img /> 6352 name = node.nodeName.toLowerCase(); 6353 if (elements && elements[name]) { 6354 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6355 if (name === 'br') { 6356 brCount++; 6357 continue; 6358 } 6359 6360 return false; 6361 } 6362 6363 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6364 attributes = self.getAttribs(node); 6365 i = node.attributes.length; 6366 while (i--) { 6367 name = node.attributes[i].nodeName; 6368 if (name === "name" || name === 'data-mce-bookmark') 6369 return false; 6370 } 6371 } 6372 6373 // Keep comment nodes 6374 if (type == 8) 6375 return false; 6376 6377 // Keep non whitespace text nodes 6378 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6379 return false; 6380 } while (node = walker.next()); 6381 } 6382 6383 return brCount <= 1; 6384 }, 6385 6386 destroy : function(s) { 6387 var t = this; 6388 6389 t.win = t.doc = t.root = t.events = t.frag = null; 6390 6391 // Manual destroy then remove unload handler 6392 if (!s) 6393 tinymce.removeUnload(t.destroy); 6394 }, 6395 6396 createRng : function() { 6397 var d = this.doc; 6398 6399 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6400 }, 6401 6402 nodeIndex : function(node, normalized) { 6403 var idx = 0, lastNodeType, lastNode, nodeType; 6404 6405 if (node) { 6406 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6407 nodeType = node.nodeType; 6408 6409 // Normalize text nodes 6410 if (normalized && nodeType == 3) { 6411 if (nodeType == lastNodeType || !node.nodeValue.length) 6412 continue; 6413 } 6414 idx++; 6415 lastNodeType = nodeType; 6416 } 6417 } 6418 6419 return idx; 6420 }, 6421 6422 split : function(pe, e, re) { 6423 var t = this, r = t.createRng(), bef, aft, pa; 6424 6425 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6426 // but we don't want that in our code since it serves no purpose for the end user 6427 // For example if this is chopped: 6428 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6429 // would produce: 6430 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6431 // this function will then trim of empty edges and produce: 6432 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6433 function trim(node) { 6434 var i, children = node.childNodes, type = node.nodeType; 6435 6436 function surroundedBySpans(node) { 6437 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6438 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6439 return previousIsSpan && nextIsSpan; 6440 } 6441 6442 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6443 return; 6444 6445 for (i = children.length - 1; i >= 0; i--) 6446 trim(children[i]); 6447 6448 if (type != 9) { 6449 // Keep non whitespace text nodes 6450 if (type == 3 && node.nodeValue.length > 0) { 6451 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6452 // Also keep text nodes with only spaces if surrounded by spans. 6453 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6454 var trimmedLength = tinymce.trim(node.nodeValue).length; 6455 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6456 return; 6457 } else if (type == 1) { 6458 // If the only child is a bookmark then move it up 6459 children = node.childNodes; 6460 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6461 node.parentNode.insertBefore(children[0], node); 6462 6463 // Keep non empty elements or img, hr etc 6464 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6465 return; 6466 } 6467 6468 t.remove(node); 6469 } 6470 6471 return node; 6472 }; 6473 6474 if (pe && e) { 6475 // Get before chunk 6476 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6477 r.setEnd(e.parentNode, t.nodeIndex(e)); 6478 bef = r.extractContents(); 6479 6480 // Get after chunk 6481 r = t.createRng(); 6482 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6483 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6484 aft = r.extractContents(); 6485 6486 // Insert before chunk 6487 pa = pe.parentNode; 6488 pa.insertBefore(trim(bef), pe); 6489 6490 // Insert middle chunk 6491 if (re) 6492 pa.replaceChild(re, e); 6493 else 6494 pa.insertBefore(e, pe); 6495 6496 // Insert after chunk 6497 pa.insertBefore(trim(aft), pe); 6498 t.remove(pe); 6499 6500 return re || e; 6501 } 6502 }, 6503 6504 bind : function(target, name, func, scope) { 6505 return this.events.add(target, name, func, scope || this); 6506 }, 6507 6508 unbind : function(target, name, func) { 6509 return this.events.remove(target, name, func); 6510 }, 6511 6512 fire : function(target, name, evt) { 6513 return this.events.fire(target, name, evt); 6514 }, 6515 6516 // Returns the content editable state of a node 6517 getContentEditable: function(node) { 6518 var contentEditable; 6519 6520 // Check type 6521 if (node.nodeType != 1) { 6522 return null; 6523 } 6524 6525 // Check for fake content editable 6526 contentEditable = node.getAttribute("data-mce-contenteditable"); 6527 if (contentEditable && contentEditable !== "inherit") { 6528 return contentEditable; 6529 } 6530 6531 // Check for real content editable 6532 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6533 }, 6534 6535 6536 _findSib : function(node, selector, name) { 6537 var t = this, f = selector; 6538 6539 if (node) { 6540 // If expression make a function of it using is 6541 if (is(f, 'string')) { 6542 f = function(node) { 6543 return t.is(node, selector); 6544 }; 6545 } 6546 6547 // Loop all siblings 6548 for (node = node[name]; node; node = node[name]) { 6549 if (f(node)) 6550 return node; 6551 } 6552 } 6553 6554 return null; 6555 }, 6556 6557 _isRes : function(c) { 6558 // Is live resizble element 6559 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6560 } 6561 6562 /* 6563 walk : function(n, f, s) { 6564 var d = this.doc, w; 6565 6566 if (d.createTreeWalker) { 6567 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6568 6569 while ((n = w.nextNode()) != null) 6570 f.call(s || this, n); 6571 } else 6572 tinymce.walk(n, f, 'childNodes', s); 6573 } 6574 */ 6575 6576 /* 6577 toRGB : function(s) { 6578 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6579 6580 if (c) { 6581 // #FFF -> #FFFFFF 6582 if (!is(c[3])) 6583 c[3] = c[2] = c[1]; 6584 6585 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6586 } 6587 6588 return s; 6589 } 6590 */ 6591 }); 6592 6593 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6594 })(tinymce); 6595 (function(ns) { 6596 // Range constructor 6597 function Range(dom) { 6598 var t = this, 6599 doc = dom.doc, 6600 EXTRACT = 0, 6601 CLONE = 1, 6602 DELETE = 2, 6603 TRUE = true, 6604 FALSE = false, 6605 START_OFFSET = 'startOffset', 6606 START_CONTAINER = 'startContainer', 6607 END_CONTAINER = 'endContainer', 6608 END_OFFSET = 'endOffset', 6609 extend = tinymce.extend, 6610 nodeIndex = dom.nodeIndex; 6611 6612 extend(t, { 6613 // Inital states 6614 startContainer : doc, 6615 startOffset : 0, 6616 endContainer : doc, 6617 endOffset : 0, 6618 collapsed : TRUE, 6619 commonAncestorContainer : doc, 6620 6621 // Range constants 6622 START_TO_START : 0, 6623 START_TO_END : 1, 6624 END_TO_END : 2, 6625 END_TO_START : 3, 6626 6627 // Public methods 6628 setStart : setStart, 6629 setEnd : setEnd, 6630 setStartBefore : setStartBefore, 6631 setStartAfter : setStartAfter, 6632 setEndBefore : setEndBefore, 6633 setEndAfter : setEndAfter, 6634 collapse : collapse, 6635 selectNode : selectNode, 6636 selectNodeContents : selectNodeContents, 6637 compareBoundaryPoints : compareBoundaryPoints, 6638 deleteContents : deleteContents, 6639 extractContents : extractContents, 6640 cloneContents : cloneContents, 6641 insertNode : insertNode, 6642 surroundContents : surroundContents, 6643 cloneRange : cloneRange, 6644 toStringIE : toStringIE 6645 }); 6646 6647 function createDocumentFragment() { 6648 return doc.createDocumentFragment(); 6649 }; 6650 6651 function setStart(n, o) { 6652 _setEndPoint(TRUE, n, o); 6653 }; 6654 6655 function setEnd(n, o) { 6656 _setEndPoint(FALSE, n, o); 6657 }; 6658 6659 function setStartBefore(n) { 6660 setStart(n.parentNode, nodeIndex(n)); 6661 }; 6662 6663 function setStartAfter(n) { 6664 setStart(n.parentNode, nodeIndex(n) + 1); 6665 }; 6666 6667 function setEndBefore(n) { 6668 setEnd(n.parentNode, nodeIndex(n)); 6669 }; 6670 6671 function setEndAfter(n) { 6672 setEnd(n.parentNode, nodeIndex(n) + 1); 6673 }; 6674 6675 function collapse(ts) { 6676 if (ts) { 6677 t[END_CONTAINER] = t[START_CONTAINER]; 6678 t[END_OFFSET] = t[START_OFFSET]; 6679 } else { 6680 t[START_CONTAINER] = t[END_CONTAINER]; 6681 t[START_OFFSET] = t[END_OFFSET]; 6682 } 6683 6684 t.collapsed = TRUE; 6685 }; 6686 6687 function selectNode(n) { 6688 setStartBefore(n); 6689 setEndAfter(n); 6690 }; 6691 6692 function selectNodeContents(n) { 6693 setStart(n, 0); 6694 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6695 }; 6696 6697 function compareBoundaryPoints(h, r) { 6698 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6699 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6700 6701 // Check START_TO_START 6702 if (h === 0) 6703 return _compareBoundaryPoints(sc, so, rsc, rso); 6704 6705 // Check START_TO_END 6706 if (h === 1) 6707 return _compareBoundaryPoints(ec, eo, rsc, rso); 6708 6709 // Check END_TO_END 6710 if (h === 2) 6711 return _compareBoundaryPoints(ec, eo, rec, reo); 6712 6713 // Check END_TO_START 6714 if (h === 3) 6715 return _compareBoundaryPoints(sc, so, rec, reo); 6716 }; 6717 6718 function deleteContents() { 6719 _traverse(DELETE); 6720 }; 6721 6722 function extractContents() { 6723 return _traverse(EXTRACT); 6724 }; 6725 6726 function cloneContents() { 6727 return _traverse(CLONE); 6728 }; 6729 6730 function insertNode(n) { 6731 var startContainer = this[START_CONTAINER], 6732 startOffset = this[START_OFFSET], nn, o; 6733 6734 // Node is TEXT_NODE or CDATA 6735 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6736 if (!startOffset) { 6737 // At the start of text 6738 startContainer.parentNode.insertBefore(n, startContainer); 6739 } else if (startOffset >= startContainer.nodeValue.length) { 6740 // At the end of text 6741 dom.insertAfter(n, startContainer); 6742 } else { 6743 // Middle, need to split 6744 nn = startContainer.splitText(startOffset); 6745 startContainer.parentNode.insertBefore(n, nn); 6746 } 6747 } else { 6748 // Insert element node 6749 if (startContainer.childNodes.length > 0) 6750 o = startContainer.childNodes[startOffset]; 6751 6752 if (o) 6753 startContainer.insertBefore(n, o); 6754 else 6755 startContainer.appendChild(n); 6756 } 6757 }; 6758 6759 function surroundContents(n) { 6760 var f = t.extractContents(); 6761 6762 t.insertNode(n); 6763 n.appendChild(f); 6764 t.selectNode(n); 6765 }; 6766 6767 function cloneRange() { 6768 return extend(new Range(dom), { 6769 startContainer : t[START_CONTAINER], 6770 startOffset : t[START_OFFSET], 6771 endContainer : t[END_CONTAINER], 6772 endOffset : t[END_OFFSET], 6773 collapsed : t.collapsed, 6774 commonAncestorContainer : t.commonAncestorContainer 6775 }); 6776 }; 6777 6778 // Private methods 6779 6780 function _getSelectedNode(container, offset) { 6781 var child; 6782 6783 if (container.nodeType == 3 /* TEXT_NODE */) 6784 return container; 6785 6786 if (offset < 0) 6787 return container; 6788 6789 child = container.firstChild; 6790 while (child && offset > 0) { 6791 --offset; 6792 child = child.nextSibling; 6793 } 6794 6795 if (child) 6796 return child; 6797 6798 return container; 6799 }; 6800 6801 function _isCollapsed() { 6802 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6803 }; 6804 6805 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6806 var c, offsetC, n, cmnRoot, childA, childB; 6807 6808 // In the first case the boundary-points have the same container. A is before B 6809 // if its offset is less than the offset of B, A is equal to B if its offset is 6810 // equal to the offset of B, and A is after B if its offset is greater than the 6811 // offset of B. 6812 if (containerA == containerB) { 6813 if (offsetA == offsetB) 6814 return 0; // equal 6815 6816 if (offsetA < offsetB) 6817 return -1; // before 6818 6819 return 1; // after 6820 } 6821 6822 // In the second case a child node C of the container of A is an ancestor 6823 // container of B. In this case, A is before B if the offset of A is less than or 6824 // equal to the index of the child node C and A is after B otherwise. 6825 c = containerB; 6826 while (c && c.parentNode != containerA) 6827 c = c.parentNode; 6828 6829 if (c) { 6830 offsetC = 0; 6831 n = containerA.firstChild; 6832 6833 while (n != c && offsetC < offsetA) { 6834 offsetC++; 6835 n = n.nextSibling; 6836 } 6837 6838 if (offsetA <= offsetC) 6839 return -1; // before 6840 6841 return 1; // after 6842 } 6843 6844 // In the third case a child node C of the container of B is an ancestor container 6845 // of A. In this case, A is before B if the index of the child node C is less than 6846 // the offset of B and A is after B otherwise. 6847 c = containerA; 6848 while (c && c.parentNode != containerB) { 6849 c = c.parentNode; 6850 } 6851 6852 if (c) { 6853 offsetC = 0; 6854 n = containerB.firstChild; 6855 6856 while (n != c && offsetC < offsetB) { 6857 offsetC++; 6858 n = n.nextSibling; 6859 } 6860 6861 if (offsetC < offsetB) 6862 return -1; // before 6863 6864 return 1; // after 6865 } 6866 6867 // In the fourth case, none of three other cases hold: the containers of A and B 6868 // are siblings or descendants of sibling nodes. In this case, A is before B if 6869 // the container of A is before the container of B in a pre-order traversal of the 6870 // Ranges' context tree and A is after B otherwise. 6871 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6872 childA = containerA; 6873 6874 while (childA && childA.parentNode != cmnRoot) 6875 childA = childA.parentNode; 6876 6877 if (!childA) 6878 childA = cmnRoot; 6879 6880 childB = containerB; 6881 while (childB && childB.parentNode != cmnRoot) 6882 childB = childB.parentNode; 6883 6884 if (!childB) 6885 childB = cmnRoot; 6886 6887 if (childA == childB) 6888 return 0; // equal 6889 6890 n = cmnRoot.firstChild; 6891 while (n) { 6892 if (n == childA) 6893 return -1; // before 6894 6895 if (n == childB) 6896 return 1; // after 6897 6898 n = n.nextSibling; 6899 } 6900 }; 6901 6902 function _setEndPoint(st, n, o) { 6903 var ec, sc; 6904 6905 if (st) { 6906 t[START_CONTAINER] = n; 6907 t[START_OFFSET] = o; 6908 } else { 6909 t[END_CONTAINER] = n; 6910 t[END_OFFSET] = o; 6911 } 6912 6913 // If one boundary-point of a Range is set to have a root container 6914 // other than the current one for the Range, the Range is collapsed to 6915 // the new position. This enforces the restriction that both boundary- 6916 // points of a Range must have the same root container. 6917 ec = t[END_CONTAINER]; 6918 while (ec.parentNode) 6919 ec = ec.parentNode; 6920 6921 sc = t[START_CONTAINER]; 6922 while (sc.parentNode) 6923 sc = sc.parentNode; 6924 6925 if (sc == ec) { 6926 // The start position of a Range is guaranteed to never be after the 6927 // end position. To enforce this restriction, if the start is set to 6928 // be at a position after the end, the Range is collapsed to that 6929 // position. 6930 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6931 t.collapse(st); 6932 } else 6933 t.collapse(st); 6934 6935 t.collapsed = _isCollapsed(); 6936 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6937 }; 6938 6939 function _traverse(how) { 6940 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6941 6942 if (t[START_CONTAINER] == t[END_CONTAINER]) 6943 return _traverseSameContainer(how); 6944 6945 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6946 if (p == t[START_CONTAINER]) 6947 return _traverseCommonStartContainer(c, how); 6948 6949 ++endContainerDepth; 6950 } 6951 6952 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6953 if (p == t[END_CONTAINER]) 6954 return _traverseCommonEndContainer(c, how); 6955 6956 ++startContainerDepth; 6957 } 6958 6959 depthDiff = startContainerDepth - endContainerDepth; 6960 6961 startNode = t[START_CONTAINER]; 6962 while (depthDiff > 0) { 6963 startNode = startNode.parentNode; 6964 depthDiff--; 6965 } 6966 6967 endNode = t[END_CONTAINER]; 6968 while (depthDiff < 0) { 6969 endNode = endNode.parentNode; 6970 depthDiff++; 6971 } 6972 6973 // ascend the ancestor hierarchy until we have a common parent. 6974 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6975 startNode = sp; 6976 endNode = ep; 6977 } 6978 6979 return _traverseCommonAncestors(startNode, endNode, how); 6980 }; 6981 6982 function _traverseSameContainer(how) { 6983 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6984 6985 if (how != DELETE) 6986 frag = createDocumentFragment(); 6987 6988 // If selection is empty, just return the fragment 6989 if (t[START_OFFSET] == t[END_OFFSET]) 6990 return frag; 6991 6992 // Text node needs special case handling 6993 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6994 // get the substring 6995 s = t[START_CONTAINER].nodeValue; 6996 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6997 6998 // set the original text node to its new value 6999 if (how != CLONE) { 7000 n = t[START_CONTAINER]; 7001 start = t[START_OFFSET]; 7002 len = t[END_OFFSET] - t[START_OFFSET]; 7003 7004 if (start === 0 && len >= n.nodeValue.length - 1) { 7005 n.parentNode.removeChild(n); 7006 } else { 7007 n.deleteData(start, len); 7008 } 7009 7010 // Nothing is partially selected, so collapse to start point 7011 t.collapse(TRUE); 7012 } 7013 7014 if (how == DELETE) 7015 return; 7016 7017 if (sub.length > 0) { 7018 frag.appendChild(doc.createTextNode(sub)); 7019 } 7020 7021 return frag; 7022 } 7023 7024 // Copy nodes between the start/end offsets. 7025 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 7026 cnt = t[END_OFFSET] - t[START_OFFSET]; 7027 7028 while (n && cnt > 0) { 7029 sibling = n.nextSibling; 7030 xferNode = _traverseFullySelected(n, how); 7031 7032 if (frag) 7033 frag.appendChild( xferNode ); 7034 7035 --cnt; 7036 n = sibling; 7037 } 7038 7039 // Nothing is partially selected, so collapse to start point 7040 if (how != CLONE) 7041 t.collapse(TRUE); 7042 7043 return frag; 7044 }; 7045 7046 function _traverseCommonStartContainer(endAncestor, how) { 7047 var frag, n, endIdx, cnt, sibling, xferNode; 7048 7049 if (how != DELETE) 7050 frag = createDocumentFragment(); 7051 7052 n = _traverseRightBoundary(endAncestor, how); 7053 7054 if (frag) 7055 frag.appendChild(n); 7056 7057 endIdx = nodeIndex(endAncestor); 7058 cnt = endIdx - t[START_OFFSET]; 7059 7060 if (cnt <= 0) { 7061 // Collapse to just before the endAncestor, which 7062 // is partially selected. 7063 if (how != CLONE) { 7064 t.setEndBefore(endAncestor); 7065 t.collapse(FALSE); 7066 } 7067 7068 return frag; 7069 } 7070 7071 n = endAncestor.previousSibling; 7072 while (cnt > 0) { 7073 sibling = n.previousSibling; 7074 xferNode = _traverseFullySelected(n, how); 7075 7076 if (frag) 7077 frag.insertBefore(xferNode, frag.firstChild); 7078 7079 --cnt; 7080 n = sibling; 7081 } 7082 7083 // Collapse to just before the endAncestor, which 7084 // is partially selected. 7085 if (how != CLONE) { 7086 t.setEndBefore(endAncestor); 7087 t.collapse(FALSE); 7088 } 7089 7090 return frag; 7091 }; 7092 7093 function _traverseCommonEndContainer(startAncestor, how) { 7094 var frag, startIdx, n, cnt, sibling, xferNode; 7095 7096 if (how != DELETE) 7097 frag = createDocumentFragment(); 7098 7099 n = _traverseLeftBoundary(startAncestor, how); 7100 if (frag) 7101 frag.appendChild(n); 7102 7103 startIdx = nodeIndex(startAncestor); 7104 ++startIdx; // Because we already traversed it 7105 7106 cnt = t[END_OFFSET] - startIdx; 7107 n = startAncestor.nextSibling; 7108 while (n && cnt > 0) { 7109 sibling = n.nextSibling; 7110 xferNode = _traverseFullySelected(n, how); 7111 7112 if (frag) 7113 frag.appendChild(xferNode); 7114 7115 --cnt; 7116 n = sibling; 7117 } 7118 7119 if (how != CLONE) { 7120 t.setStartAfter(startAncestor); 7121 t.collapse(TRUE); 7122 } 7123 7124 return frag; 7125 }; 7126 7127 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 7128 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 7129 7130 if (how != DELETE) 7131 frag = createDocumentFragment(); 7132 7133 n = _traverseLeftBoundary(startAncestor, how); 7134 if (frag) 7135 frag.appendChild(n); 7136 7137 commonParent = startAncestor.parentNode; 7138 startOffset = nodeIndex(startAncestor); 7139 endOffset = nodeIndex(endAncestor); 7140 ++startOffset; 7141 7142 cnt = endOffset - startOffset; 7143 sibling = startAncestor.nextSibling; 7144 7145 while (cnt > 0) { 7146 nextSibling = sibling.nextSibling; 7147 n = _traverseFullySelected(sibling, how); 7148 7149 if (frag) 7150 frag.appendChild(n); 7151 7152 sibling = nextSibling; 7153 --cnt; 7154 } 7155 7156 n = _traverseRightBoundary(endAncestor, how); 7157 7158 if (frag) 7159 frag.appendChild(n); 7160 7161 if (how != CLONE) { 7162 t.setStartAfter(startAncestor); 7163 t.collapse(TRUE); 7164 } 7165 7166 return frag; 7167 }; 7168 7169 function _traverseRightBoundary(root, how) { 7170 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 7171 7172 if (next == root) 7173 return _traverseNode(next, isFullySelected, FALSE, how); 7174 7175 parent = next.parentNode; 7176 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 7177 7178 while (parent) { 7179 while (next) { 7180 prevSibling = next.previousSibling; 7181 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 7182 7183 if (how != DELETE) 7184 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 7185 7186 isFullySelected = TRUE; 7187 next = prevSibling; 7188 } 7189 7190 if (parent == root) 7191 return clonedParent; 7192 7193 next = parent.previousSibling; 7194 parent = parent.parentNode; 7195 7196 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 7197 7198 if (how != DELETE) 7199 clonedGrandParent.appendChild(clonedParent); 7200 7201 clonedParent = clonedGrandParent; 7202 } 7203 }; 7204 7205 function _traverseLeftBoundary(root, how) { 7206 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 7207 7208 if (next == root) 7209 return _traverseNode(next, isFullySelected, TRUE, how); 7210 7211 parent = next.parentNode; 7212 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 7213 7214 while (parent) { 7215 while (next) { 7216 nextSibling = next.nextSibling; 7217 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 7218 7219 if (how != DELETE) 7220 clonedParent.appendChild(clonedChild); 7221 7222 isFullySelected = TRUE; 7223 next = nextSibling; 7224 } 7225 7226 if (parent == root) 7227 return clonedParent; 7228 7229 next = parent.nextSibling; 7230 parent = parent.parentNode; 7231 7232 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 7233 7234 if (how != DELETE) 7235 clonedGrandParent.appendChild(clonedParent); 7236 7237 clonedParent = clonedGrandParent; 7238 } 7239 }; 7240 7241 function _traverseNode(n, isFullySelected, isLeft, how) { 7242 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 7243 7244 if (isFullySelected) 7245 return _traverseFullySelected(n, how); 7246 7247 if (n.nodeType == 3 /* TEXT_NODE */) { 7248 txtValue = n.nodeValue; 7249 7250 if (isLeft) { 7251 offset = t[START_OFFSET]; 7252 newNodeValue = txtValue.substring(offset); 7253 oldNodeValue = txtValue.substring(0, offset); 7254 } else { 7255 offset = t[END_OFFSET]; 7256 newNodeValue = txtValue.substring(0, offset); 7257 oldNodeValue = txtValue.substring(offset); 7258 } 7259 7260 if (how != CLONE) 7261 n.nodeValue = oldNodeValue; 7262 7263 if (how == DELETE) 7264 return; 7265 7266 newNode = dom.clone(n, FALSE); 7267 newNode.nodeValue = newNodeValue; 7268 7269 return newNode; 7270 } 7271 7272 if (how == DELETE) 7273 return; 7274 7275 return dom.clone(n, FALSE); 7276 }; 7277 7278 function _traverseFullySelected(n, how) { 7279 if (how != DELETE) 7280 return how == CLONE ? dom.clone(n, TRUE) : n; 7281 7282 n.parentNode.removeChild(n); 7283 }; 7284 7285 function toStringIE() { 7286 return dom.create('body', null, cloneContents()).outerText; 7287 } 7288 7289 return t; 7290 }; 7291 7292 ns.Range = Range; 7293 7294 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7295 Range.prototype.toString = function() { 7296 return this.toStringIE(); 7297 }; 7298 })(tinymce.dom); 7299 (function() { 7300 function Selection(selection) { 7301 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7302 7303 function getPosition(rng, start) { 7304 var checkRng, startIndex = 0, endIndex, inside, 7305 children, child, offset, index, position = -1, parent; 7306 7307 // Setup test range, collapse it and get the parent 7308 checkRng = rng.duplicate(); 7309 checkRng.collapse(start); 7310 parent = checkRng.parentElement(); 7311 7312 // Check if the selection is within the right document 7313 if (parent.ownerDocument !== selection.dom.doc) 7314 return; 7315 7316 // IE will report non editable elements as it's parent so look for an editable one 7317 while (parent.contentEditable === "false") { 7318 parent = parent.parentNode; 7319 } 7320 7321 // If parent doesn't have any children then return that we are inside the element 7322 if (!parent.hasChildNodes()) { 7323 return {node : parent, inside : 1}; 7324 } 7325 7326 // Setup node list and endIndex 7327 children = parent.children; 7328 endIndex = children.length - 1; 7329 7330 // Perform a binary search for the position 7331 while (startIndex <= endIndex) { 7332 index = Math.floor((startIndex + endIndex) / 2); 7333 7334 // Move selection to node and compare the ranges 7335 child = children[index]; 7336 checkRng.moveToElementText(child); 7337 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7338 7339 // Before/after or an exact match 7340 if (position > 0) { 7341 endIndex = index - 1; 7342 } else if (position < 0) { 7343 startIndex = index + 1; 7344 } else { 7345 return {node : child}; 7346 } 7347 } 7348 7349 // Check if child position is before or we didn't find a position 7350 if (position < 0) { 7351 // No element child was found use the parent element and the offset inside that 7352 if (!child) { 7353 checkRng.moveToElementText(parent); 7354 checkRng.collapse(true); 7355 child = parent; 7356 inside = true; 7357 } else 7358 checkRng.collapse(false); 7359 7360 // 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 7361 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7362 offset = 0; 7363 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7364 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7365 break; 7366 } 7367 7368 offset++; 7369 } 7370 } else { 7371 // Child position is after the selection endpoint 7372 checkRng.collapse(true); 7373 7374 // 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 7375 offset = 0; 7376 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7377 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7378 break; 7379 } 7380 7381 offset++; 7382 } 7383 } 7384 7385 return {node : child, position : position, offset : offset, inside : inside}; 7386 }; 7387 7388 // Returns a W3C DOM compatible range object by using the IE Range API 7389 function getRange() { 7390 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7391 7392 // If selection is outside the current document just return an empty range 7393 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7394 if (element.ownerDocument != dom.doc) 7395 return domRange; 7396 7397 collapsed = selection.isCollapsed(); 7398 7399 // Handle control selection 7400 if (ieRange.item) { 7401 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7402 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7403 7404 return domRange; 7405 } 7406 7407 function findEndPoint(start) { 7408 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7409 7410 container = endPoint.node; 7411 offset = endPoint.offset; 7412 7413 if (endPoint.inside && !container.hasChildNodes()) { 7414 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7415 return; 7416 } 7417 7418 if (offset === undef) { 7419 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7420 return; 7421 } 7422 7423 if (endPoint.position < 0) { 7424 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7425 7426 if (!sibling) { 7427 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7428 return; 7429 } 7430 7431 if (!offset) { 7432 if (sibling.nodeType == 3) 7433 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7434 else 7435 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7436 7437 return; 7438 } 7439 7440 // Find the text node and offset 7441 while (sibling) { 7442 nodeValue = sibling.nodeValue; 7443 textNodeOffset += nodeValue.length; 7444 7445 // We are at or passed the position we where looking for 7446 if (textNodeOffset >= offset) { 7447 container = sibling; 7448 textNodeOffset -= offset; 7449 textNodeOffset = nodeValue.length - textNodeOffset; 7450 break; 7451 } 7452 7453 sibling = sibling.nextSibling; 7454 } 7455 } else { 7456 // Find the text node and offset 7457 sibling = container.previousSibling; 7458 7459 if (!sibling) 7460 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7461 7462 // If there isn't any text to loop then use the first position 7463 if (!offset) { 7464 if (container.nodeType == 3) 7465 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7466 else 7467 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7468 7469 return; 7470 } 7471 7472 while (sibling) { 7473 textNodeOffset += sibling.nodeValue.length; 7474 7475 // We are at or passed the position we where looking for 7476 if (textNodeOffset >= offset) { 7477 container = sibling; 7478 textNodeOffset -= offset; 7479 break; 7480 } 7481 7482 sibling = sibling.previousSibling; 7483 } 7484 } 7485 7486 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7487 }; 7488 7489 try { 7490 // Find start point 7491 findEndPoint(true); 7492 7493 // Find end point if needed 7494 if (!collapsed) 7495 findEndPoint(); 7496 } catch (ex) { 7497 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7498 // access the nodeValue or other properties of text nodes. This seems to happend when 7499 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7500 if (ex.number == -2147024809) { 7501 // Get the current selection 7502 bookmark = self.getBookmark(2); 7503 7504 // Get start element 7505 tmpRange = ieRange.duplicate(); 7506 tmpRange.collapse(true); 7507 element = tmpRange.parentElement(); 7508 7509 // Get end element 7510 if (!collapsed) { 7511 tmpRange = ieRange.duplicate(); 7512 tmpRange.collapse(false); 7513 element2 = tmpRange.parentElement(); 7514 element2.innerHTML = element2.innerHTML; 7515 } 7516 7517 // Remove the broken elements 7518 element.innerHTML = element.innerHTML; 7519 7520 // Restore the selection 7521 self.moveToBookmark(bookmark); 7522 7523 // Since the range has moved we need to re-get it 7524 ieRange = selection.getRng(); 7525 7526 // Find start point 7527 findEndPoint(true); 7528 7529 // Find end point if needed 7530 if (!collapsed) 7531 findEndPoint(); 7532 } else 7533 throw ex; // Throw other errors 7534 } 7535 7536 return domRange; 7537 }; 7538 7539 this.getBookmark = function(type) { 7540 var rng = selection.getRng(), start, end, bookmark = {}; 7541 7542 function getIndexes(node) { 7543 var parent, root, children, i, indexes = []; 7544 7545 parent = node.parentNode; 7546 root = dom.getRoot().parentNode; 7547 7548 while (parent != root && parent.nodeType !== 9) { 7549 children = parent.children; 7550 7551 i = children.length; 7552 while (i--) { 7553 if (node === children[i]) { 7554 indexes.push(i); 7555 break; 7556 } 7557 } 7558 7559 node = parent; 7560 parent = parent.parentNode; 7561 } 7562 7563 return indexes; 7564 }; 7565 7566 function getBookmarkEndPoint(start) { 7567 var position; 7568 7569 position = getPosition(rng, start); 7570 if (position) { 7571 return { 7572 position : position.position, 7573 offset : position.offset, 7574 indexes : getIndexes(position.node), 7575 inside : position.inside 7576 }; 7577 } 7578 }; 7579 7580 // Non ubstructive bookmark 7581 if (type === 2) { 7582 // Handle text selection 7583 if (!rng.item) { 7584 bookmark.start = getBookmarkEndPoint(true); 7585 7586 if (!selection.isCollapsed()) 7587 bookmark.end = getBookmarkEndPoint(); 7588 } else 7589 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7590 } 7591 7592 return bookmark; 7593 }; 7594 7595 this.moveToBookmark = function(bookmark) { 7596 var rng, body = dom.doc.body; 7597 7598 function resolveIndexes(indexes) { 7599 var node, i, idx, children; 7600 7601 node = dom.getRoot(); 7602 for (i = indexes.length - 1; i >= 0; i--) { 7603 children = node.children; 7604 idx = indexes[i]; 7605 7606 if (idx <= children.length - 1) { 7607 node = children[idx]; 7608 } 7609 } 7610 7611 return node; 7612 }; 7613 7614 function setBookmarkEndPoint(start) { 7615 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7616 7617 if (endPoint) { 7618 moveLeft = endPoint.position > 0; 7619 7620 moveRng = body.createTextRange(); 7621 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7622 7623 offset = endPoint.offset; 7624 if (offset !== undef) { 7625 moveRng.collapse(endPoint.inside || moveLeft); 7626 moveRng.moveStart('character', moveLeft ? -offset : offset); 7627 } else 7628 moveRng.collapse(start); 7629 7630 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7631 7632 if (start) 7633 rng.collapse(true); 7634 } 7635 }; 7636 7637 if (bookmark.start) { 7638 if (bookmark.start.ctrl) { 7639 rng = body.createControlRange(); 7640 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7641 rng.select(); 7642 } else { 7643 rng = body.createTextRange(); 7644 setBookmarkEndPoint(true); 7645 setBookmarkEndPoint(); 7646 rng.select(); 7647 } 7648 } 7649 }; 7650 7651 this.addRange = function(rng) { 7652 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 7653 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 7654 7655 function setEndPoint(start) { 7656 var container, offset, marker, tmpRng, nodes; 7657 7658 marker = dom.create('a'); 7659 container = start ? startContainer : endContainer; 7660 offset = start ? startOffset : endOffset; 7661 tmpRng = ieRng.duplicate(); 7662 7663 if (container == doc || container == doc.documentElement) { 7664 container = body; 7665 offset = 0; 7666 } 7667 7668 if (container.nodeType == 3) { 7669 container.parentNode.insertBefore(marker, container); 7670 tmpRng.moveToElementText(marker); 7671 tmpRng.moveStart('character', offset); 7672 dom.remove(marker); 7673 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7674 } else { 7675 nodes = container.childNodes; 7676 7677 if (nodes.length) { 7678 if (offset >= nodes.length) { 7679 dom.insertAfter(marker, nodes[nodes.length - 1]); 7680 } else { 7681 container.insertBefore(marker, nodes[offset]); 7682 } 7683 7684 tmpRng.moveToElementText(marker); 7685 } else if (container.canHaveHTML) { 7686 // Empty node selection for example <div>|</div> 7687 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7688 container.innerHTML = '<span>\uFEFF</span>'; 7689 marker = container.firstChild; 7690 tmpRng.moveToElementText(marker); 7691 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7692 } 7693 7694 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7695 dom.remove(marker); 7696 } 7697 } 7698 7699 // Setup some shorter versions 7700 startContainer = rng.startContainer; 7701 startOffset = rng.startOffset; 7702 endContainer = rng.endContainer; 7703 endOffset = rng.endOffset; 7704 ieRng = body.createTextRange(); 7705 7706 // If single element selection then try making a control selection out of it 7707 if (startContainer == endContainer && startContainer.nodeType == 1) { 7708 // Trick to place the caret inside an empty block element like <p></p> 7709 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7710 if (startContainer.canHaveHTML) { 7711 // Check if previous sibling is an empty block if it is then we need to render it 7712 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7713 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7714 sibling = startContainer.previousSibling; 7715 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7716 sibling.innerHTML = '\uFEFF'; 7717 } else { 7718 sibling = null; 7719 } 7720 7721 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7722 ieRng.moveToElementText(startContainer.lastChild); 7723 ieRng.select(); 7724 dom.doc.selection.clear(); 7725 startContainer.innerHTML = ''; 7726 7727 if (sibling) { 7728 sibling.innerHTML = ''; 7729 } 7730 return; 7731 } else { 7732 startOffset = dom.nodeIndex(startContainer); 7733 startContainer = startContainer.parentNode; 7734 } 7735 } 7736 7737 if (startOffset == endOffset - 1) { 7738 try { 7739 ctrlElm = startContainer.childNodes[startOffset]; 7740 ctrlRng = body.createControlRange(); 7741 ctrlRng.addElement(ctrlElm); 7742 ctrlRng.select(); 7743 7744 // Check if the range produced is on the correct element and is a control range 7745 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 7746 nativeRng = selection.getRng(); 7747 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 7748 return; 7749 } 7750 } catch (ex) { 7751 // Ignore 7752 } 7753 } 7754 } 7755 7756 // Set start/end point of selection 7757 setEndPoint(true); 7758 setEndPoint(); 7759 7760 // Select the new range and scroll it into view 7761 ieRng.select(); 7762 }; 7763 7764 // Expose range method 7765 this.getRangeAt = getRange; 7766 }; 7767 7768 // Expose the selection object 7769 tinymce.dom.TridentSelection = Selection; 7770 })(); 7771 7772 /* 7773 * Sizzle CSS Selector Engine 7774 * Copyright, The Dojo Foundation 7775 * Released under the MIT, BSD, and GPL Licenses. 7776 * More information: http://sizzlejs.com/ 7777 */ 7778 (function(){ 7779 7780 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7781 expando = "sizcache", 7782 done = 0, 7783 toString = Object.prototype.toString, 7784 hasDuplicate = false, 7785 baseHasDuplicate = true, 7786 rBackslash = /\\/g, 7787 rReturn = /\r\n/g, 7788 rNonWord = /\W/; 7789 7790 // Here we check if the JavaScript engine is using some sort of 7791 // optimization where it does not always call our comparision 7792 // function. If that is the case, discard the hasDuplicate value. 7793 // Thus far that includes Google Chrome. 7794 [0, 0].sort(function() { 7795 baseHasDuplicate = false; 7796 return 0; 7797 }); 7798 7799 var Sizzle = function( selector, context, results, seed ) { 7800 results = results || []; 7801 context = context || document; 7802 7803 var origContext = context; 7804 7805 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7806 return []; 7807 } 7808 7809 if ( !selector || typeof selector !== "string" ) { 7810 return results; 7811 } 7812 7813 var m, set, checkSet, extra, ret, cur, pop, i, 7814 prune = true, 7815 contextXML = Sizzle.isXML( context ), 7816 parts = [], 7817 soFar = selector; 7818 7819 // Reset the position of the chunker regexp (start from head) 7820 do { 7821 chunker.exec( "" ); 7822 m = chunker.exec( soFar ); 7823 7824 if ( m ) { 7825 soFar = m[3]; 7826 7827 parts.push( m[1] ); 7828 7829 if ( m[2] ) { 7830 extra = m[3]; 7831 break; 7832 } 7833 } 7834 } while ( m ); 7835 7836 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7837 7838 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7839 set = posProcess( parts[0] + parts[1], context, seed ); 7840 7841 } else { 7842 set = Expr.relative[ parts[0] ] ? 7843 [ context ] : 7844 Sizzle( parts.shift(), context ); 7845 7846 while ( parts.length ) { 7847 selector = parts.shift(); 7848 7849 if ( Expr.relative[ selector ] ) { 7850 selector += parts.shift(); 7851 } 7852 7853 set = posProcess( selector, set, seed ); 7854 } 7855 } 7856 7857 } else { 7858 // Take a shortcut and set the context if the root selector is an ID 7859 // (but not if it'll be faster if the inner selector is an ID) 7860 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7861 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7862 7863 ret = Sizzle.find( parts.shift(), context, contextXML ); 7864 context = ret.expr ? 7865 Sizzle.filter( ret.expr, ret.set )[0] : 7866 ret.set[0]; 7867 } 7868 7869 if ( context ) { 7870 ret = seed ? 7871 { expr: parts.pop(), set: makeArray(seed) } : 7872 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7873 7874 set = ret.expr ? 7875 Sizzle.filter( ret.expr, ret.set ) : 7876 ret.set; 7877 7878 if ( parts.length > 0 ) { 7879 checkSet = makeArray( set ); 7880 7881 } else { 7882 prune = false; 7883 } 7884 7885 while ( parts.length ) { 7886 cur = parts.pop(); 7887 pop = cur; 7888 7889 if ( !Expr.relative[ cur ] ) { 7890 cur = ""; 7891 } else { 7892 pop = parts.pop(); 7893 } 7894 7895 if ( pop == null ) { 7896 pop = context; 7897 } 7898 7899 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7900 } 7901 7902 } else { 7903 checkSet = parts = []; 7904 } 7905 } 7906 7907 if ( !checkSet ) { 7908 checkSet = set; 7909 } 7910 7911 if ( !checkSet ) { 7912 Sizzle.error( cur || selector ); 7913 } 7914 7915 if ( toString.call(checkSet) === "[object Array]" ) { 7916 if ( !prune ) { 7917 results.push.apply( results, checkSet ); 7918 7919 } else if ( context && context.nodeType === 1 ) { 7920 for ( i = 0; checkSet[i] != null; i++ ) { 7921 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7922 results.push( set[i] ); 7923 } 7924 } 7925 7926 } else { 7927 for ( i = 0; checkSet[i] != null; i++ ) { 7928 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7929 results.push( set[i] ); 7930 } 7931 } 7932 } 7933 7934 } else { 7935 makeArray( checkSet, results ); 7936 } 7937 7938 if ( extra ) { 7939 Sizzle( extra, origContext, results, seed ); 7940 Sizzle.uniqueSort( results ); 7941 } 7942 7943 return results; 7944 }; 7945 7946 Sizzle.uniqueSort = function( results ) { 7947 if ( sortOrder ) { 7948 hasDuplicate = baseHasDuplicate; 7949 results.sort( sortOrder ); 7950 7951 if ( hasDuplicate ) { 7952 for ( var i = 1; i < results.length; i++ ) { 7953 if ( results[i] === results[ i - 1 ] ) { 7954 results.splice( i--, 1 ); 7955 } 7956 } 7957 } 7958 } 7959 7960 return results; 7961 }; 7962 7963 Sizzle.matches = function( expr, set ) { 7964 return Sizzle( expr, null, null, set ); 7965 }; 7966 7967 Sizzle.matchesSelector = function( node, expr ) { 7968 return Sizzle( expr, null, null, [node] ).length > 0; 7969 }; 7970 7971 Sizzle.find = function( expr, context, isXML ) { 7972 var set, i, len, match, type, left; 7973 7974 if ( !expr ) { 7975 return []; 7976 } 7977 7978 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7979 type = Expr.order[i]; 7980 7981 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7982 left = match[1]; 7983 match.splice( 1, 1 ); 7984 7985 if ( left.substr( left.length - 1 ) !== "\\" ) { 7986 match[1] = (match[1] || "").replace( rBackslash, "" ); 7987 set = Expr.find[ type ]( match, context, isXML ); 7988 7989 if ( set != null ) { 7990 expr = expr.replace( Expr.match[ type ], "" ); 7991 break; 7992 } 7993 } 7994 } 7995 } 7996 7997 if ( !set ) { 7998 set = typeof context.getElementsByTagName !== "undefined" ? 7999 context.getElementsByTagName( "*" ) : 8000 []; 8001 } 8002 8003 return { set: set, expr: expr }; 8004 }; 8005 8006 Sizzle.filter = function( expr, set, inplace, not ) { 8007 var match, anyFound, 8008 type, found, item, filter, left, 8009 i, pass, 8010 old = expr, 8011 result = [], 8012 curLoop = set, 8013 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 8014 8015 while ( expr && set.length ) { 8016 for ( type in Expr.filter ) { 8017 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 8018 filter = Expr.filter[ type ]; 8019 left = match[1]; 8020 8021 anyFound = false; 8022 8023 match.splice(1,1); 8024 8025 if ( left.substr( left.length - 1 ) === "\\" ) { 8026 continue; 8027 } 8028 8029 if ( curLoop === result ) { 8030 result = []; 8031 } 8032 8033 if ( Expr.preFilter[ type ] ) { 8034 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 8035 8036 if ( !match ) { 8037 anyFound = found = true; 8038 8039 } else if ( match === true ) { 8040 continue; 8041 } 8042 } 8043 8044 if ( match ) { 8045 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 8046 if ( item ) { 8047 found = filter( item, match, i, curLoop ); 8048 pass = not ^ found; 8049 8050 if ( inplace && found != null ) { 8051 if ( pass ) { 8052 anyFound = true; 8053 8054 } else { 8055 curLoop[i] = false; 8056 } 8057 8058 } else if ( pass ) { 8059 result.push( item ); 8060 anyFound = true; 8061 } 8062 } 8063 } 8064 } 8065 8066 if ( found !== undefined ) { 8067 if ( !inplace ) { 8068 curLoop = result; 8069 } 8070 8071 expr = expr.replace( Expr.match[ type ], "" ); 8072 8073 if ( !anyFound ) { 8074 return []; 8075 } 8076 8077 break; 8078 } 8079 } 8080 } 8081 8082 // Improper expression 8083 if ( expr === old ) { 8084 if ( anyFound == null ) { 8085 Sizzle.error( expr ); 8086 8087 } else { 8088 break; 8089 } 8090 } 8091 8092 old = expr; 8093 } 8094 8095 return curLoop; 8096 }; 8097 8098 Sizzle.error = function( msg ) { 8099 throw new Error( "Syntax error, unrecognized expression: " + msg ); 8100 }; 8101 8102 var getText = Sizzle.getText = function( elem ) { 8103 var i, node, 8104 nodeType = elem.nodeType, 8105 ret = ""; 8106 8107 if ( nodeType ) { 8108 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 8109 // Use textContent || innerText for elements 8110 if ( typeof elem.textContent === 'string' ) { 8111 return elem.textContent; 8112 } else if ( typeof elem.innerText === 'string' ) { 8113 // Replace IE's carriage returns 8114 return elem.innerText.replace( rReturn, '' ); 8115 } else { 8116 // Traverse it's children 8117 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 8118 ret += getText( elem ); 8119 } 8120 } 8121 } else if ( nodeType === 3 || nodeType === 4 ) { 8122 return elem.nodeValue; 8123 } 8124 } else { 8125 8126 // If no nodeType, this is expected to be an array 8127 for ( i = 0; (node = elem[i]); i++ ) { 8128 // Do not traverse comment nodes 8129 if ( node.nodeType !== 8 ) { 8130 ret += getText( node ); 8131 } 8132 } 8133 } 8134 return ret; 8135 }; 8136 8137 var Expr = Sizzle.selectors = { 8138 order: [ "ID", "NAME", "TAG" ], 8139 8140 match: { 8141 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8142 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8143 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 8144 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 8145 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 8146 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 8147 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 8148 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 8149 }, 8150 8151 leftMatch: {}, 8152 8153 attrMap: { 8154 "class": "className", 8155 "for": "htmlFor" 8156 }, 8157 8158 attrHandle: { 8159 href: function( elem ) { 8160 return elem.getAttribute( "href" ); 8161 }, 8162 type: function( elem ) { 8163 return elem.getAttribute( "type" ); 8164 } 8165 }, 8166 8167 relative: { 8168 "+": function(checkSet, part){ 8169 var isPartStr = typeof part === "string", 8170 isTag = isPartStr && !rNonWord.test( part ), 8171 isPartStrNotTag = isPartStr && !isTag; 8172 8173 if ( isTag ) { 8174 part = part.toLowerCase(); 8175 } 8176 8177 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 8178 if ( (elem = checkSet[i]) ) { 8179 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 8180 8181 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 8182 elem || false : 8183 elem === part; 8184 } 8185 } 8186 8187 if ( isPartStrNotTag ) { 8188 Sizzle.filter( part, checkSet, true ); 8189 } 8190 }, 8191 8192 ">": function( checkSet, part ) { 8193 var elem, 8194 isPartStr = typeof part === "string", 8195 i = 0, 8196 l = checkSet.length; 8197 8198 if ( isPartStr && !rNonWord.test( part ) ) { 8199 part = part.toLowerCase(); 8200 8201 for ( ; i < l; i++ ) { 8202 elem = checkSet[i]; 8203 8204 if ( elem ) { 8205 var parent = elem.parentNode; 8206 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 8207 } 8208 } 8209 8210 } else { 8211 for ( ; i < l; i++ ) { 8212 elem = checkSet[i]; 8213 8214 if ( elem ) { 8215 checkSet[i] = isPartStr ? 8216 elem.parentNode : 8217 elem.parentNode === part; 8218 } 8219 } 8220 8221 if ( isPartStr ) { 8222 Sizzle.filter( part, checkSet, true ); 8223 } 8224 } 8225 }, 8226 8227 "": function(checkSet, part, isXML){ 8228 var nodeCheck, 8229 doneName = done++, 8230 checkFn = dirCheck; 8231 8232 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8233 part = part.toLowerCase(); 8234 nodeCheck = part; 8235 checkFn = dirNodeCheck; 8236 } 8237 8238 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 8239 }, 8240 8241 "~": function( checkSet, part, isXML ) { 8242 var nodeCheck, 8243 doneName = done++, 8244 checkFn = dirCheck; 8245 8246 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8247 part = part.toLowerCase(); 8248 nodeCheck = part; 8249 checkFn = dirNodeCheck; 8250 } 8251 8252 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 8253 } 8254 }, 8255 8256 find: { 8257 ID: function( match, context, isXML ) { 8258 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8259 var m = context.getElementById(match[1]); 8260 // Check parentNode to catch when Blackberry 4.6 returns 8261 // nodes that are no longer in the document #6963 8262 return m && m.parentNode ? [m] : []; 8263 } 8264 }, 8265 8266 NAME: function( match, context ) { 8267 if ( typeof context.getElementsByName !== "undefined" ) { 8268 var ret = [], 8269 results = context.getElementsByName( match[1] ); 8270 8271 for ( var i = 0, l = results.length; i < l; i++ ) { 8272 if ( results[i].getAttribute("name") === match[1] ) { 8273 ret.push( results[i] ); 8274 } 8275 } 8276 8277 return ret.length === 0 ? null : ret; 8278 } 8279 }, 8280 8281 TAG: function( match, context ) { 8282 if ( typeof context.getElementsByTagName !== "undefined" ) { 8283 return context.getElementsByTagName( match[1] ); 8284 } 8285 } 8286 }, 8287 preFilter: { 8288 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8289 match = " " + match[1].replace( rBackslash, "" ) + " "; 8290 8291 if ( isXML ) { 8292 return match; 8293 } 8294 8295 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8296 if ( elem ) { 8297 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8298 if ( !inplace ) { 8299 result.push( elem ); 8300 } 8301 8302 } else if ( inplace ) { 8303 curLoop[i] = false; 8304 } 8305 } 8306 } 8307 8308 return false; 8309 }, 8310 8311 ID: function( match ) { 8312 return match[1].replace( rBackslash, "" ); 8313 }, 8314 8315 TAG: function( match, curLoop ) { 8316 return match[1].replace( rBackslash, "" ).toLowerCase(); 8317 }, 8318 8319 CHILD: function( match ) { 8320 if ( match[1] === "nth" ) { 8321 if ( !match[2] ) { 8322 Sizzle.error( match[0] ); 8323 } 8324 8325 match[2] = match[2].replace(/^\+|\s*/g, ''); 8326 8327 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8328 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8329 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8330 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8331 8332 // calculate the numbers (first)n+(last) including if they are negative 8333 match[2] = (test[1] + (test[2] || 1)) - 0; 8334 match[3] = test[3] - 0; 8335 } 8336 else if ( match[2] ) { 8337 Sizzle.error( match[0] ); 8338 } 8339 8340 // TODO: Move to normal caching system 8341 match[0] = done++; 8342 8343 return match; 8344 }, 8345 8346 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8347 var name = match[1] = match[1].replace( rBackslash, "" ); 8348 8349 if ( !isXML && Expr.attrMap[name] ) { 8350 match[1] = Expr.attrMap[name]; 8351 } 8352 8353 // Handle if an un-quoted value was used 8354 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8355 8356 if ( match[2] === "~=" ) { 8357 match[4] = " " + match[4] + " "; 8358 } 8359 8360 return match; 8361 }, 8362 8363 PSEUDO: function( match, curLoop, inplace, result, not ) { 8364 if ( match[1] === "not" ) { 8365 // If we're dealing with a complex expression, or a simple one 8366 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8367 match[3] = Sizzle(match[3], null, null, curLoop); 8368 8369 } else { 8370 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8371 8372 if ( !inplace ) { 8373 result.push.apply( result, ret ); 8374 } 8375 8376 return false; 8377 } 8378 8379 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8380 return true; 8381 } 8382 8383 return match; 8384 }, 8385 8386 POS: function( match ) { 8387 match.unshift( true ); 8388 8389 return match; 8390 } 8391 }, 8392 8393 filters: { 8394 enabled: function( elem ) { 8395 return elem.disabled === false && elem.type !== "hidden"; 8396 }, 8397 8398 disabled: function( elem ) { 8399 return elem.disabled === true; 8400 }, 8401 8402 checked: function( elem ) { 8403 return elem.checked === true; 8404 }, 8405 8406 selected: function( elem ) { 8407 // Accessing this property makes selected-by-default 8408 // options in Safari work properly 8409 if ( elem.parentNode ) { 8410 elem.parentNode.selectedIndex; 8411 } 8412 8413 return elem.selected === true; 8414 }, 8415 8416 parent: function( elem ) { 8417 return !!elem.firstChild; 8418 }, 8419 8420 empty: function( elem ) { 8421 return !elem.firstChild; 8422 }, 8423 8424 has: function( elem, i, match ) { 8425 return !!Sizzle( match[3], elem ).length; 8426 }, 8427 8428 header: function( elem ) { 8429 return (/h\d/i).test( elem.nodeName ); 8430 }, 8431 8432 text: function( elem ) { 8433 var attr = elem.getAttribute( "type" ), type = elem.type; 8434 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8435 // use getAttribute instead to test this case 8436 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8437 }, 8438 8439 radio: function( elem ) { 8440 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8441 }, 8442 8443 checkbox: function( elem ) { 8444 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8445 }, 8446 8447 file: function( elem ) { 8448 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8449 }, 8450 8451 password: function( elem ) { 8452 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8453 }, 8454 8455 submit: function( elem ) { 8456 var name = elem.nodeName.toLowerCase(); 8457 return (name === "input" || name === "button") && "submit" === elem.type; 8458 }, 8459 8460 image: function( elem ) { 8461 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8462 }, 8463 8464 reset: function( elem ) { 8465 var name = elem.nodeName.toLowerCase(); 8466 return (name === "input" || name === "button") && "reset" === elem.type; 8467 }, 8468 8469 button: function( elem ) { 8470 var name = elem.nodeName.toLowerCase(); 8471 return name === "input" && "button" === elem.type || name === "button"; 8472 }, 8473 8474 input: function( elem ) { 8475 return (/input|select|textarea|button/i).test( elem.nodeName ); 8476 }, 8477 8478 focus: function( elem ) { 8479 return elem === elem.ownerDocument.activeElement; 8480 } 8481 }, 8482 setFilters: { 8483 first: function( elem, i ) { 8484 return i === 0; 8485 }, 8486 8487 last: function( elem, i, match, array ) { 8488 return i === array.length - 1; 8489 }, 8490 8491 even: function( elem, i ) { 8492 return i % 2 === 0; 8493 }, 8494 8495 odd: function( elem, i ) { 8496 return i % 2 === 1; 8497 }, 8498 8499 lt: function( elem, i, match ) { 8500 return i < match[3] - 0; 8501 }, 8502 8503 gt: function( elem, i, match ) { 8504 return i > match[3] - 0; 8505 }, 8506 8507 nth: function( elem, i, match ) { 8508 return match[3] - 0 === i; 8509 }, 8510 8511 eq: function( elem, i, match ) { 8512 return match[3] - 0 === i; 8513 } 8514 }, 8515 filter: { 8516 PSEUDO: function( elem, match, i, array ) { 8517 var name = match[1], 8518 filter = Expr.filters[ name ]; 8519 8520 if ( filter ) { 8521 return filter( elem, i, match, array ); 8522 8523 } else if ( name === "contains" ) { 8524 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8525 8526 } else if ( name === "not" ) { 8527 var not = match[3]; 8528 8529 for ( var j = 0, l = not.length; j < l; j++ ) { 8530 if ( not[j] === elem ) { 8531 return false; 8532 } 8533 } 8534 8535 return true; 8536 8537 } else { 8538 Sizzle.error( name ); 8539 } 8540 }, 8541 8542 CHILD: function( elem, match ) { 8543 var first, last, 8544 doneName, parent, cache, 8545 count, diff, 8546 type = match[1], 8547 node = elem; 8548 8549 switch ( type ) { 8550 case "only": 8551 case "first": 8552 while ( (node = node.previousSibling) ) { 8553 if ( node.nodeType === 1 ) { 8554 return false; 8555 } 8556 } 8557 8558 if ( type === "first" ) { 8559 return true; 8560 } 8561 8562 node = elem; 8563 8564 /* falls through */ 8565 case "last": 8566 while ( (node = node.nextSibling) ) { 8567 if ( node.nodeType === 1 ) { 8568 return false; 8569 } 8570 } 8571 8572 return true; 8573 8574 case "nth": 8575 first = match[2]; 8576 last = match[3]; 8577 8578 if ( first === 1 && last === 0 ) { 8579 return true; 8580 } 8581 8582 doneName = match[0]; 8583 parent = elem.parentNode; 8584 8585 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8586 count = 0; 8587 8588 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8589 if ( node.nodeType === 1 ) { 8590 node.nodeIndex = ++count; 8591 } 8592 } 8593 8594 parent[ expando ] = doneName; 8595 } 8596 8597 diff = elem.nodeIndex - last; 8598 8599 if ( first === 0 ) { 8600 return diff === 0; 8601 8602 } else { 8603 return ( diff % first === 0 && diff / first >= 0 ); 8604 } 8605 } 8606 }, 8607 8608 ID: function( elem, match ) { 8609 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8610 }, 8611 8612 TAG: function( elem, match ) { 8613 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8614 }, 8615 8616 CLASS: function( elem, match ) { 8617 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8618 .indexOf( match ) > -1; 8619 }, 8620 8621 ATTR: function( elem, match ) { 8622 var name = match[1], 8623 result = Sizzle.attr ? 8624 Sizzle.attr( elem, name ) : 8625 Expr.attrHandle[ name ] ? 8626 Expr.attrHandle[ name ]( elem ) : 8627 elem[ name ] != null ? 8628 elem[ name ] : 8629 elem.getAttribute( name ), 8630 value = result + "", 8631 type = match[2], 8632 check = match[4]; 8633 8634 return result == null ? 8635 type === "!=" : 8636 !type && Sizzle.attr ? 8637 result != null : 8638 type === "=" ? 8639 value === check : 8640 type === "*=" ? 8641 value.indexOf(check) >= 0 : 8642 type === "~=" ? 8643 (" " + value + " ").indexOf(check) >= 0 : 8644 !check ? 8645 value && result !== false : 8646 type === "!=" ? 8647 value !== check : 8648 type === "^=" ? 8649 value.indexOf(check) === 0 : 8650 type === "$=" ? 8651 value.substr(value.length - check.length) === check : 8652 type === "|=" ? 8653 value === check || value.substr(0, check.length + 1) === check + "-" : 8654 false; 8655 }, 8656 8657 POS: function( elem, match, i, array ) { 8658 var name = match[2], 8659 filter = Expr.setFilters[ name ]; 8660 8661 if ( filter ) { 8662 return filter( elem, i, match, array ); 8663 } 8664 } 8665 } 8666 }; 8667 8668 var origPOS = Expr.match.POS, 8669 fescape = function(all, num){ 8670 return "\\" + (num - 0 + 1); 8671 }; 8672 8673 for ( var type in Expr.match ) { 8674 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8675 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8676 } 8677 // Expose origPOS 8678 // "global" as in regardless of relation to brackets/parens 8679 Expr.match.globalPOS = origPOS; 8680 8681 var makeArray = function( array, results ) { 8682 array = Array.prototype.slice.call( array, 0 ); 8683 8684 if ( results ) { 8685 results.push.apply( results, array ); 8686 return results; 8687 } 8688 8689 return array; 8690 }; 8691 8692 // Perform a simple check to determine if the browser is capable of 8693 // converting a NodeList to an array using builtin methods. 8694 // Also verifies that the returned array holds DOM nodes 8695 // (which is not the case in the Blackberry browser) 8696 try { 8697 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8698 8699 // Provide a fallback method if it does not work 8700 } catch( e ) { 8701 makeArray = function( array, results ) { 8702 var i = 0, 8703 ret = results || []; 8704 8705 if ( toString.call(array) === "[object Array]" ) { 8706 Array.prototype.push.apply( ret, array ); 8707 8708 } else { 8709 if ( typeof array.length === "number" ) { 8710 for ( var l = array.length; i < l; i++ ) { 8711 ret.push( array[i] ); 8712 } 8713 8714 } else { 8715 for ( ; array[i]; i++ ) { 8716 ret.push( array[i] ); 8717 } 8718 } 8719 } 8720 8721 return ret; 8722 }; 8723 } 8724 8725 var sortOrder, siblingCheck; 8726 8727 if ( document.documentElement.compareDocumentPosition ) { 8728 sortOrder = function( a, b ) { 8729 if ( a === b ) { 8730 hasDuplicate = true; 8731 return 0; 8732 } 8733 8734 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8735 return a.compareDocumentPosition ? -1 : 1; 8736 } 8737 8738 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8739 }; 8740 8741 } else { 8742 sortOrder = function( a, b ) { 8743 // The nodes are identical, we can exit early 8744 if ( a === b ) { 8745 hasDuplicate = true; 8746 return 0; 8747 8748 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8749 } else if ( a.sourceIndex && b.sourceIndex ) { 8750 return a.sourceIndex - b.sourceIndex; 8751 } 8752 8753 var al, bl, 8754 ap = [], 8755 bp = [], 8756 aup = a.parentNode, 8757 bup = b.parentNode, 8758 cur = aup; 8759 8760 // If the nodes are siblings (or identical) we can do a quick check 8761 if ( aup === bup ) { 8762 return siblingCheck( a, b ); 8763 8764 // If no parents were found then the nodes are disconnected 8765 } else if ( !aup ) { 8766 return -1; 8767 8768 } else if ( !bup ) { 8769 return 1; 8770 } 8771 8772 // Otherwise they're somewhere else in the tree so we need 8773 // to build up a full list of the parentNodes for comparison 8774 while ( cur ) { 8775 ap.unshift( cur ); 8776 cur = cur.parentNode; 8777 } 8778 8779 cur = bup; 8780 8781 while ( cur ) { 8782 bp.unshift( cur ); 8783 cur = cur.parentNode; 8784 } 8785 8786 al = ap.length; 8787 bl = bp.length; 8788 8789 // Start walking down the tree looking for a discrepancy 8790 for ( var i = 0; i < al && i < bl; i++ ) { 8791 if ( ap[i] !== bp[i] ) { 8792 return siblingCheck( ap[i], bp[i] ); 8793 } 8794 } 8795 8796 // We ended someplace up the tree so do a sibling check 8797 return i === al ? 8798 siblingCheck( a, bp[i], -1 ) : 8799 siblingCheck( ap[i], b, 1 ); 8800 }; 8801 8802 siblingCheck = function( a, b, ret ) { 8803 if ( a === b ) { 8804 return ret; 8805 } 8806 8807 var cur = a.nextSibling; 8808 8809 while ( cur ) { 8810 if ( cur === b ) { 8811 return -1; 8812 } 8813 8814 cur = cur.nextSibling; 8815 } 8816 8817 return 1; 8818 }; 8819 } 8820 8821 // Check to see if the browser returns elements by name when 8822 // querying by getElementById (and provide a workaround) 8823 (function(){ 8824 // We're going to inject a fake input element with a specified name 8825 var form = document.createElement("div"), 8826 id = "script" + (new Date()).getTime(), 8827 root = document.documentElement; 8828 8829 form.innerHTML = "<a name='" + id + "'/>"; 8830 8831 // Inject it into the root element, check its status, and remove it quickly 8832 root.insertBefore( form, root.firstChild ); 8833 8834 // The workaround has to do additional checks after a getElementById 8835 // Which slows things down for other browsers (hence the branching) 8836 if ( document.getElementById( id ) ) { 8837 Expr.find.ID = function( match, context, isXML ) { 8838 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8839 var m = context.getElementById(match[1]); 8840 8841 return m ? 8842 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8843 [m] : 8844 undefined : 8845 []; 8846 } 8847 }; 8848 8849 Expr.filter.ID = function( elem, match ) { 8850 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8851 8852 return elem.nodeType === 1 && node && node.nodeValue === match; 8853 }; 8854 } 8855 8856 root.removeChild( form ); 8857 8858 // release memory in IE 8859 root = form = null; 8860 })(); 8861 8862 (function(){ 8863 // Check to see if the browser returns only elements 8864 // when doing getElementsByTagName("*") 8865 8866 // Create a fake element 8867 var div = document.createElement("div"); 8868 div.appendChild( document.createComment("") ); 8869 8870 // Make sure no comments are found 8871 if ( div.getElementsByTagName("*").length > 0 ) { 8872 Expr.find.TAG = function( match, context ) { 8873 var results = context.getElementsByTagName( match[1] ); 8874 8875 // Filter out possible comments 8876 if ( match[1] === "*" ) { 8877 var tmp = []; 8878 8879 for ( var i = 0; results[i]; i++ ) { 8880 if ( results[i].nodeType === 1 ) { 8881 tmp.push( results[i] ); 8882 } 8883 } 8884 8885 results = tmp; 8886 } 8887 8888 return results; 8889 }; 8890 } 8891 8892 // Check to see if an attribute returns normalized href attributes 8893 div.innerHTML = "<a href='#'></a>"; 8894 8895 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8896 div.firstChild.getAttribute("href") !== "#" ) { 8897 8898 Expr.attrHandle.href = function( elem ) { 8899 return elem.getAttribute( "href", 2 ); 8900 }; 8901 } 8902 8903 // release memory in IE 8904 div = null; 8905 })(); 8906 8907 if ( document.querySelectorAll ) { 8908 (function(){ 8909 var oldSizzle = Sizzle, 8910 div = document.createElement("div"), 8911 id = "__sizzle__"; 8912 8913 div.innerHTML = "<p class='TEST'></p>"; 8914 8915 // Safari can't handle uppercase or unicode characters when 8916 // in quirks mode. 8917 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8918 return; 8919 } 8920 8921 Sizzle = function( query, context, extra, seed ) { 8922 context = context || document; 8923 8924 // Only use querySelectorAll on non-XML documents 8925 // (ID selectors don't work in non-HTML documents) 8926 if ( !seed && !Sizzle.isXML(context) ) { 8927 // See if we find a selector to speed up 8928 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8929 8930 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8931 // Speed-up: Sizzle("TAG") 8932 if ( match[1] ) { 8933 return makeArray( context.getElementsByTagName( query ), extra ); 8934 8935 // Speed-up: Sizzle(".CLASS") 8936 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8937 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8938 } 8939 } 8940 8941 if ( context.nodeType === 9 ) { 8942 // Speed-up: Sizzle("body") 8943 // The body element only exists once, optimize finding it 8944 if ( query === "body" && context.body ) { 8945 return makeArray( [ context.body ], extra ); 8946 8947 // Speed-up: Sizzle("#ID") 8948 } else if ( match && match[3] ) { 8949 var elem = context.getElementById( match[3] ); 8950 8951 // Check parentNode to catch when Blackberry 4.6 returns 8952 // nodes that are no longer in the document #6963 8953 if ( elem && elem.parentNode ) { 8954 // Handle the case where IE and Opera return items 8955 // by name instead of ID 8956 if ( elem.id === match[3] ) { 8957 return makeArray( [ elem ], extra ); 8958 } 8959 8960 } else { 8961 return makeArray( [], extra ); 8962 } 8963 } 8964 8965 try { 8966 return makeArray( context.querySelectorAll(query), extra ); 8967 } catch(qsaError) {} 8968 8969 // qSA works strangely on Element-rooted queries 8970 // We can work around this by specifying an extra ID on the root 8971 // and working up from there (Thanks to Andrew Dupont for the technique) 8972 // IE 8 doesn't work on object elements 8973 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8974 var oldContext = context, 8975 old = context.getAttribute( "id" ), 8976 nid = old || id, 8977 hasParent = context.parentNode, 8978 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8979 8980 if ( !old ) { 8981 context.setAttribute( "id", nid ); 8982 } else { 8983 nid = nid.replace( /'/g, "\\$&" ); 8984 } 8985 if ( relativeHierarchySelector && hasParent ) { 8986 context = context.parentNode; 8987 } 8988 8989 try { 8990 if ( !relativeHierarchySelector || hasParent ) { 8991 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8992 } 8993 8994 } catch(pseudoError) { 8995 } finally { 8996 if ( !old ) { 8997 oldContext.removeAttribute( "id" ); 8998 } 8999 } 9000 } 9001 } 9002 9003 return oldSizzle(query, context, extra, seed); 9004 }; 9005 9006 for ( var prop in oldSizzle ) { 9007 Sizzle[ prop ] = oldSizzle[ prop ]; 9008 } 9009 9010 // release memory in IE 9011 div = null; 9012 })(); 9013 } 9014 9015 (function(){ 9016 var html = document.documentElement, 9017 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 9018 9019 if ( matches ) { 9020 // Check to see if it's possible to do matchesSelector 9021 // on a disconnected node (IE 9 fails this) 9022 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 9023 pseudoWorks = false; 9024 9025 try { 9026 // This should fail with an exception 9027 // Gecko does not error, returns false instead 9028 matches.call( document.documentElement, "[test!='']:sizzle" ); 9029 9030 } catch( pseudoError ) { 9031 pseudoWorks = true; 9032 } 9033 9034 Sizzle.matchesSelector = function( node, expr ) { 9035 // Make sure that attribute selectors are quoted 9036 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 9037 9038 if ( !Sizzle.isXML( node ) ) { 9039 try { 9040 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 9041 var ret = matches.call( node, expr ); 9042 9043 // IE 9's matchesSelector returns false on disconnected nodes 9044 if ( ret || !disconnectedMatch || 9045 // As well, disconnected nodes are said to be in a document 9046 // fragment in IE 9, so check for that 9047 node.document && node.document.nodeType !== 11 ) { 9048 return ret; 9049 } 9050 } 9051 } catch(e) {} 9052 } 9053 9054 return Sizzle(expr, null, null, [node]).length > 0; 9055 }; 9056 } 9057 })(); 9058 9059 (function(){ 9060 var div = document.createElement("div"); 9061 9062 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 9063 9064 // Opera can't find a second classname (in 9.6) 9065 // Also, make sure that getElementsByClassName actually exists 9066 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 9067 return; 9068 } 9069 9070 // Safari caches class attributes, doesn't catch changes (in 3.2) 9071 div.lastChild.className = "e"; 9072 9073 if ( div.getElementsByClassName("e").length === 1 ) { 9074 return; 9075 } 9076 9077 Expr.order.splice(1, 0, "CLASS"); 9078 Expr.find.CLASS = function( match, context, isXML ) { 9079 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 9080 return context.getElementsByClassName(match[1]); 9081 } 9082 }; 9083 9084 // release memory in IE 9085 div = null; 9086 })(); 9087 9088 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9089 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9090 var elem = checkSet[i]; 9091 9092 if ( elem ) { 9093 var match = false; 9094 9095 elem = elem[dir]; 9096 9097 while ( elem ) { 9098 if ( elem[ expando ] === doneName ) { 9099 match = checkSet[elem.sizset]; 9100 break; 9101 } 9102 9103 if ( elem.nodeType === 1 && !isXML ){ 9104 elem[ expando ] = doneName; 9105 elem.sizset = i; 9106 } 9107 9108 if ( elem.nodeName.toLowerCase() === cur ) { 9109 match = elem; 9110 break; 9111 } 9112 9113 elem = elem[dir]; 9114 } 9115 9116 checkSet[i] = match; 9117 } 9118 } 9119 } 9120 9121 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9122 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9123 var elem = checkSet[i]; 9124 9125 if ( elem ) { 9126 var match = false; 9127 9128 elem = elem[dir]; 9129 9130 while ( elem ) { 9131 if ( elem[ expando ] === doneName ) { 9132 match = checkSet[elem.sizset]; 9133 break; 9134 } 9135 9136 if ( elem.nodeType === 1 ) { 9137 if ( !isXML ) { 9138 elem[ expando ] = doneName; 9139 elem.sizset = i; 9140 } 9141 9142 if ( typeof cur !== "string" ) { 9143 if ( elem === cur ) { 9144 match = true; 9145 break; 9146 } 9147 9148 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 9149 match = elem; 9150 break; 9151 } 9152 } 9153 9154 elem = elem[dir]; 9155 } 9156 9157 checkSet[i] = match; 9158 } 9159 } 9160 } 9161 9162 if ( document.documentElement.contains ) { 9163 Sizzle.contains = function( a, b ) { 9164 return a !== b && (a.contains ? a.contains(b) : true); 9165 }; 9166 9167 } else if ( document.documentElement.compareDocumentPosition ) { 9168 Sizzle.contains = function( a, b ) { 9169 return !!(a.compareDocumentPosition(b) & 16); 9170 }; 9171 9172 } else { 9173 Sizzle.contains = function() { 9174 return false; 9175 }; 9176 } 9177 9178 Sizzle.isXML = function( elem ) { 9179 // documentElement is verified for cases where it doesn't yet exist 9180 // (such as loading iframes in IE - #4833) 9181 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 9182 9183 return documentElement ? documentElement.nodeName !== "HTML" : false; 9184 }; 9185 9186 var posProcess = function( selector, context, seed ) { 9187 var match, 9188 tmpSet = [], 9189 later = "", 9190 root = context.nodeType ? [context] : context; 9191 9192 // Position selectors must be done after the filter 9193 // And so must :not(positional) so we move all PSEUDOs to the end 9194 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 9195 later += match[0]; 9196 selector = selector.replace( Expr.match.PSEUDO, "" ); 9197 } 9198 9199 selector = Expr.relative[selector] ? selector + "*" : selector; 9200 9201 for ( var i = 0, l = root.length; i < l; i++ ) { 9202 Sizzle( selector, root[i], tmpSet, seed ); 9203 } 9204 9205 return Sizzle.filter( later, tmpSet ); 9206 }; 9207 9208 // EXPOSE 9209 9210 window.tinymce.dom.Sizzle = Sizzle; 9211 9212 })(); 9213 9214 (function(tinymce) { 9215 tinymce.dom.Element = function(id, settings) { 9216 var t = this, dom, el; 9217 9218 t.settings = settings = settings || {}; 9219 t.id = id; 9220 t.dom = dom = settings.dom || tinymce.DOM; 9221 9222 // Only IE leaks DOM references, this is a lot faster 9223 if (!tinymce.isIE) 9224 el = dom.get(t.id); 9225 9226 tinymce.each( 9227 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 9228 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 9229 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 9230 'isHidden,setHTML,get').split(/,/), function(k) { 9231 t[k] = function() { 9232 var a = [id], i; 9233 9234 for (i = 0; i < arguments.length; i++) 9235 a.push(arguments[i]); 9236 9237 a = dom[k].apply(dom, a); 9238 t.update(k); 9239 9240 return a; 9241 }; 9242 } 9243 ); 9244 9245 tinymce.extend(t, { 9246 on : function(n, f, s) { 9247 return tinymce.dom.Event.add(t.id, n, f, s); 9248 }, 9249 9250 getXY : function() { 9251 return { 9252 x : parseInt(t.getStyle('left')), 9253 y : parseInt(t.getStyle('top')) 9254 }; 9255 }, 9256 9257 getSize : function() { 9258 var n = dom.get(t.id); 9259 9260 return { 9261 w : parseInt(t.getStyle('width') || n.clientWidth), 9262 h : parseInt(t.getStyle('height') || n.clientHeight) 9263 }; 9264 }, 9265 9266 moveTo : function(x, y) { 9267 t.setStyles({left : x, top : y}); 9268 }, 9269 9270 moveBy : function(x, y) { 9271 var p = t.getXY(); 9272 9273 t.moveTo(p.x + x, p.y + y); 9274 }, 9275 9276 resizeTo : function(w, h) { 9277 t.setStyles({width : w, height : h}); 9278 }, 9279 9280 resizeBy : function(w, h) { 9281 var s = t.getSize(); 9282 9283 t.resizeTo(s.w + w, s.h + h); 9284 }, 9285 9286 update : function(k) { 9287 var b; 9288 9289 if (tinymce.isIE6 && settings.blocker) { 9290 k = k || ''; 9291 9292 // Ignore getters 9293 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9294 return; 9295 9296 // Remove blocker on remove 9297 if (k == 'remove') { 9298 dom.remove(t.blocker); 9299 return; 9300 } 9301 9302 if (!t.blocker) { 9303 t.blocker = dom.uniqueId(); 9304 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9305 dom.setStyle(b, 'opacity', 0); 9306 } else 9307 b = dom.get(t.blocker); 9308 9309 dom.setStyles(b, { 9310 left : t.getStyle('left', 1), 9311 top : t.getStyle('top', 1), 9312 width : t.getStyle('width', 1), 9313 height : t.getStyle('height', 1), 9314 display : t.getStyle('display', 1), 9315 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9316 }); 9317 } 9318 } 9319 }); 9320 }; 9321 })(tinymce); 9322 (function(tinymce) { 9323 function trimNl(s) { 9324 return s.replace(/[\n\r]+/g, ''); 9325 }; 9326 9327 // Shorten names 9328 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9329 9330 tinymce.create('tinymce.dom.Selection', { 9331 Selection : function(dom, win, serializer, editor) { 9332 var t = this; 9333 9334 t.dom = dom; 9335 t.win = win; 9336 t.serializer = serializer; 9337 t.editor = editor; 9338 9339 // Add events 9340 each([ 9341 'onBeforeSetContent', 9342 9343 'onBeforeGetContent', 9344 9345 'onSetContent', 9346 9347 'onGetContent' 9348 ], function(e) { 9349 t[e] = new tinymce.util.Dispatcher(t); 9350 }); 9351 9352 // No W3C Range support 9353 if (!t.win.getSelection) 9354 t.tridentSel = new tinymce.dom.TridentSelection(t); 9355 9356 if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel) 9357 this._fixIESelection(); 9358 9359 // Prevent leaks 9360 tinymce.addUnload(t.destroy, t); 9361 }, 9362 9363 setCursorLocation: function(node, offset) { 9364 var t = this; var r = t.dom.createRng(); 9365 r.setStart(node, offset); 9366 r.setEnd(node, offset); 9367 t.setRng(r); 9368 t.collapse(false); 9369 }, 9370 getContent : function(s) { 9371 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9372 9373 s = s || {}; 9374 wb = wa = ''; 9375 s.get = true; 9376 s.format = s.format || 'html'; 9377 s.forced_root_block = ''; 9378 t.onBeforeGetContent.dispatch(t, s); 9379 9380 if (s.format == 'text') 9381 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9382 9383 if (r.cloneContents) { 9384 n = r.cloneContents(); 9385 9386 if (n) 9387 e.appendChild(n); 9388 } else if (is(r.item) || is(r.htmlText)) { 9389 // IE will produce invalid markup if elements are present that 9390 // it doesn't understand like custom elements or HTML5 elements. 9391 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9392 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9393 e.removeChild(e.firstChild); 9394 } else 9395 e.innerHTML = r.toString(); 9396 9397 // Keep whitespace before and after 9398 if (/^\s/.test(e.innerHTML)) 9399 wb = ' '; 9400 9401 if (/\s+$/.test(e.innerHTML)) 9402 wa = ' '; 9403 9404 s.getInner = true; 9405 9406 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9407 t.onGetContent.dispatch(t, s); 9408 9409 return s.content; 9410 }, 9411 9412 setContent : function(content, args) { 9413 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9414 9415 args = args || {format : 'html'}; 9416 args.set = true; 9417 content = args.content = content; 9418 9419 // Dispatch before set content event 9420 if (!args.no_events) 9421 self.onBeforeSetContent.dispatch(self, args); 9422 9423 content = args.content; 9424 9425 if (rng.insertNode) { 9426 // Make caret marker since insertNode places the caret in the beginning of text after insert 9427 content += '<span id="__caret">_</span>'; 9428 9429 // Delete and insert new node 9430 if (rng.startContainer == doc && rng.endContainer == doc) { 9431 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9432 doc.body.innerHTML = content; 9433 } else { 9434 rng.deleteContents(); 9435 9436 if (doc.body.childNodes.length === 0) { 9437 doc.body.innerHTML = content; 9438 } else { 9439 // createContextualFragment doesn't exists in IE 9 DOMRanges 9440 if (rng.createContextualFragment) { 9441 rng.insertNode(rng.createContextualFragment(content)); 9442 } else { 9443 // Fake createContextualFragment call in IE 9 9444 frag = doc.createDocumentFragment(); 9445 temp = doc.createElement('div'); 9446 9447 frag.appendChild(temp); 9448 temp.outerHTML = content; 9449 9450 rng.insertNode(frag); 9451 } 9452 } 9453 } 9454 9455 // Move to caret marker 9456 caretNode = self.dom.get('__caret'); 9457 9458 // Make sure we wrap it compleatly, Opera fails with a simple select call 9459 rng = doc.createRange(); 9460 rng.setStartBefore(caretNode); 9461 rng.setEndBefore(caretNode); 9462 self.setRng(rng); 9463 9464 // Remove the caret position 9465 self.dom.remove('__caret'); 9466 9467 try { 9468 self.setRng(rng); 9469 } catch (ex) { 9470 // Might fail on Opera for some odd reason 9471 } 9472 } else { 9473 if (rng.item) { 9474 // Delete content and get caret text selection 9475 doc.execCommand('Delete', false, null); 9476 rng = self.getRng(); 9477 } 9478 9479 // Explorer removes spaces from the beginning of pasted contents 9480 if (/^\s+/.test(content)) { 9481 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9482 self.dom.remove('__mce_tmp'); 9483 } else 9484 rng.pasteHTML(content); 9485 } 9486 9487 // Dispatch set content event 9488 if (!args.no_events) 9489 self.onSetContent.dispatch(self, args); 9490 }, 9491 9492 getStart : function() { 9493 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9494 9495 if (rng.duplicate || rng.item) { 9496 // Control selection, return first item 9497 if (rng.item) 9498 return rng.item(0); 9499 9500 // Get start element 9501 checkRng = rng.duplicate(); 9502 checkRng.collapse(1); 9503 startElement = checkRng.parentElement(); 9504 if (startElement.ownerDocument !== self.dom.doc) { 9505 startElement = self.dom.getRoot(); 9506 } 9507 9508 // Check if range parent is inside the start element, then return the inner parent element 9509 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9510 parentElement = node = rng.parentElement(); 9511 while (node = node.parentNode) { 9512 if (node == startElement) { 9513 startElement = parentElement; 9514 break; 9515 } 9516 } 9517 9518 return startElement; 9519 } else { 9520 startElement = rng.startContainer; 9521 9522 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9523 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9524 9525 if (startElement && startElement.nodeType == 3) 9526 return startElement.parentNode; 9527 9528 return startElement; 9529 } 9530 }, 9531 9532 getEnd : function() { 9533 var self = this, rng = self.getRng(), endElement, endOffset; 9534 9535 if (rng.duplicate || rng.item) { 9536 if (rng.item) 9537 return rng.item(0); 9538 9539 rng = rng.duplicate(); 9540 rng.collapse(0); 9541 endElement = rng.parentElement(); 9542 if (endElement.ownerDocument !== self.dom.doc) { 9543 endElement = self.dom.getRoot(); 9544 } 9545 9546 if (endElement && endElement.nodeName == 'BODY') 9547 return endElement.lastChild || endElement; 9548 9549 return endElement; 9550 } else { 9551 endElement = rng.endContainer; 9552 endOffset = rng.endOffset; 9553 9554 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9555 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9556 9557 if (endElement && endElement.nodeType == 3) 9558 return endElement.parentNode; 9559 9560 return endElement; 9561 } 9562 }, 9563 9564 getBookmark : function(type, normalized) { 9565 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9566 9567 function findIndex(name, element) { 9568 var index = 0; 9569 9570 each(dom.select(name), function(node, i) { 9571 if (node == element) 9572 index = i; 9573 }); 9574 9575 return index; 9576 }; 9577 9578 function normalizeTableCellSelection(rng) { 9579 function moveEndPoint(start) { 9580 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9581 9582 container = rng[prefix + 'Container']; 9583 offset = rng[prefix + 'Offset']; 9584 9585 if (container.nodeType == 1 && container.nodeName == "TR") { 9586 childNodes = container.childNodes; 9587 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9588 if (container) { 9589 offset = start ? 0 : container.childNodes.length; 9590 rng['set' + (start ? 'Start' : 'End')](container, offset); 9591 } 9592 } 9593 }; 9594 9595 moveEndPoint(true); 9596 moveEndPoint(); 9597 9598 return rng; 9599 }; 9600 9601 function getLocation() { 9602 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9603 9604 function getPoint(rng, start) { 9605 var container = rng[start ? 'startContainer' : 'endContainer'], 9606 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9607 9608 if (container.nodeType == 3) { 9609 if (normalized) { 9610 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9611 offset += node.nodeValue.length; 9612 } 9613 9614 point.push(offset); 9615 } else { 9616 childNodes = container.childNodes; 9617 9618 if (offset >= childNodes.length && childNodes.length) { 9619 after = 1; 9620 offset = Math.max(0, childNodes.length - 1); 9621 } 9622 9623 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9624 } 9625 9626 for (; container && container != root; container = container.parentNode) 9627 point.push(t.dom.nodeIndex(container, normalized)); 9628 9629 return point; 9630 }; 9631 9632 bookmark.start = getPoint(rng, true); 9633 9634 if (!t.isCollapsed()) 9635 bookmark.end = getPoint(rng); 9636 9637 return bookmark; 9638 }; 9639 9640 if (type == 2) { 9641 if (t.tridentSel) 9642 return t.tridentSel.getBookmark(type); 9643 9644 return getLocation(); 9645 } 9646 9647 // Handle simple range 9648 if (type) { 9649 rng = t.getRng(); 9650 9651 if (rng.setStart) { 9652 rng = { 9653 startContainer: rng.startContainer, 9654 startOffset: rng.startOffset, 9655 endContainer: rng.endContainer, 9656 endOffset: rng.endOffset 9657 }; 9658 } 9659 9660 return {rng : rng}; 9661 } 9662 9663 rng = t.getRng(); 9664 id = dom.uniqueId(); 9665 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9666 styles = 'overflow:hidden;line-height:0px'; 9667 9668 // Explorer method 9669 if (rng.duplicate || rng.item) { 9670 // Text selection 9671 if (!rng.item) { 9672 rng2 = rng.duplicate(); 9673 9674 try { 9675 // Insert start marker 9676 rng.collapse(); 9677 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9678 9679 // Insert end marker 9680 if (!collapsed) { 9681 rng2.collapse(false); 9682 9683 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9684 rng.moveToElementText(rng2.parentElement()); 9685 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9686 rng2.move('character', -1); 9687 9688 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9689 } 9690 } catch (ex) { 9691 // IE might throw unspecified error so lets ignore it 9692 return null; 9693 } 9694 } else { 9695 // Control selection 9696 element = rng.item(0); 9697 name = element.nodeName; 9698 9699 return {name : name, index : findIndex(name, element)}; 9700 } 9701 } else { 9702 element = t.getNode(); 9703 name = element.nodeName; 9704 if (name == 'IMG') 9705 return {name : name, index : findIndex(name, element)}; 9706 9707 // W3C method 9708 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9709 9710 // Insert end marker 9711 if (!collapsed) { 9712 rng2.collapse(false); 9713 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9714 } 9715 9716 rng = normalizeTableCellSelection(rng); 9717 rng.collapse(true); 9718 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9719 } 9720 9721 t.moveToBookmark({id : id, keep : 1}); 9722 9723 return {id : id}; 9724 }, 9725 9726 moveToBookmark : function(bookmark) { 9727 var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset; 9728 9729 function setEndPoint(start) { 9730 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9731 9732 if (point) { 9733 offset = point[0]; 9734 9735 // Find container node 9736 for (node = root, i = point.length - 1; i >= 1; i--) { 9737 children = node.childNodes; 9738 9739 if (point[i] > children.length - 1) 9740 return; 9741 9742 node = children[point[i]]; 9743 } 9744 9745 // Move text offset to best suitable location 9746 if (node.nodeType === 3) 9747 offset = Math.min(point[0], node.nodeValue.length); 9748 9749 // Move element offset to best suitable location 9750 if (node.nodeType === 1) 9751 offset = Math.min(point[0], node.childNodes.length); 9752 9753 // Set offset within container node 9754 if (start) 9755 rng.setStart(node, offset); 9756 else 9757 rng.setEnd(node, offset); 9758 } 9759 9760 return true; 9761 }; 9762 9763 function restoreEndPoint(suffix) { 9764 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9765 9766 if (marker) { 9767 node = marker.parentNode; 9768 9769 if (suffix == 'start') { 9770 if (!keep) { 9771 idx = dom.nodeIndex(marker); 9772 } else { 9773 node = marker.firstChild; 9774 idx = 1; 9775 } 9776 9777 startContainer = endContainer = node; 9778 startOffset = endOffset = idx; 9779 } else { 9780 if (!keep) { 9781 idx = dom.nodeIndex(marker); 9782 } else { 9783 node = marker.firstChild; 9784 idx = 1; 9785 } 9786 9787 endContainer = node; 9788 endOffset = idx; 9789 } 9790 9791 if (!keep) { 9792 prev = marker.previousSibling; 9793 next = marker.nextSibling; 9794 9795 // Remove all marker text nodes 9796 each(tinymce.grep(marker.childNodes), function(node) { 9797 if (node.nodeType == 3) 9798 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9799 }); 9800 9801 // Remove marker but keep children if for example contents where inserted into the marker 9802 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9803 while (marker = dom.get(bookmark.id + '_' + suffix)) 9804 dom.remove(marker, 1); 9805 9806 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9807 // 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 9808 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9809 idx = prev.nodeValue.length; 9810 prev.appendData(next.nodeValue); 9811 dom.remove(next); 9812 9813 if (suffix == 'start') { 9814 startContainer = endContainer = prev; 9815 startOffset = endOffset = idx; 9816 } else { 9817 endContainer = prev; 9818 endOffset = idx; 9819 } 9820 } 9821 } 9822 } 9823 }; 9824 9825 function addBogus(node) { 9826 // Adds a bogus BR element for empty block elements 9827 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9828 node.innerHTML = '<br data-mce-bogus="1" />'; 9829 9830 return node; 9831 }; 9832 9833 if (bookmark) { 9834 if (bookmark.start) { 9835 rng = dom.createRng(); 9836 root = dom.getRoot(); 9837 9838 if (t.tridentSel) 9839 return t.tridentSel.moveToBookmark(bookmark); 9840 9841 if (setEndPoint(true) && setEndPoint()) { 9842 t.setRng(rng); 9843 } 9844 } else if (bookmark.id) { 9845 // Restore start/end points 9846 restoreEndPoint('start'); 9847 restoreEndPoint('end'); 9848 9849 if (startContainer) { 9850 rng = dom.createRng(); 9851 rng.setStart(addBogus(startContainer), startOffset); 9852 rng.setEnd(addBogus(endContainer), endOffset); 9853 t.setRng(rng); 9854 } 9855 } else if (bookmark.name) { 9856 t.select(dom.select(bookmark.name)[bookmark.index]); 9857 } else if (bookmark.rng) { 9858 rng = bookmark.rng; 9859 9860 if (rng.startContainer) { 9861 rng2 = t.dom.createRng(); 9862 9863 try { 9864 rng2.setStart(rng.startContainer, rng.startOffset); 9865 rng2.setEnd(rng.endContainer, rng.endOffset); 9866 } catch (e) { 9867 // Might fail with index error 9868 } 9869 9870 rng = rng2; 9871 } 9872 9873 t.setRng(rng); 9874 } 9875 } 9876 }, 9877 9878 select : function(node, content) { 9879 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9880 9881 function setPoint(node, start) { 9882 var walker = new TreeWalker(node, node); 9883 9884 do { 9885 // Text node 9886 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9887 if (start) 9888 rng.setStart(node, 0); 9889 else 9890 rng.setEnd(node, node.nodeValue.length); 9891 9892 return; 9893 } 9894 9895 // BR element 9896 if (node.nodeName == 'BR') { 9897 if (start) 9898 rng.setStartBefore(node); 9899 else 9900 rng.setEndBefore(node); 9901 9902 return; 9903 } 9904 } while (node = (start ? walker.next() : walker.prev())); 9905 }; 9906 9907 if (node) { 9908 idx = dom.nodeIndex(node); 9909 rng.setStart(node.parentNode, idx); 9910 rng.setEnd(node.parentNode, idx + 1); 9911 9912 // Find first/last text node or BR element 9913 if (content) { 9914 setPoint(node, 1); 9915 setPoint(node); 9916 } 9917 9918 t.setRng(rng); 9919 } 9920 9921 return node; 9922 }, 9923 9924 isCollapsed : function() { 9925 var t = this, r = t.getRng(), s = t.getSel(); 9926 9927 if (!r || r.item) 9928 return false; 9929 9930 if (r.compareEndPoints) 9931 return r.compareEndPoints('StartToEnd', r) === 0; 9932 9933 return !s || r.collapsed; 9934 }, 9935 9936 collapse : function(to_start) { 9937 var self = this, rng = self.getRng(), node; 9938 9939 // Control range on IE 9940 if (rng.item) { 9941 node = rng.item(0); 9942 rng = self.win.document.body.createTextRange(); 9943 rng.moveToElementText(node); 9944 } 9945 9946 rng.collapse(!!to_start); 9947 self.setRng(rng); 9948 }, 9949 9950 getSel : function() { 9951 var t = this, w = this.win; 9952 9953 return w.getSelection ? w.getSelection() : w.document.selection; 9954 }, 9955 9956 getRng : function(w3c) { 9957 var self = this, selection, rng, elm, doc = self.win.document; 9958 9959 // Found tridentSel object then we need to use that one 9960 if (w3c && self.tridentSel) { 9961 return self.tridentSel.getRangeAt(0); 9962 } 9963 9964 try { 9965 if (selection = self.getSel()) { 9966 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9967 } 9968 } catch (ex) { 9969 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9970 } 9971 9972 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9973 if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) { 9974 elm = doc.selection.createRange().item(0); 9975 rng = doc.createRange(); 9976 rng.setStartBefore(elm); 9977 rng.setEndAfter(elm); 9978 } 9979 9980 // No range found then create an empty one 9981 // This can occur when the editor is placed in a hidden container element on Gecko 9982 // Or on IE when there was an exception 9983 if (!rng) { 9984 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9985 } 9986 9987 // If range is at start of document then move it to start of body 9988 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9989 elm = self.dom.getRoot(); 9990 rng.setStart(elm, 0); 9991 rng.setEnd(elm, 0); 9992 } 9993 9994 if (self.selectedRange && self.explicitRange) { 9995 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9996 // Safari, Opera and Chrome only ever select text which causes the range to change. 9997 // This lets us use the originally set range if the selection hasn't been changed by the user. 9998 rng = self.explicitRange; 9999 } else { 10000 self.selectedRange = null; 10001 self.explicitRange = null; 10002 } 10003 } 10004 10005 return rng; 10006 }, 10007 10008 setRng : function(r, forward) { 10009 var s, t = this; 10010 10011 if (!t.tridentSel) { 10012 s = t.getSel(); 10013 10014 if (s) { 10015 t.explicitRange = r; 10016 10017 try { 10018 s.removeAllRanges(); 10019 } catch (ex) { 10020 // IE9 might throw errors here don't know why 10021 } 10022 10023 s.addRange(r); 10024 10025 // Forward is set to false and we have an extend function 10026 if (forward === false && s.extend) { 10027 s.collapse(r.endContainer, r.endOffset); 10028 s.extend(r.startContainer, r.startOffset); 10029 } 10030 10031 // adding range isn't always successful so we need to check range count otherwise an exception can occur 10032 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 10033 } 10034 } else { 10035 // Is W3C Range 10036 if (r.cloneRange) { 10037 try { 10038 t.tridentSel.addRange(r); 10039 return; 10040 } catch (ex) { 10041 //IE9 throws an error here if called before selection is placed in the editor 10042 } 10043 } 10044 10045 // Is IE specific range 10046 try { 10047 r.select(); 10048 } catch (ex) { 10049 // Needed for some odd IE bug #1843306 10050 } 10051 } 10052 }, 10053 10054 setNode : function(n) { 10055 var t = this; 10056 10057 t.setContent(t.dom.getOuterHTML(n)); 10058 10059 return n; 10060 }, 10061 10062 getNode : function() { 10063 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 10064 10065 function skipEmptyTextNodes(n, forwards) { 10066 var orig = n; 10067 while (n && n.nodeType === 3 && n.length === 0) { 10068 n = forwards ? n.nextSibling : n.previousSibling; 10069 } 10070 return n || orig; 10071 }; 10072 10073 // Range maybe lost after the editor is made visible again 10074 if (!rng) 10075 return t.dom.getRoot(); 10076 10077 if (rng.setStart) { 10078 elm = rng.commonAncestorContainer; 10079 10080 // Handle selection a image or other control like element such as anchors 10081 if (!rng.collapsed) { 10082 if (rng.startContainer == rng.endContainer) { 10083 if (rng.endOffset - rng.startOffset < 2) { 10084 if (rng.startContainer.hasChildNodes()) 10085 elm = rng.startContainer.childNodes[rng.startOffset]; 10086 } 10087 } 10088 10089 // If the anchor node is a element instead of a text node then return this element 10090 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 10091 // return sel.anchorNode.childNodes[sel.anchorOffset]; 10092 10093 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 10094 // This happens when you double click an underlined word in FireFox. 10095 if (start.nodeType === 3 && end.nodeType === 3) { 10096 if (start.length === rng.startOffset) { 10097 start = skipEmptyTextNodes(start.nextSibling, true); 10098 } else { 10099 start = start.parentNode; 10100 } 10101 if (rng.endOffset === 0) { 10102 end = skipEmptyTextNodes(end.previousSibling, false); 10103 } else { 10104 end = end.parentNode; 10105 } 10106 10107 if (start && start === end) 10108 return start; 10109 } 10110 } 10111 10112 if (elm && elm.nodeType == 3) 10113 return elm.parentNode; 10114 10115 return elm; 10116 } 10117 10118 return rng.item ? rng.item(0) : rng.parentElement(); 10119 }, 10120 10121 getSelectedBlocks : function(st, en) { 10122 var t = this, dom = t.dom, sb, eb, n, bl = []; 10123 10124 sb = dom.getParent(st || t.getStart(), dom.isBlock); 10125 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 10126 10127 if (sb) 10128 bl.push(sb); 10129 10130 if (sb && eb && sb != eb) { 10131 n = sb; 10132 10133 var walker = new TreeWalker(sb, dom.getRoot()); 10134 while ((n = walker.next()) && n != eb) { 10135 if (dom.isBlock(n)) 10136 bl.push(n); 10137 } 10138 } 10139 10140 if (eb && sb != eb) 10141 bl.push(eb); 10142 10143 return bl; 10144 }, 10145 10146 isForward: function(){ 10147 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 10148 10149 // No support for selection direction then always return true 10150 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 10151 return true; 10152 } 10153 10154 anchorRange = dom.createRng(); 10155 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 10156 anchorRange.collapse(true); 10157 10158 focusRange = dom.createRng(); 10159 focusRange.setStart(sel.focusNode, sel.focusOffset); 10160 focusRange.collapse(true); 10161 10162 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 10163 }, 10164 10165 normalize : function() { 10166 var self = this, rng, normalized, collapsed, node, sibling; 10167 10168 function normalizeEndPoint(start) { 10169 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 10170 10171 function hasBrBeforeAfter(node, left) { 10172 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 10173 10174 while (node = walker[left ? 'prev' : 'next']()) { 10175 if (node.nodeName === "BR") { 10176 return true; 10177 } 10178 } 10179 }; 10180 10181 // Walks the dom left/right to find a suitable text node to move the endpoint into 10182 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 10183 function findTextNodeRelative(left, startNode) { 10184 var walker, lastInlineElement; 10185 10186 startNode = startNode || container; 10187 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 10188 10189 // Walk left until we hit a text node we can move to or a block/br/img 10190 while (node = walker[left ? 'prev' : 'next']()) { 10191 // Found text node that has a length 10192 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10193 container = node; 10194 offset = left ? node.nodeValue.length : 0; 10195 normalized = true; 10196 return; 10197 } 10198 10199 // Break if we find a block or a BR/IMG/INPUT etc 10200 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10201 return; 10202 } 10203 10204 lastInlineElement = node; 10205 } 10206 10207 // Only fetch the last inline element when in caret mode for now 10208 if (collapsed && lastInlineElement) { 10209 container = lastInlineElement; 10210 normalized = true; 10211 offset = 0; 10212 } 10213 }; 10214 10215 container = rng[(start ? 'start' : 'end') + 'Container']; 10216 offset = rng[(start ? 'start' : 'end') + 'Offset']; 10217 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 10218 10219 // If the container is a document move it to the body element 10220 if (container.nodeType === 9) { 10221 container = dom.getRoot(); 10222 offset = 0; 10223 } 10224 10225 // If the container is body try move it into the closest text node or position 10226 if (container === body) { 10227 // If start is before/after a image, table etc 10228 if (start) { 10229 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 10230 if (node) { 10231 nodeName = node.nodeName.toLowerCase(); 10232 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 10233 return; 10234 } 10235 } 10236 } 10237 10238 // Resolve the index 10239 if (container.hasChildNodes()) { 10240 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 10241 offset = 0; 10242 10243 // Don't walk into elements that doesn't have any child nodes like a IMG 10244 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 10245 // Walk the DOM to find a text node to place the caret at or a BR 10246 node = container; 10247 walker = new TreeWalker(container, body); 10248 10249 do { 10250 // Found a text node use that position 10251 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10252 offset = start ? 0 : node.nodeValue.length; 10253 container = node; 10254 normalized = true; 10255 break; 10256 } 10257 10258 // Found a BR/IMG element that we can place the caret before 10259 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10260 offset = dom.nodeIndex(node); 10261 container = node.parentNode; 10262 10263 // Put caret after image when moving the end point 10264 if (node.nodeName == "IMG" && !start) { 10265 offset++; 10266 } 10267 10268 normalized = true; 10269 break; 10270 } 10271 } while (node = (start ? walker.next() : walker.prev())); 10272 } 10273 } 10274 } 10275 10276 // Lean the caret to the left if possible 10277 if (collapsed) { 10278 // So this: <b>x</b><i>|x</i> 10279 // Becomes: <b>x|</b><i>x</i> 10280 // Seems that only gecko has issues with this 10281 if (container.nodeType === 3 && offset === 0) { 10282 findTextNodeRelative(true); 10283 } 10284 10285 // Lean left into empty inline elements when the caret is before a BR 10286 // So this: <i><b></b><i>|<br></i> 10287 // Becomes: <i><b>|</b><i><br></i> 10288 // Seems that only gecko has issues with this 10289 if (container.nodeType === 1) { 10290 node = container.childNodes[offset]; 10291 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 10292 findTextNodeRelative(true, container.childNodes[offset]); 10293 } 10294 } 10295 } 10296 10297 // Lean the start of the selection right if possible 10298 // So this: x[<b>x]</b> 10299 // Becomes: x<b>[x]</b> 10300 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 10301 findTextNodeRelative(false); 10302 } 10303 10304 // Set endpoint if it was normalized 10305 if (normalized) 10306 rng['set' + (start ? 'Start' : 'End')](container, offset); 10307 }; 10308 10309 // Normalize only on non IE browsers for now 10310 if (tinymce.isIE) 10311 return; 10312 10313 rng = self.getRng(); 10314 collapsed = rng.collapsed; 10315 10316 // Normalize the end points 10317 normalizeEndPoint(true); 10318 10319 if (!collapsed) 10320 normalizeEndPoint(); 10321 10322 // Set the selection if it was normalized 10323 if (normalized) { 10324 // If it was collapsed then make sure it still is 10325 if (collapsed) { 10326 rng.collapse(true); 10327 } 10328 10329 //console.log(self.dom.dumpRng(rng)); 10330 self.setRng(rng, self.isForward()); 10331 } 10332 }, 10333 10334 selectorChanged: function(selector, callback) { 10335 var self = this, currentSelectors; 10336 10337 if (!self.selectorChangedData) { 10338 self.selectorChangedData = {}; 10339 currentSelectors = {}; 10340 10341 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10342 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10343 10344 // Check for new matching selectors 10345 each(self.selectorChangedData, function(callbacks, selector) { 10346 each(parents, function(node) { 10347 if (dom.is(node, selector)) { 10348 if (!currentSelectors[selector]) { 10349 // Execute callbacks 10350 each(callbacks, function(callback) { 10351 callback(true, {node: node, selector: selector, parents: parents}); 10352 }); 10353 10354 currentSelectors[selector] = callbacks; 10355 } 10356 10357 matchedSelectors[selector] = callbacks; 10358 return false; 10359 } 10360 }); 10361 }); 10362 10363 // Check if current selectors still match 10364 each(currentSelectors, function(callbacks, selector) { 10365 if (!matchedSelectors[selector]) { 10366 delete currentSelectors[selector]; 10367 10368 each(callbacks, function(callback) { 10369 callback(false, {node: node, selector: selector, parents: parents}); 10370 }); 10371 } 10372 }); 10373 }); 10374 } 10375 10376 // Add selector listeners 10377 if (!self.selectorChangedData[selector]) { 10378 self.selectorChangedData[selector] = []; 10379 } 10380 10381 self.selectorChangedData[selector].push(callback); 10382 10383 return self; 10384 }, 10385 10386 scrollIntoView: function(elm) { 10387 var y, viewPort, self = this, dom = self.dom; 10388 10389 viewPort = dom.getViewPort(self.editor.getWin()); 10390 y = dom.getPos(elm).y; 10391 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 10392 self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); 10393 } 10394 }, 10395 10396 destroy : function(manual) { 10397 var self = this; 10398 10399 self.win = null; 10400 10401 // Manual destroy then remove unload handler 10402 if (!manual) 10403 tinymce.removeUnload(self.destroy); 10404 }, 10405 10406 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10407 _fixIESelection : function() { 10408 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10409 10410 // Return range from point or null if it failed 10411 function rngFromPoint(x, y) { 10412 var rng = body.createTextRange(); 10413 10414 try { 10415 rng.moveToPoint(x, y); 10416 } catch (ex) { 10417 // IE sometimes throws and exception, so lets just ignore it 10418 rng = null; 10419 } 10420 10421 return rng; 10422 }; 10423 10424 // Fires while the selection is changing 10425 function selectionChange(e) { 10426 var pointRng; 10427 10428 // Check if the button is down or not 10429 if (e.button) { 10430 // Create range from mouse position 10431 pointRng = rngFromPoint(e.x, e.y); 10432 10433 if (pointRng) { 10434 // Check if pointRange is before/after selection then change the endPoint 10435 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10436 pointRng.setEndPoint('StartToStart', startRng); 10437 else 10438 pointRng.setEndPoint('EndToEnd', startRng); 10439 10440 pointRng.select(); 10441 } 10442 } else 10443 endSelection(); 10444 } 10445 10446 // Removes listeners 10447 function endSelection() { 10448 var rng = doc.selection.createRange(); 10449 10450 // If the range is collapsed then use the last start range 10451 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10452 startRng.select(); 10453 10454 dom.unbind(doc, 'mouseup', endSelection); 10455 dom.unbind(doc, 'mousemove', selectionChange); 10456 startRng = started = 0; 10457 }; 10458 10459 // Make HTML element unselectable since we are going to handle selection by hand 10460 doc.documentElement.unselectable = true; 10461 10462 // Detect when user selects outside BODY 10463 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10464 if (e.target.nodeName === 'HTML') { 10465 if (started) 10466 endSelection(); 10467 10468 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10469 htmlElm = doc.documentElement; 10470 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10471 return; 10472 10473 started = 1; 10474 // Setup start position 10475 startRng = rngFromPoint(e.x, e.y); 10476 if (startRng) { 10477 // Listen for selection change events 10478 dom.bind(doc, 'mouseup', endSelection); 10479 dom.bind(doc, 'mousemove', selectionChange); 10480 10481 dom.win.focus(); 10482 startRng.select(); 10483 } 10484 } 10485 }); 10486 } 10487 }); 10488 })(tinymce); 10489 (function(tinymce) { 10490 tinymce.dom.Serializer = function(settings, dom, schema) { 10491 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10492 10493 // Support the old apply_source_formatting option 10494 if (!settings.apply_source_formatting) 10495 settings.indent = false; 10496 10497 // Default DOM and Schema if they are undefined 10498 dom = dom || tinymce.DOM; 10499 schema = schema || new tinymce.html.Schema(settings); 10500 settings.entity_encoding = settings.entity_encoding || 'named'; 10501 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10502 10503 onPreProcess = new tinymce.util.Dispatcher(self); 10504 10505 onPostProcess = new tinymce.util.Dispatcher(self); 10506 10507 htmlParser = new tinymce.html.DomParser(settings, schema); 10508 10509 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10510 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10511 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10512 10513 while (i--) { 10514 node = nodes[i]; 10515 10516 value = node.attributes.map[internalName]; 10517 if (value !== undef) { 10518 // Set external name to internal value and remove internal 10519 node.attr(name, value.length > 0 ? value : null); 10520 node.attr(internalName, null); 10521 } else { 10522 // No internal attribute found then convert the value we have in the DOM 10523 value = node.attributes.map[name]; 10524 10525 if (name === "style") 10526 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10527 else if (urlConverter) 10528 value = urlConverter.call(urlConverterScope, value, name, node.name); 10529 10530 node.attr(name, value.length > 0 ? value : null); 10531 } 10532 } 10533 }); 10534 10535 // Remove internal classes mceItem<..> or mceSelected 10536 htmlParser.addAttributeFilter('class', function(nodes, name) { 10537 var i = nodes.length, node, value; 10538 10539 while (i--) { 10540 node = nodes[i]; 10541 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10542 node.attr('class', value.length > 0 ? value : null); 10543 } 10544 }); 10545 10546 // Remove bookmark elements 10547 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10548 var i = nodes.length, node; 10549 10550 while (i--) { 10551 node = nodes[i]; 10552 10553 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10554 node.remove(); 10555 } 10556 }); 10557 10558 // Remove expando attributes 10559 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10560 var i = nodes.length; 10561 10562 while (i--) { 10563 nodes[i].attr(name, null); 10564 } 10565 }); 10566 10567 htmlParser.addNodeFilter('noscript', function(nodes) { 10568 var i = nodes.length, node; 10569 10570 while (i--) { 10571 node = nodes[i].firstChild; 10572 10573 if (node) { 10574 node.value = tinymce.html.Entities.decode(node.value); 10575 } 10576 } 10577 }); 10578 10579 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10580 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10581 var i = nodes.length, node, value; 10582 10583 function trim(value) { 10584 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10585 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10586 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10587 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10588 }; 10589 10590 while (i--) { 10591 node = nodes[i]; 10592 value = node.firstChild ? node.firstChild.value : ''; 10593 10594 if (name === "script") { 10595 // Remove mce- prefix from script elements 10596 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10597 10598 if (value.length > 0) 10599 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10600 } else { 10601 if (value.length > 0) 10602 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10603 } 10604 } 10605 }); 10606 10607 // Convert comments to cdata and handle protected comments 10608 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10609 var i = nodes.length, node; 10610 10611 while (i--) { 10612 node = nodes[i]; 10613 10614 if (node.value.indexOf('[CDATA[') === 0) { 10615 node.name = '#cdata'; 10616 node.type = 4; 10617 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10618 } else if (node.value.indexOf('mce:protected ') === 0) { 10619 node.name = "#text"; 10620 node.type = 3; 10621 node.raw = true; 10622 node.value = unescape(node.value).substr(14); 10623 } 10624 } 10625 }); 10626 10627 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10628 var i = nodes.length, node; 10629 10630 while (i--) { 10631 node = nodes[i]; 10632 if (node.type === 7) 10633 node.remove(); 10634 else if (node.type === 1) { 10635 if (name === "input" && !("type" in node.attributes.map)) 10636 node.attr('type', 'text'); 10637 } 10638 } 10639 }); 10640 10641 // Fix list elements, TODO: Replace this later 10642 if (settings.fix_list_elements) { 10643 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10644 var i = nodes.length, node, parentNode; 10645 10646 while (i--) { 10647 node = nodes[i]; 10648 parentNode = node.parent; 10649 10650 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10651 if (node.prev && node.prev.name === 'li') { 10652 node.prev.append(node); 10653 } 10654 } 10655 } 10656 }); 10657 } 10658 10659 // Remove internal data attributes 10660 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10661 var i = nodes.length; 10662 10663 while (i--) { 10664 nodes[i].attr(name, null); 10665 } 10666 }); 10667 10668 // Return public methods 10669 return { 10670 schema : schema, 10671 10672 addNodeFilter : htmlParser.addNodeFilter, 10673 10674 addAttributeFilter : htmlParser.addAttributeFilter, 10675 10676 onPreProcess : onPreProcess, 10677 10678 onPostProcess : onPostProcess, 10679 10680 serialize : function(node, args) { 10681 var impl, doc, oldDoc, htmlSerializer, content; 10682 10683 // Explorer won't clone contents of script and style and the 10684 // selected index of select elements are cleared on a clone operation. 10685 if (isIE && dom.select('script,style,select,map').length > 0) { 10686 content = node.innerHTML; 10687 node = node.cloneNode(false); 10688 dom.setHTML(node, content); 10689 } else 10690 node = node.cloneNode(true); 10691 10692 // Nodes needs to be attached to something in WebKit/Opera 10693 // Older builds of Opera crashes if you attach the node to an document created dynamically 10694 // and since we can't feature detect a crash we need to sniff the acutal build number 10695 // This fix will make DOM ranges and make Sizzle happy! 10696 impl = node.ownerDocument.implementation; 10697 if (impl.createHTMLDocument) { 10698 // Create an empty HTML document 10699 doc = impl.createHTMLDocument(""); 10700 10701 // Add the element or it's children if it's a body element to the new document 10702 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10703 doc.body.appendChild(doc.importNode(node, true)); 10704 }); 10705 10706 // Grab first child or body element for serialization 10707 if (node.nodeName != 'BODY') 10708 node = doc.body.firstChild; 10709 else 10710 node = doc.body; 10711 10712 // set the new document in DOMUtils so createElement etc works 10713 oldDoc = dom.doc; 10714 dom.doc = doc; 10715 } 10716 10717 args = args || {}; 10718 args.format = args.format || 'html'; 10719 10720 // Pre process 10721 if (!args.no_events) { 10722 args.node = node; 10723 onPreProcess.dispatch(self, args); 10724 } 10725 10726 // Setup serializer 10727 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10728 10729 // Parse and serialize HTML 10730 args.content = htmlSerializer.serialize( 10731 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10732 ); 10733 10734 // Replace all BOM characters for now until we can find a better solution 10735 if (!args.cleanup) 10736 args.content = args.content.replace(/\uFEFF/g, ''); 10737 10738 // Post process 10739 if (!args.no_events) 10740 onPostProcess.dispatch(self, args); 10741 10742 // Restore the old document if it was changed 10743 if (oldDoc) 10744 dom.doc = oldDoc; 10745 10746 args.node = null; 10747 10748 return args.content; 10749 }, 10750 10751 addRules : function(rules) { 10752 schema.addValidElements(rules); 10753 }, 10754 10755 setRules : function(rules) { 10756 schema.setValidElements(rules); 10757 } 10758 }; 10759 }; 10760 })(tinymce); 10761 (function(tinymce) { 10762 tinymce.dom.ScriptLoader = function(settings) { 10763 var QUEUED = 0, 10764 LOADING = 1, 10765 LOADED = 2, 10766 states = {}, 10767 queue = [], 10768 scriptLoadedCallbacks = {}, 10769 queueLoadedCallbacks = [], 10770 loading = 0, 10771 undef; 10772 10773 function loadScript(url, callback) { 10774 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10775 10776 // Execute callback when script is loaded 10777 function done() { 10778 dom.remove(id); 10779 10780 if (elm) 10781 elm.onreadystatechange = elm.onload = elm = null; 10782 10783 callback(); 10784 }; 10785 10786 function error() { 10787 // Report the error so it's easier for people to spot loading errors 10788 if (typeof(console) !== "undefined" && console.log) 10789 console.log("Failed to load: " + url); 10790 10791 // We can't mark it as done if there is a load error since 10792 // A) We don't want to produce 404 errors on the server and 10793 // B) the onerror event won't fire on all browsers. 10794 // done(); 10795 }; 10796 10797 id = dom.uniqueId(); 10798 10799 if (tinymce.isIE6) { 10800 uri = new tinymce.util.URI(url); 10801 loc = location; 10802 10803 // If script is from same domain and we 10804 // use IE 6 then use XHR since it's more reliable 10805 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10806 tinymce.util.XHR.send({ 10807 url : tinymce._addVer(uri.getURI()), 10808 success : function(content) { 10809 // Create new temp script element 10810 var script = dom.create('script', { 10811 type : 'text/javascript' 10812 }); 10813 10814 // Evaluate script in global scope 10815 script.text = content; 10816 document.getElementsByTagName('head')[0].appendChild(script); 10817 dom.remove(script); 10818 10819 done(); 10820 }, 10821 10822 error : error 10823 }); 10824 10825 return; 10826 } 10827 } 10828 10829 // Create new script element 10830 elm = document.createElement('script'); 10831 elm.id = id; 10832 elm.type = 'text/javascript'; 10833 elm.src = tinymce._addVer(url); 10834 10835 // Add onload listener for non IE browsers since IE9 10836 // fires onload event before the script is parsed and executed 10837 if (!tinymce.isIE || tinymce.isIE11) 10838 elm.onload = done; 10839 10840 // Add onerror event will get fired on some browsers but not all of them 10841 elm.onerror = error; 10842 10843 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10844 if (!tinymce.isOpera) { 10845 elm.onreadystatechange = function() { 10846 var state = elm.readyState; 10847 10848 // Loaded state is passed on IE 6 however there 10849 // are known issues with this method but we can't use 10850 // XHR in a cross domain loading 10851 if (state == 'complete' || state == 'loaded') 10852 done(); 10853 }; 10854 } 10855 10856 // Most browsers support this feature so we report errors 10857 // for those at least to help users track their missing plugins etc 10858 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10859 /*elm.onerror = function() { 10860 alert('Failed to load: ' + url); 10861 };*/ 10862 10863 // Add script to document 10864 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10865 }; 10866 10867 this.isDone = function(url) { 10868 return states[url] == LOADED; 10869 }; 10870 10871 this.markDone = function(url) { 10872 states[url] = LOADED; 10873 }; 10874 10875 this.add = this.load = function(url, callback, scope) { 10876 var item, state = states[url]; 10877 10878 // Add url to load queue 10879 if (state == undef) { 10880 queue.push(url); 10881 states[url] = QUEUED; 10882 } 10883 10884 if (callback) { 10885 // Store away callback for later execution 10886 if (!scriptLoadedCallbacks[url]) 10887 scriptLoadedCallbacks[url] = []; 10888 10889 scriptLoadedCallbacks[url].push({ 10890 func : callback, 10891 scope : scope || this 10892 }); 10893 } 10894 }; 10895 10896 this.loadQueue = function(callback, scope) { 10897 this.loadScripts(queue, callback, scope); 10898 }; 10899 10900 this.loadScripts = function(scripts, callback, scope) { 10901 var loadScripts; 10902 10903 function execScriptLoadedCallbacks(url) { 10904 // Execute URL callback functions 10905 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10906 callback.func.call(callback.scope); 10907 }); 10908 10909 scriptLoadedCallbacks[url] = undef; 10910 }; 10911 10912 queueLoadedCallbacks.push({ 10913 func : callback, 10914 scope : scope || this 10915 }); 10916 10917 loadScripts = function() { 10918 var loadingScripts = tinymce.grep(scripts); 10919 10920 // Current scripts has been handled 10921 scripts.length = 0; 10922 10923 // Load scripts that needs to be loaded 10924 tinymce.each(loadingScripts, function(url) { 10925 // Script is already loaded then execute script callbacks directly 10926 if (states[url] == LOADED) { 10927 execScriptLoadedCallbacks(url); 10928 return; 10929 } 10930 10931 // Is script not loading then start loading it 10932 if (states[url] != LOADING) { 10933 states[url] = LOADING; 10934 loading++; 10935 10936 loadScript(url, function() { 10937 states[url] = LOADED; 10938 loading--; 10939 10940 execScriptLoadedCallbacks(url); 10941 10942 // Load more scripts if they where added by the recently loaded script 10943 loadScripts(); 10944 }); 10945 } 10946 }); 10947 10948 // No scripts are currently loading then execute all pending queue loaded callbacks 10949 if (!loading) { 10950 tinymce.each(queueLoadedCallbacks, function(callback) { 10951 callback.func.call(callback.scope); 10952 }); 10953 10954 queueLoadedCallbacks.length = 0; 10955 } 10956 }; 10957 10958 loadScripts(); 10959 }; 10960 }; 10961 10962 // Global script loader 10963 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10964 })(tinymce); 10965 (function(tinymce) { 10966 tinymce.dom.RangeUtils = function(dom) { 10967 var INVISIBLE_CHAR = '\uFEFF'; 10968 10969 this.walk = function(rng, callback) { 10970 var startContainer = rng.startContainer, 10971 startOffset = rng.startOffset, 10972 endContainer = rng.endContainer, 10973 endOffset = rng.endOffset, 10974 ancestor, startPoint, 10975 endPoint, node, parent, siblings, nodes; 10976 10977 // Handle table cell selection the table plugin enables 10978 // you to fake select table cells and perform formatting actions on them 10979 nodes = dom.select('td.mceSelected,th.mceSelected'); 10980 if (nodes.length > 0) { 10981 tinymce.each(nodes, function(node) { 10982 callback([node]); 10983 }); 10984 10985 return; 10986 } 10987 10988 function exclude(nodes) { 10989 var node; 10990 10991 // First node is excluded 10992 node = nodes[0]; 10993 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10994 nodes.splice(0, 1); 10995 } 10996 10997 // Last node is excluded 10998 node = nodes[nodes.length - 1]; 10999 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 11000 nodes.splice(nodes.length - 1, 1); 11001 } 11002 11003 return nodes; 11004 }; 11005 11006 function collectSiblings(node, name, end_node) { 11007 var siblings = []; 11008 11009 for (; node && node != end_node; node = node[name]) 11010 siblings.push(node); 11011 11012 return siblings; 11013 }; 11014 11015 function findEndPoint(node, root) { 11016 do { 11017 if (node.parentNode == root) 11018 return node; 11019 11020 node = node.parentNode; 11021 } while(node); 11022 }; 11023 11024 function walkBoundary(start_node, end_node, next) { 11025 var siblingName = next ? 'nextSibling' : 'previousSibling'; 11026 11027 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 11028 parent = node.parentNode; 11029 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 11030 11031 if (siblings.length) { 11032 if (!next) 11033 siblings.reverse(); 11034 11035 callback(exclude(siblings)); 11036 } 11037 } 11038 }; 11039 11040 // If index based start position then resolve it 11041 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 11042 startContainer = startContainer.childNodes[startOffset]; 11043 11044 // If index based end position then resolve it 11045 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 11046 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 11047 11048 // Same container 11049 if (startContainer == endContainer) 11050 return callback(exclude([startContainer])); 11051 11052 // Find common ancestor and end points 11053 ancestor = dom.findCommonAncestor(startContainer, endContainer); 11054 11055 // Process left side 11056 for (node = startContainer; node; node = node.parentNode) { 11057 if (node === endContainer) 11058 return walkBoundary(startContainer, ancestor, true); 11059 11060 if (node === ancestor) 11061 break; 11062 } 11063 11064 // Process right side 11065 for (node = endContainer; node; node = node.parentNode) { 11066 if (node === startContainer) 11067 return walkBoundary(endContainer, ancestor); 11068 11069 if (node === ancestor) 11070 break; 11071 } 11072 11073 // Find start/end point 11074 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 11075 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 11076 11077 // Walk left leaf 11078 walkBoundary(startContainer, startPoint, true); 11079 11080 // Walk the middle from start to end point 11081 siblings = collectSiblings( 11082 startPoint == startContainer ? startPoint : startPoint.nextSibling, 11083 'nextSibling', 11084 endPoint == endContainer ? endPoint.nextSibling : endPoint 11085 ); 11086 11087 if (siblings.length) 11088 callback(exclude(siblings)); 11089 11090 // Walk right leaf 11091 walkBoundary(endContainer, endPoint); 11092 }; 11093 11094 this.split = function(rng) { 11095 var startContainer = rng.startContainer, 11096 startOffset = rng.startOffset, 11097 endContainer = rng.endContainer, 11098 endOffset = rng.endOffset; 11099 11100 function splitText(node, offset) { 11101 return node.splitText(offset); 11102 }; 11103 11104 // Handle single text node 11105 if (startContainer == endContainer && startContainer.nodeType == 3) { 11106 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11107 endContainer = splitText(startContainer, startOffset); 11108 startContainer = endContainer.previousSibling; 11109 11110 if (endOffset > startOffset) { 11111 endOffset = endOffset - startOffset; 11112 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 11113 endOffset = endContainer.nodeValue.length; 11114 startOffset = 0; 11115 } else { 11116 endOffset = 0; 11117 } 11118 } 11119 } else { 11120 // Split startContainer text node if needed 11121 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11122 startContainer = splitText(startContainer, startOffset); 11123 startOffset = 0; 11124 } 11125 11126 // Split endContainer text node if needed 11127 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 11128 endContainer = splitText(endContainer, endOffset).previousSibling; 11129 endOffset = endContainer.nodeValue.length; 11130 } 11131 } 11132 11133 return { 11134 startContainer : startContainer, 11135 startOffset : startOffset, 11136 endContainer : endContainer, 11137 endOffset : endOffset 11138 }; 11139 }; 11140 11141 }; 11142 11143 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 11144 if (rng1 && rng2) { 11145 // Compare native IE ranges 11146 if (rng1.item || rng1.duplicate) { 11147 // Both are control ranges and the selected element matches 11148 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 11149 return true; 11150 11151 // Both are text ranges and the range matches 11152 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 11153 return true; 11154 } else { 11155 // Compare w3c ranges 11156 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 11157 } 11158 } 11159 11160 return false; 11161 }; 11162 })(tinymce); 11163 (function(tinymce) { 11164 var Event = tinymce.dom.Event, each = tinymce.each; 11165 11166 tinymce.create('tinymce.ui.KeyboardNavigation', { 11167 KeyboardNavigation: function(settings, dom) { 11168 var t = this, root = settings.root, items = settings.items, 11169 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 11170 excludeFromTabOrder = settings.excludeFromTabOrder, 11171 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 11172 11173 dom = dom || tinymce.DOM; 11174 11175 itemFocussed = function(evt) { 11176 focussedId = evt.target.id; 11177 }; 11178 11179 itemBlurred = function(evt) { 11180 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 11181 }; 11182 11183 rootFocussed = function(evt) { 11184 var item = dom.get(focussedId); 11185 dom.setAttrib(item, 'tabindex', '0'); 11186 item.focus(); 11187 }; 11188 11189 t.focus = function() { 11190 dom.get(focussedId).focus(); 11191 }; 11192 11193 t.destroy = function() { 11194 each(items, function(item) { 11195 var elm = dom.get(item.id); 11196 11197 dom.unbind(elm, 'focus', itemFocussed); 11198 dom.unbind(elm, 'blur', itemBlurred); 11199 }); 11200 11201 var rootElm = dom.get(root); 11202 dom.unbind(rootElm, 'focus', rootFocussed); 11203 dom.unbind(rootElm, 'keydown', rootKeydown); 11204 11205 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 11206 t.destroy = function() {}; 11207 }; 11208 11209 t.moveFocus = function(dir, evt) { 11210 var idx = -1, controls = t.controls, newFocus; 11211 11212 if (!focussedId) 11213 return; 11214 11215 each(items, function(item, index) { 11216 if (item.id === focussedId) { 11217 idx = index; 11218 return false; 11219 } 11220 }); 11221 11222 idx += dir; 11223 if (idx < 0) { 11224 idx = items.length - 1; 11225 } else if (idx >= items.length) { 11226 idx = 0; 11227 } 11228 11229 newFocus = items[idx]; 11230 dom.setAttrib(focussedId, 'tabindex', '-1'); 11231 dom.setAttrib(newFocus.id, 'tabindex', '0'); 11232 dom.get(newFocus.id).focus(); 11233 11234 if (settings.actOnFocus) { 11235 settings.onAction(newFocus.id); 11236 } 11237 11238 if (evt) 11239 Event.cancel(evt); 11240 }; 11241 11242 rootKeydown = function(evt) { 11243 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; 11244 11245 switch (evt.keyCode) { 11246 case DOM_VK_LEFT: 11247 if (enableLeftRight) t.moveFocus(-1); 11248 Event.cancel(evt); 11249 break; 11250 11251 case DOM_VK_RIGHT: 11252 if (enableLeftRight) t.moveFocus(1); 11253 Event.cancel(evt); 11254 break; 11255 11256 case DOM_VK_UP: 11257 if (enableUpDown) t.moveFocus(-1); 11258 Event.cancel(evt); 11259 break; 11260 11261 case DOM_VK_DOWN: 11262 if (enableUpDown) t.moveFocus(1); 11263 Event.cancel(evt); 11264 break; 11265 11266 case DOM_VK_ESCAPE: 11267 if (settings.onCancel) { 11268 settings.onCancel(); 11269 Event.cancel(evt); 11270 } 11271 break; 11272 11273 case DOM_VK_ENTER: 11274 case DOM_VK_RETURN: 11275 case DOM_VK_SPACE: 11276 if (settings.onAction) { 11277 settings.onAction(focussedId); 11278 Event.cancel(evt); 11279 } 11280 break; 11281 } 11282 }; 11283 11284 // Set up state and listeners for each item. 11285 each(items, function(item, idx) { 11286 var tabindex, elm; 11287 11288 if (!item.id) { 11289 item.id = dom.uniqueId('_mce_item_'); 11290 } 11291 11292 elm = dom.get(item.id); 11293 11294 if (excludeFromTabOrder) { 11295 dom.bind(elm, 'blur', itemBlurred); 11296 tabindex = '-1'; 11297 } else { 11298 tabindex = (idx === 0 ? '0' : '-1'); 11299 } 11300 11301 elm.setAttribute('tabindex', tabindex); 11302 dom.bind(elm, 'focus', itemFocussed); 11303 }); 11304 11305 // Setup initial state for root element. 11306 if (items[0]){ 11307 focussedId = items[0].id; 11308 } 11309 11310 dom.setAttrib(root, 'tabindex', '-1'); 11311 11312 // Setup listeners for root element. 11313 var rootElm = dom.get(root); 11314 dom.bind(rootElm, 'focus', rootFocussed); 11315 dom.bind(rootElm, 'keydown', rootKeydown); 11316 } 11317 }); 11318 })(tinymce); 11319 (function(tinymce) { 11320 // Shorten class names 11321 var DOM = tinymce.DOM, is = tinymce.is; 11322 11323 tinymce.create('tinymce.ui.Control', { 11324 Control : function(id, s, editor) { 11325 this.id = id; 11326 this.settings = s = s || {}; 11327 this.rendered = false; 11328 this.onRender = new tinymce.util.Dispatcher(this); 11329 this.classPrefix = ''; 11330 this.scope = s.scope || this; 11331 this.disabled = 0; 11332 this.active = 0; 11333 this.editor = editor; 11334 }, 11335 11336 setAriaProperty : function(property, value) { 11337 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11338 if (element) { 11339 DOM.setAttrib(element, 'aria-' + property, !!value); 11340 } 11341 }, 11342 11343 focus : function() { 11344 DOM.get(this.id).focus(); 11345 }, 11346 11347 setDisabled : function(s) { 11348 if (s != this.disabled) { 11349 this.setAriaProperty('disabled', s); 11350 11351 this.setState('Disabled', s); 11352 this.setState('Enabled', !s); 11353 this.disabled = s; 11354 } 11355 }, 11356 11357 isDisabled : function() { 11358 return this.disabled; 11359 }, 11360 11361 setActive : function(s) { 11362 if (s != this.active) { 11363 this.setState('Active', s); 11364 this.active = s; 11365 this.setAriaProperty('pressed', s); 11366 } 11367 }, 11368 11369 isActive : function() { 11370 return this.active; 11371 }, 11372 11373 setState : function(c, s) { 11374 var n = DOM.get(this.id); 11375 11376 c = this.classPrefix + c; 11377 11378 if (s) 11379 DOM.addClass(n, c); 11380 else 11381 DOM.removeClass(n, c); 11382 }, 11383 11384 isRendered : function() { 11385 return this.rendered; 11386 }, 11387 11388 renderHTML : function() { 11389 }, 11390 11391 renderTo : function(n) { 11392 DOM.setHTML(n, this.renderHTML()); 11393 }, 11394 11395 postRender : function() { 11396 var t = this, b; 11397 11398 // Set pending states 11399 if (is(t.disabled)) { 11400 b = t.disabled; 11401 t.disabled = -1; 11402 t.setDisabled(b); 11403 } 11404 11405 if (is(t.active)) { 11406 b = t.active; 11407 t.active = -1; 11408 t.setActive(b); 11409 } 11410 }, 11411 11412 remove : function() { 11413 DOM.remove(this.id); 11414 this.destroy(); 11415 }, 11416 11417 destroy : function() { 11418 tinymce.dom.Event.clear(this.id); 11419 } 11420 }); 11421 })(tinymce); 11422 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11423 Container : function(id, s, editor) { 11424 this.parent(id, s, editor); 11425 11426 this.controls = []; 11427 11428 this.lookup = {}; 11429 }, 11430 11431 add : function(c) { 11432 this.lookup[c.id] = c; 11433 this.controls.push(c); 11434 11435 return c; 11436 }, 11437 11438 get : function(n) { 11439 return this.lookup[n]; 11440 } 11441 }); 11442 11443 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11444 Separator : function(id, s) { 11445 this.parent(id, s); 11446 this.classPrefix = 'mceSeparator'; 11447 this.setDisabled(true); 11448 }, 11449 11450 renderHTML : function() { 11451 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11452 } 11453 }); 11454 (function(tinymce) { 11455 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11456 11457 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11458 MenuItem : function(id, s) { 11459 this.parent(id, s); 11460 this.classPrefix = 'mceMenuItem'; 11461 }, 11462 11463 setSelected : function(s) { 11464 this.setState('Selected', s); 11465 this.setAriaProperty('checked', !!s); 11466 this.selected = s; 11467 }, 11468 11469 isSelected : function() { 11470 return this.selected; 11471 }, 11472 11473 postRender : function() { 11474 var t = this; 11475 11476 t.parent(); 11477 11478 // Set pending state 11479 if (is(t.selected)) 11480 t.setSelected(t.selected); 11481 } 11482 }); 11483 })(tinymce); 11484 (function(tinymce) { 11485 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11486 11487 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11488 Menu : function(id, s) { 11489 var t = this; 11490 11491 t.parent(id, s); 11492 t.items = {}; 11493 t.collapsed = false; 11494 t.menuCount = 0; 11495 t.onAddItem = new tinymce.util.Dispatcher(this); 11496 }, 11497 11498 expand : function(d) { 11499 var t = this; 11500 11501 if (d) { 11502 walk(t, function(o) { 11503 if (o.expand) 11504 o.expand(); 11505 }, 'items', t); 11506 } 11507 11508 t.collapsed = false; 11509 }, 11510 11511 collapse : function(d) { 11512 var t = this; 11513 11514 if (d) { 11515 walk(t, function(o) { 11516 if (o.collapse) 11517 o.collapse(); 11518 }, 'items', t); 11519 } 11520 11521 t.collapsed = true; 11522 }, 11523 11524 isCollapsed : function() { 11525 return this.collapsed; 11526 }, 11527 11528 add : function(o) { 11529 if (!o.settings) 11530 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11531 11532 this.onAddItem.dispatch(this, o); 11533 11534 return this.items[o.id] = o; 11535 }, 11536 11537 addSeparator : function() { 11538 return this.add({separator : true}); 11539 }, 11540 11541 addMenu : function(o) { 11542 if (!o.collapse) 11543 o = this.createMenu(o); 11544 11545 this.menuCount++; 11546 11547 return this.add(o); 11548 }, 11549 11550 hasMenus : function() { 11551 return this.menuCount !== 0; 11552 }, 11553 11554 remove : function(o) { 11555 delete this.items[o.id]; 11556 }, 11557 11558 removeAll : function() { 11559 var t = this; 11560 11561 walk(t, function(o) { 11562 if (o.removeAll) 11563 o.removeAll(); 11564 else 11565 o.remove(); 11566 11567 o.destroy(); 11568 }, 'items', t); 11569 11570 t.items = {}; 11571 }, 11572 11573 createMenu : function(o) { 11574 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11575 11576 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11577 11578 return m; 11579 } 11580 }); 11581 })(tinymce); 11582 (function(tinymce) { 11583 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11584 11585 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11586 DropMenu : function(id, s) { 11587 s = s || {}; 11588 s.container = s.container || DOM.doc.body; 11589 s.offset_x = s.offset_x || 0; 11590 s.offset_y = s.offset_y || 0; 11591 s.vp_offset_x = s.vp_offset_x || 0; 11592 s.vp_offset_y = s.vp_offset_y || 0; 11593 11594 if (is(s.icons) && !s.icons) 11595 s['class'] += ' mceNoIcons'; 11596 11597 this.parent(id, s); 11598 this.onShowMenu = new tinymce.util.Dispatcher(this); 11599 this.onHideMenu = new tinymce.util.Dispatcher(this); 11600 this.classPrefix = 'mceMenu'; 11601 }, 11602 11603 createMenu : function(s) { 11604 var t = this, cs = t.settings, m; 11605 11606 s.container = s.container || cs.container; 11607 s.parent = t; 11608 s.constrain = s.constrain || cs.constrain; 11609 s['class'] = s['class'] || cs['class']; 11610 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11611 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11612 s.keyboard_focus = cs.keyboard_focus; 11613 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11614 11615 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11616 11617 return m; 11618 }, 11619 11620 focus : function() { 11621 var t = this; 11622 if (t.keyboardNav) { 11623 t.keyboardNav.focus(); 11624 } 11625 }, 11626 11627 update : function() { 11628 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11629 11630 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11631 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11632 11633 if (!DOM.boxModel) 11634 t.element.setStyles({width : tw + 2, height : th + 2}); 11635 else 11636 t.element.setStyles({width : tw, height : th}); 11637 11638 if (s.max_width) 11639 DOM.setStyle(co, 'width', tw); 11640 11641 if (s.max_height) { 11642 DOM.setStyle(co, 'height', th); 11643 11644 if (tb.clientHeight < s.max_height) 11645 DOM.setStyle(co, 'overflow', 'hidden'); 11646 } 11647 }, 11648 11649 showMenu : function(x, y, px) { 11650 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11651 11652 t.collapse(1); 11653 11654 if (t.isMenuVisible) 11655 return; 11656 11657 if (!t.rendered) { 11658 co = DOM.add(t.settings.container, t.renderNode()); 11659 11660 each(t.items, function(o) { 11661 o.postRender(); 11662 }); 11663 11664 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11665 } else 11666 co = DOM.get('menu_' + t.id); 11667 11668 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11669 if (!tinymce.isOpera) 11670 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11671 11672 DOM.show(co); 11673 t.update(); 11674 11675 x += s.offset_x || 0; 11676 y += s.offset_y || 0; 11677 vp.w -= 4; 11678 vp.h -= 4; 11679 11680 // Move inside viewport if not submenu 11681 if (s.constrain) { 11682 w = co.clientWidth - ot; 11683 h = co.clientHeight - ot; 11684 mx = vp.x + vp.w; 11685 my = vp.y + vp.h; 11686 11687 if ((x + s.vp_offset_x + w) > mx) 11688 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11689 11690 if ((y + s.vp_offset_y + h) > my) 11691 y = Math.max(0, (my - s.vp_offset_y) - h); 11692 } 11693 11694 DOM.setStyles(co, {left : x , top : y}); 11695 t.element.update(); 11696 11697 t.isMenuVisible = 1; 11698 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11699 var m; 11700 11701 e = e.target; 11702 11703 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11704 m = t.items[e.id]; 11705 11706 if (m.isDisabled()) 11707 return; 11708 11709 dm = t; 11710 11711 while (dm) { 11712 if (dm.hideMenu) 11713 dm.hideMenu(); 11714 11715 dm = dm.settings.parent; 11716 } 11717 11718 if (m.settings.onclick) 11719 m.settings.onclick(e); 11720 11721 return false; // Cancel to fix onbeforeunload problem 11722 } 11723 }); 11724 11725 if (t.hasMenus()) { 11726 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11727 var m, r, mi; 11728 11729 e = e.target; 11730 if (e && (e = DOM.getParent(e, 'tr'))) { 11731 m = t.items[e.id]; 11732 11733 if (t.lastMenu) 11734 t.lastMenu.collapse(1); 11735 11736 if (m.isDisabled()) 11737 return; 11738 11739 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11740 //p = DOM.getPos(s.container); 11741 r = DOM.getRect(e); 11742 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11743 t.lastMenu = m; 11744 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11745 } 11746 } 11747 }); 11748 } 11749 11750 Event.add(co, 'keydown', t._keyHandler, t); 11751 11752 t.onShowMenu.dispatch(t); 11753 11754 if (s.keyboard_focus) { 11755 t._setupKeyboardNav(); 11756 } 11757 }, 11758 11759 hideMenu : function(c) { 11760 var t = this, co = DOM.get('menu_' + t.id), e; 11761 11762 if (!t.isMenuVisible) 11763 return; 11764 11765 if (t.keyboardNav) t.keyboardNav.destroy(); 11766 Event.remove(co, 'mouseover', t.mouseOverFunc); 11767 Event.remove(co, 'click', t.mouseClickFunc); 11768 Event.remove(co, 'keydown', t._keyHandler); 11769 DOM.hide(co); 11770 t.isMenuVisible = 0; 11771 11772 if (!c) 11773 t.collapse(1); 11774 11775 if (t.element) 11776 t.element.hide(); 11777 11778 if (e = DOM.get(t.id)) 11779 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11780 11781 t.onHideMenu.dispatch(t); 11782 }, 11783 11784 add : function(o) { 11785 var t = this, co; 11786 11787 o = t.parent(o); 11788 11789 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11790 t._add(DOM.select('tbody', co)[0], o); 11791 11792 return o; 11793 }, 11794 11795 collapse : function(d) { 11796 this.parent(d); 11797 this.hideMenu(1); 11798 }, 11799 11800 remove : function(o) { 11801 DOM.remove(o.id); 11802 this.destroy(); 11803 11804 return this.parent(o); 11805 }, 11806 11807 destroy : function() { 11808 var t = this, co = DOM.get('menu_' + t.id); 11809 11810 if (t.keyboardNav) t.keyboardNav.destroy(); 11811 Event.remove(co, 'mouseover', t.mouseOverFunc); 11812 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11813 Event.remove(co, 'click', t.mouseClickFunc); 11814 Event.remove(co, 'keydown', t._keyHandler); 11815 11816 if (t.element) 11817 t.element.remove(); 11818 11819 DOM.remove(co); 11820 }, 11821 11822 renderNode : function() { 11823 var t = this, s = t.settings, n, tb, co, w; 11824 11825 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'}); 11826 if (t.settings.parent) { 11827 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11828 } 11829 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11830 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11831 11832 if (s.menu_line) 11833 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11834 11835 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11836 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11837 tb = DOM.add(n, 'tbody'); 11838 11839 each(t.items, function(o) { 11840 t._add(tb, o); 11841 }); 11842 11843 t.rendered = true; 11844 11845 return w; 11846 }, 11847 11848 // Internal functions 11849 _setupKeyboardNav : function(){ 11850 var contextMenu, menuItems, t=this; 11851 contextMenu = DOM.get('menu_' + t.id); 11852 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11853 menuItems.splice(0,0,contextMenu); 11854 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11855 root: 'menu_' + t.id, 11856 items: menuItems, 11857 onCancel: function() { 11858 t.hideMenu(); 11859 }, 11860 enableUpDown: true 11861 }); 11862 contextMenu.focus(); 11863 }, 11864 11865 _keyHandler : function(evt) { 11866 var t = this, e; 11867 switch (evt.keyCode) { 11868 case 37: // Left 11869 if (t.settings.parent) { 11870 t.hideMenu(); 11871 t.settings.parent.focus(); 11872 Event.cancel(evt); 11873 } 11874 break; 11875 case 39: // Right 11876 if (t.mouseOverFunc) 11877 t.mouseOverFunc(evt); 11878 break; 11879 } 11880 }, 11881 11882 _add : function(tb, o) { 11883 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11884 11885 if (s.separator) { 11886 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11887 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11888 11889 if (n = ro.previousSibling) 11890 DOM.addClass(n, 'mceLast'); 11891 11892 return; 11893 } 11894 11895 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11896 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11897 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11898 11899 if (s.parent) { 11900 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11901 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11902 } 11903 11904 DOM.addClass(it, s['class']); 11905 // n = DOM.add(n, 'span', {'class' : 'item'}); 11906 11907 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11908 11909 if (s.icon_src) 11910 DOM.add(ic, 'img', {src : s.icon_src}); 11911 11912 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11913 11914 if (o.settings.style) { 11915 if (typeof o.settings.style == "function") 11916 o.settings.style = o.settings.style(); 11917 11918 DOM.setAttrib(n, 'style', o.settings.style); 11919 } 11920 11921 if (tb.childNodes.length == 1) 11922 DOM.addClass(ro, 'mceFirst'); 11923 11924 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11925 DOM.addClass(ro, 'mceFirst'); 11926 11927 if (o.collapse) 11928 DOM.addClass(ro, cp + 'ItemSub'); 11929 11930 if (n = ro.previousSibling) 11931 DOM.removeClass(n, 'mceLast'); 11932 11933 DOM.addClass(ro, 'mceLast'); 11934 } 11935 }); 11936 })(tinymce); 11937 (function(tinymce) { 11938 var DOM = tinymce.DOM; 11939 11940 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11941 Button : function(id, s, ed) { 11942 this.parent(id, s, ed); 11943 this.classPrefix = 'mceButton'; 11944 }, 11945 11946 renderHTML : function() { 11947 var cp = this.classPrefix, s = this.settings, h, l; 11948 11949 l = DOM.encode(s.label || ''); 11950 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) + '">'; 11951 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11952 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>' : ''); 11953 else 11954 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11955 11956 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11957 h += '</a>'; 11958 return h; 11959 }, 11960 11961 postRender : function() { 11962 var t = this, s = t.settings, imgBookmark; 11963 11964 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11965 // need to keep the selection in case the selection is lost 11966 if (tinymce.isIE && t.editor) { 11967 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11968 var nodeName = t.editor.selection.getNode().nodeName; 11969 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11970 }); 11971 } 11972 tinymce.dom.Event.add(t.id, 'click', function(e) { 11973 if (!t.isDisabled()) { 11974 // restore the selection in case the selection is lost in IE 11975 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11976 t.editor.selection.moveToBookmark(imgBookmark); 11977 } 11978 return s.onclick.call(s.scope, e); 11979 } 11980 }); 11981 tinymce.dom.Event.add(t.id, 'keydown', function(e) { 11982 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) { 11983 tinymce.dom.Event.cancel(e); 11984 return s.onclick.call(s.scope, e); 11985 } 11986 }); 11987 } 11988 }); 11989 })(tinymce); 11990 (function(tinymce) { 11991 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11992 11993 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11994 ListBox : function(id, s, ed) { 11995 var t = this; 11996 11997 t.parent(id, s, ed); 11998 11999 t.items = []; 12000 12001 t.onChange = new Dispatcher(t); 12002 12003 t.onPostRender = new Dispatcher(t); 12004 12005 t.onAdd = new Dispatcher(t); 12006 12007 t.onRenderMenu = new tinymce.util.Dispatcher(this); 12008 12009 t.classPrefix = 'mceListBox'; 12010 t.marked = {}; 12011 }, 12012 12013 select : function(va) { 12014 var t = this, fv, f; 12015 12016 t.marked = {}; 12017 12018 if (va == undef) 12019 return t.selectByIndex(-1); 12020 12021 // Is string or number make function selector 12022 if (va && typeof(va)=="function") 12023 f = va; 12024 else { 12025 f = function(v) { 12026 return v == va; 12027 }; 12028 } 12029 12030 // Do we need to do something? 12031 if (va != t.selectedValue) { 12032 // Find item 12033 each(t.items, function(o, i) { 12034 if (f(o.value)) { 12035 fv = 1; 12036 t.selectByIndex(i); 12037 return false; 12038 } 12039 }); 12040 12041 if (!fv) 12042 t.selectByIndex(-1); 12043 } 12044 }, 12045 12046 selectByIndex : function(idx) { 12047 var t = this, e, o, label; 12048 12049 t.marked = {}; 12050 12051 if (idx != t.selectedIndex) { 12052 e = DOM.get(t.id + '_text'); 12053 label = DOM.get(t.id + '_voiceDesc'); 12054 o = t.items[idx]; 12055 12056 if (o) { 12057 t.selectedValue = o.value; 12058 t.selectedIndex = idx; 12059 DOM.setHTML(e, DOM.encode(o.title)); 12060 DOM.setHTML(label, t.settings.title + " - " + o.title); 12061 DOM.removeClass(e, 'mceTitle'); 12062 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 12063 } else { 12064 DOM.setHTML(e, DOM.encode(t.settings.title)); 12065 DOM.setHTML(label, DOM.encode(t.settings.title)); 12066 DOM.addClass(e, 'mceTitle'); 12067 t.selectedValue = t.selectedIndex = null; 12068 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 12069 } 12070 e = 0; 12071 } 12072 }, 12073 12074 mark : function(value) { 12075 this.marked[value] = true; 12076 }, 12077 12078 add : function(n, v, o) { 12079 var t = this; 12080 12081 o = o || {}; 12082 o = tinymce.extend(o, { 12083 title : n, 12084 value : v 12085 }); 12086 12087 t.items.push(o); 12088 t.onAdd.dispatch(t, o); 12089 }, 12090 12091 getLength : function() { 12092 return this.items.length; 12093 }, 12094 12095 renderHTML : function() { 12096 var h = '', t = this, s = t.settings, cp = t.classPrefix; 12097 12098 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>'; 12099 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 12100 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>'; 12101 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>'; 12102 h += '</tr></tbody></table></span>'; 12103 12104 return h; 12105 }, 12106 12107 showMenu : function() { 12108 var t = this, p2, e = DOM.get(this.id), m; 12109 12110 if (t.isDisabled() || t.items.length === 0) 12111 return; 12112 12113 if (t.menu && t.menu.isMenuVisible) 12114 return t.hideMenu(); 12115 12116 if (!t.isMenuRendered) { 12117 t.renderMenu(); 12118 t.isMenuRendered = true; 12119 } 12120 12121 p2 = DOM.getPos(e); 12122 12123 m = t.menu; 12124 m.settings.offset_x = p2.x; 12125 m.settings.offset_y = p2.y; 12126 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 12127 12128 // Select in menu 12129 each(t.items, function(o) { 12130 if (m.items[o.id]) { 12131 m.items[o.id].setSelected(0); 12132 } 12133 }); 12134 12135 each(t.items, function(o) { 12136 if (m.items[o.id] && t.marked[o.value]) { 12137 m.items[o.id].setSelected(1); 12138 } 12139 12140 if (o.value === t.selectedValue) { 12141 m.items[o.id].setSelected(1); 12142 } 12143 }); 12144 12145 m.showMenu(0, e.clientHeight); 12146 12147 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12148 DOM.addClass(t.id, t.classPrefix + 'Selected'); 12149 12150 //DOM.get(t.id + '_text').focus(); 12151 }, 12152 12153 hideMenu : function(e) { 12154 var t = this; 12155 12156 if (t.menu && t.menu.isMenuVisible) { 12157 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12158 12159 // Prevent double toogles by canceling the mouse click event to the button 12160 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 12161 return; 12162 12163 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12164 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12165 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12166 t.menu.hideMenu(); 12167 } 12168 } 12169 }, 12170 12171 renderMenu : function() { 12172 var t = this, m; 12173 12174 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12175 menu_line : 1, 12176 'class' : t.classPrefix + 'Menu mceNoIcons', 12177 max_width : 250, 12178 max_height : 150 12179 }); 12180 12181 m.onHideMenu.add(function() { 12182 t.hideMenu(); 12183 t.focus(); 12184 }); 12185 12186 m.add({ 12187 title : t.settings.title, 12188 'class' : 'mceMenuItemTitle', 12189 onclick : function() { 12190 if (t.settings.onselect('') !== false) 12191 t.select(''); // Must be runned after 12192 } 12193 }); 12194 12195 each(t.items, function(o) { 12196 // No value then treat it as a title 12197 if (o.value === undef) { 12198 m.add({ 12199 title : o.title, 12200 role : "option", 12201 'class' : 'mceMenuItemTitle', 12202 onclick : function() { 12203 if (t.settings.onselect('') !== false) 12204 t.select(''); // Must be runned after 12205 } 12206 }); 12207 } else { 12208 o.id = DOM.uniqueId(); 12209 o.role= "option"; 12210 o.onclick = function() { 12211 if (t.settings.onselect(o.value) !== false) 12212 t.select(o.value); // Must be runned after 12213 }; 12214 12215 m.add(o); 12216 } 12217 }); 12218 12219 t.onRenderMenu.dispatch(t, m); 12220 t.menu = m; 12221 }, 12222 12223 postRender : function() { 12224 var t = this, cp = t.classPrefix; 12225 12226 Event.add(t.id, 'click', t.showMenu, t); 12227 Event.add(t.id, 'keydown', function(evt) { 12228 if (evt.keyCode == 32) { // Space 12229 t.showMenu(evt); 12230 Event.cancel(evt); 12231 } 12232 }); 12233 Event.add(t.id, 'focus', function() { 12234 if (!t._focused) { 12235 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 12236 if (e.keyCode == 40) { 12237 t.showMenu(); 12238 Event.cancel(e); 12239 } 12240 }); 12241 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 12242 var v; 12243 if (e.keyCode == 13) { 12244 // Fake select on enter 12245 v = t.selectedValue; 12246 t.selectedValue = null; // Needs to be null to fake change 12247 Event.cancel(e); 12248 t.settings.onselect(v); 12249 } 12250 }); 12251 } 12252 12253 t._focused = 1; 12254 }); 12255 Event.add(t.id, 'blur', function() { 12256 Event.remove(t.id, 'keydown', t.keyDownHandler); 12257 Event.remove(t.id, 'keypress', t.keyPressHandler); 12258 t._focused = 0; 12259 }); 12260 12261 // Old IE doesn't have hover on all elements 12262 if (tinymce.isIE6 || !DOM.boxModel) { 12263 Event.add(t.id, 'mouseover', function() { 12264 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12265 DOM.addClass(t.id, cp + 'Hover'); 12266 }); 12267 12268 Event.add(t.id, 'mouseout', function() { 12269 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12270 DOM.removeClass(t.id, cp + 'Hover'); 12271 }); 12272 } 12273 12274 t.onPostRender.dispatch(t, DOM.get(t.id)); 12275 }, 12276 12277 destroy : function() { 12278 this.parent(); 12279 12280 Event.clear(this.id + '_text'); 12281 Event.clear(this.id + '_open'); 12282 } 12283 }); 12284 })(tinymce); 12285 (function(tinymce) { 12286 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 12287 12288 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 12289 NativeListBox : function(id, s) { 12290 this.parent(id, s); 12291 this.classPrefix = 'mceNativeListBox'; 12292 }, 12293 12294 setDisabled : function(s) { 12295 DOM.get(this.id).disabled = s; 12296 this.setAriaProperty('disabled', s); 12297 }, 12298 12299 isDisabled : function() { 12300 return DOM.get(this.id).disabled; 12301 }, 12302 12303 select : function(va) { 12304 var t = this, fv, f; 12305 12306 if (va == undef) 12307 return t.selectByIndex(-1); 12308 12309 // Is string or number make function selector 12310 if (va && typeof(va)=="function") 12311 f = va; 12312 else { 12313 f = function(v) { 12314 return v == va; 12315 }; 12316 } 12317 12318 // Do we need to do something? 12319 if (va != t.selectedValue) { 12320 // Find item 12321 each(t.items, function(o, i) { 12322 if (f(o.value)) { 12323 fv = 1; 12324 t.selectByIndex(i); 12325 return false; 12326 } 12327 }); 12328 12329 if (!fv) 12330 t.selectByIndex(-1); 12331 } 12332 }, 12333 12334 selectByIndex : function(idx) { 12335 DOM.get(this.id).selectedIndex = idx + 1; 12336 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12337 }, 12338 12339 add : function(n, v, a) { 12340 var o, t = this; 12341 12342 a = a || {}; 12343 a.value = v; 12344 12345 if (t.isRendered()) 12346 DOM.add(DOM.get(this.id), 'option', a, n); 12347 12348 o = { 12349 title : n, 12350 value : v, 12351 attribs : a 12352 }; 12353 12354 t.items.push(o); 12355 t.onAdd.dispatch(t, o); 12356 }, 12357 12358 getLength : function() { 12359 return this.items.length; 12360 }, 12361 12362 renderHTML : function() { 12363 var h, t = this; 12364 12365 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12366 12367 each(t.items, function(it) { 12368 h += DOM.createHTML('option', {value : it.value}, it.title); 12369 }); 12370 12371 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12372 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12373 return h; 12374 }, 12375 12376 postRender : function() { 12377 var t = this, ch, changeListenerAdded = true; 12378 12379 t.rendered = true; 12380 12381 function onChange(e) { 12382 var v = t.items[e.target.selectedIndex - 1]; 12383 12384 if (v && (v = v.value)) { 12385 t.onChange.dispatch(t, v); 12386 12387 if (t.settings.onselect) 12388 t.settings.onselect(v); 12389 } 12390 }; 12391 12392 Event.add(t.id, 'change', onChange); 12393 12394 // Accessibility keyhandler 12395 Event.add(t.id, 'keydown', function(e) { 12396 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; 12397 12398 Event.remove(t.id, 'change', ch); 12399 changeListenerAdded = false; 12400 12401 bf = Event.add(t.id, 'blur', function() { 12402 if (changeListenerAdded) return; 12403 changeListenerAdded = true; 12404 Event.add(t.id, 'change', onChange); 12405 Event.remove(t.id, 'blur', bf); 12406 }); 12407 12408 if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) { 12409 onChange(e); 12410 return Event.cancel(e); 12411 } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) { 12412 // allow native implementation (navigate select element options) 12413 e.stopImmediatePropagation(); 12414 } 12415 }); 12416 12417 t.onPostRender.dispatch(t, DOM.get(t.id)); 12418 } 12419 }); 12420 })(tinymce); 12421 (function(tinymce) { 12422 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12423 12424 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12425 MenuButton : function(id, s, ed) { 12426 this.parent(id, s, ed); 12427 12428 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12429 12430 s.menu_container = s.menu_container || DOM.doc.body; 12431 }, 12432 12433 showMenu : function() { 12434 var t = this, p1, p2, e = DOM.get(t.id), m; 12435 12436 if (t.isDisabled()) 12437 return; 12438 12439 if (!t.isMenuRendered) { 12440 t.renderMenu(); 12441 t.isMenuRendered = true; 12442 } 12443 12444 if (t.isMenuVisible) 12445 return t.hideMenu(); 12446 12447 p1 = DOM.getPos(t.settings.menu_container); 12448 p2 = DOM.getPos(e); 12449 12450 m = t.menu; 12451 m.settings.offset_x = p2.x; 12452 m.settings.offset_y = p2.y; 12453 m.settings.vp_offset_x = p2.x; 12454 m.settings.vp_offset_y = p2.y; 12455 m.settings.keyboard_focus = t._focused; 12456 m.showMenu(0, e.firstChild.clientHeight); 12457 12458 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12459 t.setState('Selected', 1); 12460 12461 t.isMenuVisible = 1; 12462 }, 12463 12464 renderMenu : function() { 12465 var t = this, m; 12466 12467 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12468 menu_line : 1, 12469 'class' : this.classPrefix + 'Menu', 12470 icons : t.settings.icons 12471 }); 12472 12473 m.onHideMenu.add(function() { 12474 t.hideMenu(); 12475 t.focus(); 12476 }); 12477 12478 t.onRenderMenu.dispatch(t, m); 12479 t.menu = m; 12480 }, 12481 12482 hideMenu : function(e) { 12483 var t = this; 12484 12485 // Prevent double toogles by canceling the mouse click event to the button 12486 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12487 return; 12488 12489 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12490 t.setState('Selected', 0); 12491 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12492 if (t.menu) 12493 t.menu.hideMenu(); 12494 } 12495 12496 t.isMenuVisible = 0; 12497 }, 12498 12499 postRender : function() { 12500 var t = this, s = t.settings; 12501 12502 Event.add(t.id, 'click', function() { 12503 if (!t.isDisabled()) { 12504 if (s.onclick) 12505 s.onclick(t.value); 12506 12507 t.showMenu(); 12508 } 12509 }); 12510 } 12511 }); 12512 })(tinymce); 12513 (function(tinymce) { 12514 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12515 12516 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12517 SplitButton : function(id, s, ed) { 12518 this.parent(id, s, ed); 12519 this.classPrefix = 'mceSplitButton'; 12520 }, 12521 12522 renderHTML : function() { 12523 var h, t = this, s = t.settings, h1; 12524 12525 h = '<tbody><tr>'; 12526 12527 if (s.image) 12528 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12529 else 12530 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12531 12532 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12533 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>'; 12534 12535 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12536 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>'; 12537 12538 h += '</tr></tbody>'; 12539 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12540 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12541 }, 12542 12543 postRender : function() { 12544 var t = this, s = t.settings, activate; 12545 12546 if (s.onclick) { 12547 activate = function(evt) { 12548 if (!t.isDisabled()) { 12549 s.onclick(t.value); 12550 Event.cancel(evt); 12551 } 12552 }; 12553 Event.add(t.id + '_action', 'click', activate); 12554 Event.add(t.id, ['click', 'keydown'], function(evt) { 12555 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12556 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12557 activate(); 12558 Event.cancel(evt); 12559 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12560 t.showMenu(); 12561 Event.cancel(evt); 12562 } 12563 }); 12564 } 12565 12566 Event.add(t.id + '_open', 'click', function (evt) { 12567 t.showMenu(); 12568 Event.cancel(evt); 12569 }); 12570 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12571 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12572 12573 // Old IE doesn't have hover on all elements 12574 if (tinymce.isIE6 || !DOM.boxModel) { 12575 Event.add(t.id, 'mouseover', function() { 12576 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12577 DOM.addClass(t.id, 'mceSplitButtonHover'); 12578 }); 12579 12580 Event.add(t.id, 'mouseout', function() { 12581 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12582 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12583 }); 12584 } 12585 }, 12586 12587 destroy : function() { 12588 this.parent(); 12589 12590 Event.clear(this.id + '_action'); 12591 Event.clear(this.id + '_open'); 12592 Event.clear(this.id); 12593 } 12594 }); 12595 })(tinymce); 12596 (function(tinymce) { 12597 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12598 12599 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12600 ColorSplitButton : function(id, s, ed) { 12601 var t = this; 12602 12603 t.parent(id, s, ed); 12604 12605 t.settings = s = tinymce.extend({ 12606 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', 12607 grid_width : 8, 12608 default_color : '#888888' 12609 }, t.settings); 12610 12611 t.onShowMenu = new tinymce.util.Dispatcher(t); 12612 12613 t.onHideMenu = new tinymce.util.Dispatcher(t); 12614 12615 t.value = s.default_color; 12616 }, 12617 12618 showMenu : function() { 12619 var t = this, r, p, e, p2; 12620 12621 if (t.isDisabled()) 12622 return; 12623 12624 if (!t.isMenuRendered) { 12625 t.renderMenu(); 12626 t.isMenuRendered = true; 12627 } 12628 12629 if (t.isMenuVisible) 12630 return t.hideMenu(); 12631 12632 e = DOM.get(t.id); 12633 DOM.show(t.id + '_menu'); 12634 DOM.addClass(e, 'mceSplitButtonSelected'); 12635 p2 = DOM.getPos(e); 12636 DOM.setStyles(t.id + '_menu', { 12637 left : p2.x, 12638 top : p2.y + e.firstChild.clientHeight, 12639 zIndex : 200000 12640 }); 12641 e = 0; 12642 12643 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12644 t.onShowMenu.dispatch(t); 12645 12646 if (t._focused) { 12647 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12648 if (e.keyCode == 27) 12649 t.hideMenu(); 12650 }); 12651 12652 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12653 } 12654 12655 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12656 root: t.id + '_menu', 12657 items: DOM.select('a', t.id + '_menu'), 12658 onCancel: function() { 12659 t.hideMenu(); 12660 t.focus(); 12661 } 12662 }); 12663 12664 t.keyboardNav.focus(); 12665 t.isMenuVisible = 1; 12666 }, 12667 12668 hideMenu : function(e) { 12669 var t = this; 12670 12671 if (t.isMenuVisible) { 12672 // Prevent double toogles by canceling the mouse click event to the button 12673 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12674 return; 12675 12676 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12677 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12678 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12679 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12680 DOM.hide(t.id + '_menu'); 12681 } 12682 12683 t.isMenuVisible = 0; 12684 t.onHideMenu.dispatch(); 12685 t.keyboardNav.destroy(); 12686 } 12687 }, 12688 12689 renderMenu : function() { 12690 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12691 12692 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;'}); 12693 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12694 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12695 12696 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12697 tb = DOM.add(n, 'tbody'); 12698 12699 // Generate color grid 12700 i = 0; 12701 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12702 c = c.replace(/^#/, ''); 12703 12704 if (!i--) { 12705 tr = DOM.add(tb, 'tr'); 12706 i = s.grid_width - 1; 12707 } 12708 12709 n = DOM.add(tr, 'td'); 12710 var settings = { 12711 href : 'javascript:;', 12712 style : { 12713 backgroundColor : '#' + c 12714 }, 12715 'title': t.editor.getLang('colors.' + c, c), 12716 'data-mce-color' : '#' + c 12717 }; 12718 12719 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12720 if (!tinymce.isIE ) { 12721 settings.role = 'option'; 12722 } 12723 12724 n = DOM.add(n, 'a', settings); 12725 12726 if (t.editor.forcedHighContrastMode) { 12727 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12728 if (n.getContext && (context = n.getContext("2d"))) { 12729 context.fillStyle = '#' + c; 12730 context.fillRect(0, 0, 16, 16); 12731 } else { 12732 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12733 DOM.remove(n); 12734 } 12735 } 12736 }); 12737 12738 if (s.more_colors_func) { 12739 n = DOM.add(tb, 'tr'); 12740 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12741 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12742 12743 Event.add(n, 'click', function(e) { 12744 s.more_colors_func.call(s.more_colors_scope || this); 12745 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12746 }); 12747 } 12748 12749 DOM.addClass(m, 'mceColorSplitMenu'); 12750 12751 // Prevent IE from scrolling and hindering click to occur #4019 12752 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12753 12754 Event.add(t.id + '_menu', 'click', function(e) { 12755 var c; 12756 12757 e = DOM.getParent(e.target, 'a', tb); 12758 12759 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12760 t.setColor(c); 12761 12762 return false; // Prevent IE auto save warning 12763 }); 12764 12765 return w; 12766 }, 12767 12768 setColor : function(c) { 12769 this.displayColor(c); 12770 this.hideMenu(); 12771 this.settings.onselect(c); 12772 }, 12773 12774 displayColor : function(c) { 12775 var t = this; 12776 12777 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12778 12779 t.value = c; 12780 }, 12781 12782 postRender : function() { 12783 var t = this, id = t.id; 12784 12785 t.parent(); 12786 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12787 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12788 }, 12789 12790 destroy : function() { 12791 var self = this; 12792 12793 self.parent(); 12794 12795 Event.clear(self.id + '_menu'); 12796 Event.clear(self.id + '_more'); 12797 DOM.remove(self.id + '_menu'); 12798 12799 if (self.keyboardNav) { 12800 self.keyboardNav.destroy(); 12801 } 12802 } 12803 }); 12804 })(tinymce); 12805 (function(tinymce) { 12806 // Shorten class names 12807 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12808 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12809 renderHTML : function() { 12810 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12811 12812 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12813 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12814 h.push("<span role='application'>"); 12815 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12816 each(controls, function(toolbar) { 12817 h.push(toolbar.renderHTML()); 12818 }); 12819 h.push("</span>"); 12820 h.push('</div>'); 12821 12822 return h.join(''); 12823 }, 12824 12825 focus : function() { 12826 var t = this; 12827 dom.get(t.id).focus(); 12828 }, 12829 12830 postRender : function() { 12831 var t = this, items = []; 12832 12833 each(t.controls, function(toolbar) { 12834 each (toolbar.controls, function(control) { 12835 if (control.id) { 12836 items.push(control); 12837 } 12838 }); 12839 }); 12840 12841 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12842 root: t.id, 12843 items: items, 12844 onCancel: function() { 12845 //Move focus if webkit so that navigation back will read the item. 12846 if (tinymce.isWebKit) { 12847 dom.get(t.editor.id+"_ifr").focus(); 12848 } 12849 t.editor.focus(); 12850 }, 12851 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12852 }); 12853 }, 12854 12855 destroy : function() { 12856 var self = this; 12857 12858 self.parent(); 12859 self.keyNav.destroy(); 12860 Event.clear(self.id); 12861 } 12862 }); 12863 })(tinymce); 12864 (function(tinymce) { 12865 // Shorten class names 12866 var dom = tinymce.DOM, each = tinymce.each; 12867 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12868 renderHTML : function() { 12869 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12870 12871 cl = t.controls; 12872 for (i=0; i<cl.length; i++) { 12873 // Get current control, prev control, next control and if the control is a list box or not 12874 co = cl[i]; 12875 pr = cl[i - 1]; 12876 nx = cl[i + 1]; 12877 12878 // Add toolbar start 12879 if (i === 0) { 12880 c = 'mceToolbarStart'; 12881 12882 if (co.Button) 12883 c += ' mceToolbarStartButton'; 12884 else if (co.SplitButton) 12885 c += ' mceToolbarStartSplitButton'; 12886 else if (co.ListBox) 12887 c += ' mceToolbarStartListBox'; 12888 12889 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12890 } 12891 12892 // Add toolbar end before list box and after the previous button 12893 // This is to fix the o2k7 editor skins 12894 if (pr && co.ListBox) { 12895 if (pr.Button || pr.SplitButton) 12896 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12897 } 12898 12899 // Render control HTML 12900 12901 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12902 if (dom.stdMode) 12903 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12904 else 12905 h += '<td>' + co.renderHTML() + '</td>'; 12906 12907 // Add toolbar start after list box and before the next button 12908 // This is to fix the o2k7 editor skins 12909 if (nx && co.ListBox) { 12910 if (nx.Button || nx.SplitButton) 12911 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12912 } 12913 } 12914 12915 c = 'mceToolbarEnd'; 12916 12917 if (co.Button) 12918 c += ' mceToolbarEndButton'; 12919 else if (co.SplitButton) 12920 c += ' mceToolbarEndSplitButton'; 12921 else if (co.ListBox) 12922 c += ' mceToolbarEndListBox'; 12923 12924 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12925 12926 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>'); 12927 } 12928 }); 12929 })(tinymce); 12930 (function(tinymce) { 12931 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12932 12933 tinymce.create('tinymce.AddOnManager', { 12934 AddOnManager : function() { 12935 var self = this; 12936 12937 self.items = []; 12938 self.urls = {}; 12939 self.lookup = {}; 12940 self.onAdd = new Dispatcher(self); 12941 }, 12942 12943 get : function(n) { 12944 if (this.lookup[n]) { 12945 return this.lookup[n].instance; 12946 } else { 12947 return undefined; 12948 } 12949 }, 12950 12951 dependencies : function(n) { 12952 var result; 12953 if (this.lookup[n]) { 12954 result = this.lookup[n].dependencies; 12955 } 12956 return result || []; 12957 }, 12958 12959 requireLangPack : function(n) { 12960 var s = tinymce.settings; 12961 12962 if (s && s.language && s.language_load !== false) 12963 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12964 }, 12965 12966 add : function(id, o, dependencies) { 12967 this.items.push(o); 12968 this.lookup[id] = {instance:o, dependencies:dependencies}; 12969 this.onAdd.dispatch(this, id, o); 12970 12971 return o; 12972 }, 12973 createUrl: function(baseUrl, dep) { 12974 if (typeof dep === "object") { 12975 return dep 12976 } else { 12977 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12978 } 12979 }, 12980 12981 addComponents: function(pluginName, scripts) { 12982 var pluginUrl = this.urls[pluginName]; 12983 tinymce.each(scripts, function(script){ 12984 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12985 }); 12986 }, 12987 12988 load : function(n, u, cb, s) { 12989 var t = this, url = u; 12990 12991 function loadDependencies() { 12992 var dependencies = t.dependencies(n); 12993 tinymce.each(dependencies, function(dep) { 12994 var newUrl = t.createUrl(u, dep); 12995 t.load(newUrl.resource, newUrl, undefined, undefined); 12996 }); 12997 if (cb) { 12998 if (s) { 12999 cb.call(s); 13000 } else { 13001 cb.call(tinymce.ScriptLoader); 13002 } 13003 } 13004 } 13005 13006 if (t.urls[n]) 13007 return; 13008 if (typeof u === "object") 13009 url = u.prefix + u.resource + u.suffix; 13010 13011 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 13012 url = tinymce.baseURL + '/' + url; 13013 13014 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 13015 13016 if (t.lookup[n]) { 13017 loadDependencies(); 13018 } else { 13019 tinymce.ScriptLoader.add(url, loadDependencies, s); 13020 } 13021 } 13022 }); 13023 13024 // Create plugin and theme managers 13025 tinymce.PluginManager = new tinymce.AddOnManager(); 13026 tinymce.ThemeManager = new tinymce.AddOnManager(); 13027 }(tinymce)); 13028 13029 (function(tinymce) { 13030 // Shorten names 13031 var each = tinymce.each, extend = tinymce.extend, 13032 DOM = tinymce.DOM, Event = tinymce.dom.Event, 13033 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13034 explode = tinymce.explode, 13035 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 13036 13037 // Setup some URLs where the editor API is located and where the document is 13038 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 13039 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 13040 tinymce.documentBaseURL += '/'; 13041 13042 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 13043 13044 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 13045 13046 // Add before unload listener 13047 // This was required since IE was leaking memory if you added and removed beforeunload listeners 13048 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 13049 tinymce.onBeforeUnload = new Dispatcher(tinymce); 13050 13051 // Must be on window or IE will leak if the editor is placed in frame or iframe 13052 Event.add(window, 'beforeunload', function(e) { 13053 tinymce.onBeforeUnload.dispatch(tinymce, e); 13054 }); 13055 13056 tinymce.onAddEditor = new Dispatcher(tinymce); 13057 13058 tinymce.onRemoveEditor = new Dispatcher(tinymce); 13059 13060 tinymce.EditorManager = extend(tinymce, { 13061 editors : [], 13062 13063 i18n : {}, 13064 13065 activeEditor : null, 13066 13067 init : function(s) { 13068 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 13069 13070 function createId(elm) { 13071 var id = elm.id; 13072 13073 // Use element id, or unique name or generate a unique id 13074 if (!id) { 13075 id = elm.name; 13076 13077 if (id && !DOM.get(id)) { 13078 id = elm.name; 13079 } else { 13080 // Generate unique name 13081 id = DOM.uniqueId(); 13082 } 13083 13084 elm.setAttribute('id', id); 13085 } 13086 13087 return id; 13088 }; 13089 13090 function execCallback(se, n, s) { 13091 var f = se[n]; 13092 13093 if (!f) 13094 return; 13095 13096 if (tinymce.is(f, 'string')) { 13097 s = f.replace(/\.\w+$/, ''); 13098 s = s ? tinymce.resolve(s) : 0; 13099 f = tinymce.resolve(f); 13100 } 13101 13102 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 13103 }; 13104 13105 function hasClass(n, c) { 13106 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 13107 }; 13108 13109 t.settings = s; 13110 13111 // Legacy call 13112 Event.bind(window, 'ready', function() { 13113 var l, co; 13114 13115 execCallback(s, 'onpageload'); 13116 13117 switch (s.mode) { 13118 case "exact": 13119 l = s.elements || ''; 13120 13121 if(l.length > 0) { 13122 each(explode(l), function(v) { 13123 if (DOM.get(v)) { 13124 ed = new tinymce.Editor(v, s); 13125 el.push(ed); 13126 ed.render(1); 13127 } else { 13128 each(document.forms, function(f) { 13129 each(f.elements, function(e) { 13130 if (e.name === v) { 13131 v = 'mce_editor_' + instanceCounter++; 13132 DOM.setAttrib(e, 'id', v); 13133 13134 ed = new tinymce.Editor(v, s); 13135 el.push(ed); 13136 ed.render(1); 13137 } 13138 }); 13139 }); 13140 } 13141 }); 13142 } 13143 break; 13144 13145 case "textareas": 13146 case "specific_textareas": 13147 each(DOM.select('textarea'), function(elm) { 13148 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 13149 return; 13150 13151 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 13152 ed = new tinymce.Editor(createId(elm), s); 13153 el.push(ed); 13154 ed.render(1); 13155 } 13156 }); 13157 break; 13158 13159 default: 13160 if (s.types) { 13161 // Process type specific selector 13162 each(s.types, function(type) { 13163 each(DOM.select(type.selector), function(elm) { 13164 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 13165 el.push(editor); 13166 editor.render(1); 13167 }); 13168 }); 13169 } else if (s.selector) { 13170 // Process global selector 13171 each(DOM.select(s.selector), function(elm) { 13172 var editor = new tinymce.Editor(createId(elm), s); 13173 el.push(editor); 13174 editor.render(1); 13175 }); 13176 } 13177 } 13178 13179 // Call onInit when all editors are initialized 13180 if (s.oninit) { 13181 l = co = 0; 13182 13183 each(el, function(ed) { 13184 co++; 13185 13186 if (!ed.initialized) { 13187 // Wait for it 13188 ed.onInit.add(function() { 13189 l++; 13190 13191 // All done 13192 if (l == co) 13193 execCallback(s, 'oninit'); 13194 }); 13195 } else 13196 l++; 13197 13198 // All done 13199 if (l == co) 13200 execCallback(s, 'oninit'); 13201 }); 13202 } 13203 }); 13204 }, 13205 13206 get : function(id) { 13207 if (id === undef) 13208 return this.editors; 13209 13210 if (!this.editors.hasOwnProperty(id)) 13211 return undef; 13212 13213 return this.editors[id]; 13214 }, 13215 13216 getInstanceById : function(id) { 13217 return this.get(id); 13218 }, 13219 13220 add : function(editor) { 13221 var self = this, editors = self.editors; 13222 13223 // Add named and index editor instance 13224 editors[editor.id] = editor; 13225 editors.push(editor); 13226 13227 self._setActive(editor); 13228 self.onAddEditor.dispatch(self, editor); 13229 13230 13231 return editor; 13232 }, 13233 13234 remove : function(editor) { 13235 var t = this, i, editors = t.editors; 13236 13237 // Not in the collection 13238 if (!editors[editor.id]) 13239 return null; 13240 13241 delete editors[editor.id]; 13242 13243 for (i = 0; i < editors.length; i++) { 13244 if (editors[i] == editor) { 13245 editors.splice(i, 1); 13246 break; 13247 } 13248 } 13249 13250 // Select another editor since the active one was removed 13251 if (t.activeEditor == editor) 13252 t._setActive(editors[0]); 13253 13254 editor.destroy(); 13255 t.onRemoveEditor.dispatch(t, editor); 13256 13257 return editor; 13258 }, 13259 13260 execCommand : function(c, u, v) { 13261 var t = this, ed = t.get(v), w; 13262 13263 function clr() { 13264 ed.destroy(); 13265 w.detachEvent('onunload', clr); 13266 w = w.tinyMCE = w.tinymce = null; // IE leak 13267 }; 13268 13269 // Manager commands 13270 switch (c) { 13271 case "mceFocus": 13272 ed.focus(); 13273 return true; 13274 13275 case "mceAddEditor": 13276 case "mceAddControl": 13277 if (!t.get(v)) 13278 new tinymce.Editor(v, t.settings).render(); 13279 13280 return true; 13281 13282 case "mceAddFrameControl": 13283 w = v.window; 13284 13285 // Add tinyMCE global instance and tinymce namespace to specified window 13286 w.tinyMCE = tinyMCE; 13287 w.tinymce = tinymce; 13288 13289 tinymce.DOM.doc = w.document; 13290 tinymce.DOM.win = w; 13291 13292 ed = new tinymce.Editor(v.element_id, v); 13293 ed.render(); 13294 13295 // Fix IE memory leaks 13296 if (tinymce.isIE && ! tinymce.isIE11) { 13297 w.attachEvent('onunload', clr); 13298 } 13299 13300 v.page_window = null; 13301 13302 return true; 13303 13304 case "mceRemoveEditor": 13305 case "mceRemoveControl": 13306 if (ed) 13307 ed.remove(); 13308 13309 return true; 13310 13311 case 'mceToggleEditor': 13312 if (!ed) { 13313 t.execCommand('mceAddControl', 0, v); 13314 return true; 13315 } 13316 13317 if (ed.isHidden()) 13318 ed.show(); 13319 else 13320 ed.hide(); 13321 13322 return true; 13323 } 13324 13325 // Run command on active editor 13326 if (t.activeEditor) 13327 return t.activeEditor.execCommand(c, u, v); 13328 13329 return false; 13330 }, 13331 13332 execInstanceCommand : function(id, c, u, v) { 13333 var ed = this.get(id); 13334 13335 if (ed) 13336 return ed.execCommand(c, u, v); 13337 13338 return false; 13339 }, 13340 13341 triggerSave : function() { 13342 each(this.editors, function(e) { 13343 e.save(); 13344 }); 13345 }, 13346 13347 addI18n : function(p, o) { 13348 var lo, i18n = this.i18n; 13349 13350 if (!tinymce.is(p, 'string')) { 13351 each(p, function(o, lc) { 13352 each(o, function(o, g) { 13353 each(o, function(o, k) { 13354 if (g === 'common') 13355 i18n[lc + '.' + k] = o; 13356 else 13357 i18n[lc + '.' + g + '.' + k] = o; 13358 }); 13359 }); 13360 }); 13361 } else { 13362 each(o, function(o, k) { 13363 i18n[p + '.' + k] = o; 13364 }); 13365 } 13366 }, 13367 13368 // Private methods 13369 13370 _setActive : function(editor) { 13371 this.selectedInstance = this.activeEditor = editor; 13372 } 13373 }); 13374 })(tinymce); 13375 13376 (function(tinymce) { 13377 // Shorten these names 13378 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13379 each = tinymce.each, isGecko = tinymce.isGecko, 13380 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13381 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13382 explode = tinymce.explode; 13383 13384 tinymce.create('tinymce.Editor', { 13385 Editor : function(id, settings) { 13386 var self = this, TRUE = true; 13387 13388 self.settings = settings = extend({ 13389 id : id, 13390 language : 'en', 13391 theme : 'advanced', 13392 skin : 'default', 13393 delta_width : 0, 13394 delta_height : 0, 13395 popup_css : '', 13396 plugins : '', 13397 document_base_url : tinymce.documentBaseURL, 13398 add_form_submit_trigger : TRUE, 13399 submit_patch : TRUE, 13400 add_unload_trigger : TRUE, 13401 convert_urls : TRUE, 13402 relative_urls : TRUE, 13403 remove_script_host : TRUE, 13404 table_inline_editing : false, 13405 object_resizing : TRUE, 13406 accessibility_focus : TRUE, 13407 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13408 visual : TRUE, 13409 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13410 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 13411 apply_source_formatting : TRUE, 13412 directionality : 'ltr', 13413 forced_root_block : 'p', 13414 hidden_input : TRUE, 13415 padd_empty_editor : TRUE, 13416 render_ui : TRUE, 13417 indentation : '30px', 13418 fix_table_elements : TRUE, 13419 inline_styles : TRUE, 13420 convert_fonts_to_spans : TRUE, 13421 indent : 'simple', 13422 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', 13423 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', 13424 validate : TRUE, 13425 entity_encoding : 'named', 13426 url_converter : self.convertURL, 13427 url_converter_scope : self, 13428 ie7_compat : TRUE 13429 }, settings); 13430 13431 self.id = self.editorId = id; 13432 13433 self.isNotDirty = false; 13434 13435 self.plugins = {}; 13436 13437 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13438 base_uri : tinyMCE.baseURI 13439 }); 13440 13441 self.baseURI = tinymce.baseURI; 13442 13443 self.contentCSS = []; 13444 13445 self.contentStyles = []; 13446 13447 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13448 self.setupEvents(); 13449 13450 // Internal command handler objects 13451 self.execCommands = {}; 13452 self.queryStateCommands = {}; 13453 self.queryValueCommands = {}; 13454 13455 // Call setup 13456 self.execCallback('setup', self); 13457 }, 13458 13459 render : function(nst) { 13460 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13461 13462 // Page is not loaded yet, wait for it 13463 if (!Event.domLoaded) { 13464 Event.add(window, 'ready', function() { 13465 t.render(); 13466 }); 13467 return; 13468 } 13469 13470 tinyMCE.settings = s; 13471 13472 // Element not found, then skip initialization 13473 if (!t.getElement()) 13474 return; 13475 13476 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13477 // here since the browser says it has contentEditable support but there is no visible caret. 13478 if (tinymce.isIDevice && !tinymce.isIOS5) 13479 return; 13480 13481 // Add hidden input for non input elements inside form elements 13482 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13483 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13484 13485 // Hide target element early to prevent content flashing 13486 if (!s.content_editable) { 13487 t.orgVisibility = t.getElement().style.visibility; 13488 t.getElement().style.visibility = 'hidden'; 13489 } 13490 13491 if (tinymce.WindowManager) 13492 t.windowManager = new tinymce.WindowManager(t); 13493 13494 if (s.encoding == 'xml') { 13495 t.onGetContent.add(function(ed, o) { 13496 if (o.save) 13497 o.content = DOM.encode(o.content); 13498 }); 13499 } 13500 13501 if (s.add_form_submit_trigger) { 13502 t.onSubmit.addToTop(function() { 13503 if (t.initialized) { 13504 t.save(); 13505 t.isNotDirty = 1; 13506 } 13507 }); 13508 } 13509 13510 if (s.add_unload_trigger) { 13511 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13512 if (t.initialized && !t.destroyed && !t.isHidden()) 13513 t.save({format : 'raw', no_events : true}); 13514 }); 13515 } 13516 13517 tinymce.addUnload(t.destroy, t); 13518 13519 if (s.submit_patch) { 13520 t.onBeforeRenderUI.add(function() { 13521 var n = t.getElement().form; 13522 13523 if (!n) 13524 return; 13525 13526 // Already patched 13527 if (n._mceOldSubmit) 13528 return; 13529 13530 // Check page uses id="submit" or name="submit" for it's submit button 13531 if (!n.submit.nodeType && !n.submit.length) { 13532 t.formElement = n; 13533 n._mceOldSubmit = n.submit; 13534 n.submit = function() { 13535 // Save all instances 13536 tinymce.triggerSave(); 13537 t.isNotDirty = 1; 13538 13539 return t.formElement._mceOldSubmit(t.formElement); 13540 }; 13541 } 13542 13543 n = null; 13544 }); 13545 } 13546 13547 // Load scripts 13548 function loadScripts() { 13549 if (s.language && s.language_load !== false) 13550 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13551 13552 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13553 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13554 13555 each(explode(s.plugins), function(p) { 13556 if (p &&!PluginManager.urls[p]) { 13557 if (p.charAt(0) == '-') { 13558 p = p.substr(1, p.length); 13559 var dependencies = PluginManager.dependencies(p); 13560 each(dependencies, function(dep) { 13561 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13562 dep = PluginManager.createUrl(defaultSettings, dep); 13563 PluginManager.load(dep.resource, dep); 13564 }); 13565 } else { 13566 // Skip safari plugin, since it is removed as of 3.3b1 13567 if (p == 'safari') { 13568 return; 13569 } 13570 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13571 } 13572 } 13573 }); 13574 13575 // Init when que is loaded 13576 sl.loadQueue(function() { 13577 if (!t.removed) 13578 t.init(); 13579 }); 13580 }; 13581 13582 loadScripts(); 13583 }, 13584 13585 init : function() { 13586 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13587 13588 tinymce.add(t); 13589 13590 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13591 13592 if (s.theme) { 13593 if (typeof s.theme != "function") { 13594 s.theme = s.theme.replace(/-/, ''); 13595 o = ThemeManager.get(s.theme); 13596 t.theme = new o(); 13597 13598 if (t.theme.init) 13599 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13600 } else { 13601 t.theme = s.theme; 13602 } 13603 } 13604 13605 function initPlugin(p) { 13606 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13607 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13608 each(PluginManager.dependencies(p), function(dep){ 13609 initPlugin(dep); 13610 }); 13611 po = new c(t, u); 13612 13613 t.plugins[p] = po; 13614 13615 if (po.init) { 13616 po.init(t, u); 13617 initializedPlugins.push(p); 13618 } 13619 } 13620 } 13621 13622 // Create all plugins 13623 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13624 13625 // Setup popup CSS path(s) 13626 if (s.popup_css !== false) { 13627 if (s.popup_css) 13628 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13629 else 13630 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13631 } 13632 13633 if (s.popup_css_add) 13634 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13635 13636 t.controlManager = new tinymce.ControlManager(t); 13637 13638 // Enables users to override the control factory 13639 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13640 13641 // Measure box 13642 if (s.render_ui && t.theme) { 13643 t.orgDisplay = e.style.display; 13644 13645 if (typeof s.theme != "function") { 13646 w = s.width || e.style.width || e.offsetWidth; 13647 h = s.height || e.style.height || e.offsetHeight; 13648 mh = s.min_height || 100; 13649 re = /^[0-9\.]+(|px)$/i; 13650 13651 if (re.test('' + w)) 13652 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13653 13654 if (re.test('' + h)) 13655 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13656 13657 // Render UI 13658 o = t.theme.renderUI({ 13659 targetNode : e, 13660 width : w, 13661 height : h, 13662 deltaWidth : s.delta_width, 13663 deltaHeight : s.delta_height 13664 }); 13665 13666 // Resize editor 13667 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13668 width : w, 13669 height : h 13670 }); 13671 13672 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13673 if (h < mh) 13674 h = mh; 13675 } else { 13676 o = s.theme(t, e); 13677 13678 // Convert element type to id:s 13679 if (o.editorContainer.nodeType) { 13680 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13681 } 13682 13683 // Convert element type to id:s 13684 if (o.iframeContainer.nodeType) { 13685 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13686 } 13687 13688 // Use specified iframe height or the targets offsetHeight 13689 h = o.iframeHeight || e.offsetHeight; 13690 13691 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13692 if (isIE) { 13693 t.onInit.add(function(ed) { 13694 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() { 13695 ed.bookmark = ed.selection.getBookmark(1); 13696 }); 13697 }); 13698 13699 t.onNodeChange.add(function(ed) { 13700 if (document.activeElement.id == ed.id + "_ifr") { 13701 ed.bookmark = ed.selection.getBookmark(1); 13702 } 13703 }); 13704 } 13705 } 13706 13707 t.editorContainer = o.editorContainer; 13708 } 13709 13710 // Load specified content CSS last 13711 if (s.content_css) { 13712 each(explode(s.content_css), function(u) { 13713 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13714 }); 13715 } 13716 13717 // Load specified content CSS last 13718 if (s.content_style) { 13719 t.contentStyles.push(s.content_style); 13720 } 13721 13722 // Content editable mode ends here 13723 if (s.content_editable) { 13724 e = n = o = null; // Fix IE leak 13725 return t.initContentBody(); 13726 } 13727 13728 // User specified a document.domain value 13729 if (document.domain && location.hostname != document.domain) 13730 tinymce.relaxedDomain = document.domain; 13731 13732 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13733 13734 // We only need to override paths if we have to 13735 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13736 if (s.document_base_url != tinymce.documentBaseURL) 13737 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13738 13739 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13740 if (tinymce.isIE8) { 13741 if (s.ie7_compat) 13742 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13743 else 13744 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13745 } 13746 13747 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13748 13749 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13750 for (i = 0; i < t.contentCSS.length; i++) { 13751 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13752 } 13753 13754 t.contentCSS = []; 13755 13756 bi = s.body_id || 'tinymce'; 13757 if (bi.indexOf('=') != -1) { 13758 bi = t.getParam('body_id', '', 'hash'); 13759 bi = bi[t.id] || bi; 13760 } 13761 13762 bc = s.body_class || ''; 13763 if (bc.indexOf('=') != -1) { 13764 bc = t.getParam('body_class', '', 'hash'); 13765 bc = bc[t.id] || ''; 13766 } 13767 13768 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13769 13770 // Domain relaxing enabled, then set document domain 13771 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13772 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13773 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();})()'; 13774 } 13775 13776 // Create iframe 13777 // TODO: ACC add the appropriate description on this. 13778 n = DOM.add(o.iframeContainer, 'iframe', { 13779 id : t.id + "_ifr", 13780 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13781 frameBorder : '0', 13782 allowTransparency : "true", 13783 title : s.aria_label, 13784 style : { 13785 width : '100%', 13786 height : h, 13787 display : 'block' // Important for Gecko to render the iframe correctly 13788 } 13789 }); 13790 13791 t.contentAreaContainer = o.iframeContainer; 13792 13793 if (o.editorContainer) { 13794 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13795 } 13796 13797 // Restore visibility on target element 13798 e.style.visibility = t.orgVisibility; 13799 13800 DOM.get(t.id).style.display = 'none'; 13801 DOM.setAttrib(t.id, 'aria-hidden', true); 13802 13803 if (!tinymce.relaxedDomain || !u) 13804 t.initContentBody(); 13805 13806 e = n = o = null; // Cleanup 13807 }, 13808 13809 initContentBody : function() { 13810 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13811 13812 // Setup iframe body 13813 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13814 doc.open(); 13815 doc.write(self.iframeHTML); 13816 doc.close(); 13817 13818 if (tinymce.relaxedDomain) 13819 doc.domain = tinymce.relaxedDomain; 13820 } 13821 13822 if (settings.content_editable) { 13823 DOM.addClass(targetElm, 'mceContentBody'); 13824 self.contentDocument = doc = settings.content_document || document; 13825 self.contentWindow = settings.content_window || window; 13826 self.bodyElement = targetElm; 13827 13828 // Prevent leak in IE 13829 settings.content_document = settings.content_window = null; 13830 } 13831 13832 // It will not steal focus while setting contentEditable 13833 body = self.getBody(); 13834 body.disabled = true; 13835 13836 if (!settings.readonly) 13837 body.contentEditable = self.getParam('content_editable_state', true); 13838 13839 body.disabled = false; 13840 13841 self.schema = new tinymce.html.Schema(settings); 13842 13843 self.dom = new tinymce.dom.DOMUtils(doc, { 13844 keep_values : true, 13845 url_converter : self.convertURL, 13846 url_converter_scope : self, 13847 hex_colors : settings.force_hex_style_colors, 13848 class_filter : settings.class_filter, 13849 update_styles : true, 13850 root_element : settings.content_editable ? self.id : null, 13851 schema : self.schema 13852 }); 13853 13854 self.parser = new tinymce.html.DomParser(settings, self.schema); 13855 13856 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13857 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13858 var i = nodes.length, node, dom = self.dom, value, internalName; 13859 13860 while (i--) { 13861 node = nodes[i]; 13862 value = node.attr(name); 13863 internalName = 'data-mce-' + name; 13864 13865 // Add internal attribute if we need to we don't on a refresh of the document 13866 if (!node.attributes.map[internalName]) { 13867 if (name === "style") 13868 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13869 else 13870 node.attr(internalName, self.convertURL(value, name, node.name)); 13871 } 13872 } 13873 }); 13874 13875 // Keep scripts from executing 13876 self.parser.addNodeFilter('script', function(nodes, name) { 13877 var i = nodes.length, node; 13878 13879 while (i--) { 13880 node = nodes[i]; 13881 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13882 } 13883 }); 13884 13885 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13886 var i = nodes.length, node; 13887 13888 while (i--) { 13889 node = nodes[i]; 13890 node.type = 8; 13891 node.name = '#comment'; 13892 node.value = '[CDATA[' + node.value + ']]'; 13893 } 13894 }); 13895 13896 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13897 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13898 13899 while (i--) { 13900 node = nodes[i]; 13901 13902 if (node.isEmpty(nonEmptyElements)) 13903 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13904 } 13905 }); 13906 13907 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13908 13909 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13910 13911 self.formatter = new tinymce.Formatter(self); 13912 13913 self.undoManager = new tinymce.UndoManager(self); 13914 13915 self.forceBlocks = new tinymce.ForceBlocks(self); 13916 self.enterKey = new tinymce.EnterKey(self); 13917 self.editorCommands = new tinymce.EditorCommands(self); 13918 13919 self.onExecCommand.add(function(editor, command) { 13920 // Don't refresh the select lists until caret move 13921 if (!/^(FontName|FontSize)$/.test(command)) 13922 self.nodeChanged(); 13923 }); 13924 13925 // Pass through 13926 self.serializer.onPreProcess.add(function(se, o) { 13927 return self.onPreProcess.dispatch(self, o, se); 13928 }); 13929 13930 self.serializer.onPostProcess.add(function(se, o) { 13931 return self.onPostProcess.dispatch(self, o, se); 13932 }); 13933 13934 self.onPreInit.dispatch(self); 13935 13936 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13937 doc.body.spellcheck = false; 13938 13939 if (!settings.readonly) { 13940 self.bindNativeEvents(); 13941 } 13942 13943 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13944 self.onPostRender.dispatch(self); 13945 13946 self.quirks = tinymce.util.Quirks(self); 13947 13948 if (settings.directionality) 13949 body.dir = settings.directionality; 13950 13951 if (settings.nowrap) 13952 body.style.whiteSpace = "nowrap"; 13953 13954 if (settings.protect) { 13955 self.onBeforeSetContent.add(function(ed, o) { 13956 each(settings.protect, function(pattern) { 13957 o.content = o.content.replace(pattern, function(str) { 13958 return '<!--mce:protected ' + escape(str) + '-->'; 13959 }); 13960 }); 13961 }); 13962 } 13963 13964 // Add visual aids when new contents is added 13965 self.onSetContent.add(function() { 13966 self.addVisual(self.getBody()); 13967 }); 13968 13969 // Remove empty contents 13970 if (settings.padd_empty_editor) { 13971 self.onPostProcess.add(function(ed, o) { 13972 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13973 }); 13974 } 13975 13976 self.load({initial : true, format : 'html'}); 13977 self.startContent = self.getContent({format : 'raw'}); 13978 13979 self.initialized = true; 13980 13981 self.onInit.dispatch(self); 13982 self.execCallback('setupcontent_callback', self.id, body, doc); 13983 self.execCallback('init_instance_callback', self); 13984 self.focus(true); 13985 self.nodeChanged({initial : true}); 13986 13987 // Add editor specific CSS styles 13988 if (self.contentStyles.length > 0) { 13989 contentCssText = ''; 13990 13991 each(self.contentStyles, function(style) { 13992 contentCssText += style + "\r\n"; 13993 }); 13994 13995 self.dom.addStyle(contentCssText); 13996 } 13997 13998 // Load specified content CSS last 13999 each(self.contentCSS, function(url) { 14000 self.dom.loadCSS(url); 14001 }); 14002 14003 // Handle auto focus 14004 if (settings.auto_focus) { 14005 setTimeout(function () { 14006 var ed = tinymce.get(settings.auto_focus); 14007 14008 ed.selection.select(ed.getBody(), 1); 14009 ed.selection.collapse(1); 14010 ed.getBody().focus(); 14011 ed.getWin().focus(); 14012 }, 100); 14013 } 14014 14015 // Clean up references for IE 14016 targetElm = doc = body = null; 14017 }, 14018 14019 focus : function(skip_focus) { 14020 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 14021 14022 if (!skip_focus) { 14023 if (self.bookmark) { 14024 selection.moveToBookmark(self.bookmark); 14025 self.bookmark = null; 14026 } 14027 14028 // Get selected control element 14029 ieRng = selection.getRng(); 14030 if (ieRng.item) { 14031 controlElm = ieRng.item(0); 14032 } 14033 14034 self._refreshContentEditable(); 14035 14036 // Focus the window iframe 14037 if (!contentEditable) { 14038 self.getWin().focus(); 14039 } 14040 14041 // Focus the body as well since it's contentEditable 14042 if (tinymce.isGecko || contentEditable) { 14043 body = self.getBody(); 14044 14045 // Check for setActive since it doesn't scroll to the element 14046 if (body.setActive && ! tinymce.isIE11) { 14047 body.setActive(); 14048 } else { 14049 body.focus(); 14050 } 14051 14052 if (contentEditable) { 14053 selection.normalize(); 14054 } 14055 } 14056 14057 // Restore selected control element 14058 // This is needed when for example an image is selected within a 14059 // layer a call to focus will then remove the control selection 14060 if (controlElm && controlElm.ownerDocument == doc) { 14061 ieRng = doc.body.createControlRange(); 14062 ieRng.addElement(controlElm); 14063 ieRng.select(); 14064 } 14065 } 14066 14067 if (tinymce.activeEditor != self) { 14068 if ((oed = tinymce.activeEditor) != null) 14069 oed.onDeactivate.dispatch(oed, self); 14070 14071 self.onActivate.dispatch(self, oed); 14072 } 14073 14074 tinymce._setActive(self); 14075 }, 14076 14077 execCallback : function(n) { 14078 var t = this, f = t.settings[n], s; 14079 14080 if (!f) 14081 return; 14082 14083 // Look through lookup 14084 if (t.callbackLookup && (s = t.callbackLookup[n])) { 14085 f = s.func; 14086 s = s.scope; 14087 } 14088 14089 if (is(f, 'string')) { 14090 s = f.replace(/\.\w+$/, ''); 14091 s = s ? tinymce.resolve(s) : 0; 14092 f = tinymce.resolve(f); 14093 t.callbackLookup = t.callbackLookup || {}; 14094 t.callbackLookup[n] = {func : f, scope : s}; 14095 } 14096 14097 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 14098 }, 14099 14100 translate : function(s) { 14101 var c = this.settings.language || 'en', i18n = tinymce.i18n; 14102 14103 if (!s) 14104 return ''; 14105 14106 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 14107 return i18n[c + '.' + b] || '{#' + b + '}'; 14108 }); 14109 }, 14110 14111 getLang : function(n, dv) { 14112 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 14113 }, 14114 14115 getParam : function(n, dv, ty) { 14116 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 14117 14118 if (ty === 'hash') { 14119 o = {}; 14120 14121 if (is(v, 'string')) { 14122 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 14123 v = v.split('='); 14124 14125 if (v.length > 1) 14126 o[tr(v[0])] = tr(v[1]); 14127 else 14128 o[tr(v[0])] = tr(v); 14129 }); 14130 } else 14131 o = v; 14132 14133 return o; 14134 } 14135 14136 return v; 14137 }, 14138 14139 nodeChanged : function(o) { 14140 var self = this, selection = self.selection, node; 14141 14142 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 14143 if (self.initialized) { 14144 o = o || {}; 14145 14146 // Get start node 14147 node = selection.getStart() || self.getBody(); 14148 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 14149 14150 // Get parents and add them to object 14151 o.parents = []; 14152 self.dom.getParent(node, function(node) { 14153 if (node.nodeName == 'BODY') 14154 return true; 14155 14156 o.parents.push(node); 14157 }); 14158 14159 self.onNodeChange.dispatch( 14160 self, 14161 o ? o.controlManager || self.controlManager : self.controlManager, 14162 node, 14163 selection.isCollapsed(), 14164 o 14165 ); 14166 } 14167 }, 14168 14169 addButton : function(name, settings) { 14170 var self = this; 14171 14172 self.buttons = self.buttons || {}; 14173 self.buttons[name] = settings; 14174 }, 14175 14176 addCommand : function(name, callback, scope) { 14177 this.execCommands[name] = {func : callback, scope : scope || this}; 14178 }, 14179 14180 addQueryStateHandler : function(name, callback, scope) { 14181 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 14182 }, 14183 14184 addQueryValueHandler : function(name, callback, scope) { 14185 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 14186 }, 14187 14188 addShortcut : function(pa, desc, cmd_func, sc) { 14189 var t = this, c; 14190 14191 if (t.settings.custom_shortcuts === false) 14192 return false; 14193 14194 t.shortcuts = t.shortcuts || {}; 14195 14196 if (is(cmd_func, 'string')) { 14197 c = cmd_func; 14198 14199 cmd_func = function() { 14200 t.execCommand(c, false, null); 14201 }; 14202 } 14203 14204 if (is(cmd_func, 'object')) { 14205 c = cmd_func; 14206 14207 cmd_func = function() { 14208 t.execCommand(c[0], c[1], c[2]); 14209 }; 14210 } 14211 14212 each(explode(pa), function(pa) { 14213 var o = { 14214 func : cmd_func, 14215 scope : sc || this, 14216 desc : t.translate(desc), 14217 alt : false, 14218 ctrl : false, 14219 shift : false 14220 }; 14221 14222 each(explode(pa, '+'), function(v) { 14223 switch (v) { 14224 case 'alt': 14225 case 'ctrl': 14226 case 'shift': 14227 o[v] = true; 14228 break; 14229 14230 default: 14231 o.charCode = v.charCodeAt(0); 14232 o.keyCode = v.toUpperCase().charCodeAt(0); 14233 } 14234 }); 14235 14236 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 14237 }); 14238 14239 return true; 14240 }, 14241 14242 execCommand : function(cmd, ui, val, a) { 14243 var t = this, s = 0, o, st; 14244 14245 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 14246 t.focus(); 14247 14248 a = extend({}, a); 14249 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 14250 if (a.terminate) 14251 return false; 14252 14253 // Command callback 14254 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 14255 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14256 return true; 14257 } 14258 14259 // Registred commands 14260 if (o = t.execCommands[cmd]) { 14261 st = o.func.call(o.scope, ui, val); 14262 14263 // Fall through on true 14264 if (st !== true) { 14265 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14266 return st; 14267 } 14268 } 14269 14270 // Plugin commands 14271 each(t.plugins, function(p) { 14272 if (p.execCommand && p.execCommand(cmd, ui, val)) { 14273 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14274 s = 1; 14275 return false; 14276 } 14277 }); 14278 14279 if (s) 14280 return true; 14281 14282 // Theme commands 14283 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 14284 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14285 return true; 14286 } 14287 14288 // Editor commands 14289 if (t.editorCommands.execCommand(cmd, ui, val)) { 14290 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14291 return true; 14292 } 14293 14294 // Browser commands 14295 t.getDoc().execCommand(cmd, ui, val); 14296 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14297 }, 14298 14299 queryCommandState : function(cmd) { 14300 var t = this, o, s; 14301 14302 // Is hidden then return undefined 14303 if (t._isHidden()) 14304 return; 14305 14306 // Registred commands 14307 if (o = t.queryStateCommands[cmd]) { 14308 s = o.func.call(o.scope); 14309 14310 // Fall though on true 14311 if (s !== true) 14312 return s; 14313 } 14314 14315 // Registred commands 14316 o = t.editorCommands.queryCommandState(cmd); 14317 if (o !== -1) 14318 return o; 14319 14320 // Browser commands 14321 try { 14322 return this.getDoc().queryCommandState(cmd); 14323 } catch (ex) { 14324 // Fails sometimes see bug: 1896577 14325 } 14326 }, 14327 14328 queryCommandValue : function(c) { 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.queryValueCommands[c]) { 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.queryCommandValue(c); 14346 if (is(o)) 14347 return o; 14348 14349 // Browser commands 14350 try { 14351 return this.getDoc().queryCommandValue(c); 14352 } catch (ex) { 14353 // Fails sometimes see bug: 1896577 14354 } 14355 }, 14356 14357 show : function() { 14358 var self = this; 14359 14360 DOM.show(self.getContainer()); 14361 DOM.hide(self.id); 14362 self.load(); 14363 }, 14364 14365 hide : function() { 14366 var self = this, doc = self.getDoc(); 14367 14368 // Fixed bug where IE has a blinking cursor left from the editor 14369 if (isIE && doc) 14370 doc.execCommand('SelectAll'); 14371 14372 // We must save before we hide so Safari doesn't crash 14373 self.save(); 14374 14375 // defer the call to hide to prevent an IE9 crash #4921 14376 DOM.hide(self.getContainer()); 14377 DOM.setStyle(self.id, 'display', self.orgDisplay); 14378 }, 14379 14380 isHidden : function() { 14381 return !DOM.isHidden(this.id); 14382 }, 14383 14384 setProgressState : function(b, ti, o) { 14385 this.onSetProgressState.dispatch(this, b, ti, o); 14386 14387 return b; 14388 }, 14389 14390 load : function(o) { 14391 var t = this, e = t.getElement(), h; 14392 14393 if (e) { 14394 o = o || {}; 14395 o.load = true; 14396 14397 // Double encode existing entities in the value 14398 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14399 o.element = e; 14400 14401 if (!o.no_events) 14402 t.onLoadContent.dispatch(t, o); 14403 14404 o.element = e = null; 14405 14406 return h; 14407 } 14408 }, 14409 14410 save : function(o) { 14411 var t = this, e = t.getElement(), h, f; 14412 14413 if (!e || !t.initialized) 14414 return; 14415 14416 o = o || {}; 14417 o.save = true; 14418 14419 o.element = e; 14420 h = o.content = t.getContent(o); 14421 14422 if (!o.no_events) 14423 t.onSaveContent.dispatch(t, o); 14424 14425 h = o.content; 14426 14427 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14428 e.innerHTML = h; 14429 14430 // Update hidden form element 14431 if (f = DOM.getParent(t.id, 'form')) { 14432 each(f.elements, function(e) { 14433 if (e.name == t.id) { 14434 e.value = h; 14435 return false; 14436 } 14437 }); 14438 } 14439 } else 14440 e.value = h; 14441 14442 o.element = e = null; 14443 14444 return h; 14445 }, 14446 14447 setContent : function(content, args) { 14448 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14449 14450 // Setup args object 14451 args = args || {}; 14452 args.format = args.format || 'html'; 14453 args.set = true; 14454 args.content = content; 14455 14456 // Do preprocessing 14457 if (!args.no_events) 14458 self.onBeforeSetContent.dispatch(self, args); 14459 14460 content = args.content; 14461 14462 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14463 // It will also be impossible to place the caret in the editor unless there is a BR element present 14464 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14465 forcedRootBlockName = self.settings.forced_root_block; 14466 if (forcedRootBlockName) 14467 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14468 else 14469 content = '<br data-mce-bogus="1">'; 14470 14471 body.innerHTML = content; 14472 self.selection.select(body, true); 14473 self.selection.collapse(true); 14474 return; 14475 } 14476 14477 // Parse and serialize the html 14478 if (args.format !== 'raw') { 14479 content = new tinymce.html.Serializer({}, self.schema).serialize( 14480 self.parser.parse(content) 14481 ); 14482 } 14483 14484 // Set the new cleaned contents to the editor 14485 args.content = tinymce.trim(content); 14486 self.dom.setHTML(body, args.content); 14487 14488 // Do post processing 14489 if (!args.no_events) 14490 self.onSetContent.dispatch(self, args); 14491 14492 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14493 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14494 self.selection.normalize(); 14495 } 14496 14497 return args.content; 14498 }, 14499 14500 getContent : function(args) { 14501 var self = this, content, body = self.getBody(); 14502 14503 // Setup args object 14504 args = args || {}; 14505 args.format = args.format || 'html'; 14506 args.get = true; 14507 args.getInner = true; 14508 14509 // Do preprocessing 14510 if (!args.no_events) 14511 self.onBeforeGetContent.dispatch(self, args); 14512 14513 // Get raw contents or by default the cleaned contents 14514 if (args.format == 'raw') 14515 content = body.innerHTML; 14516 else if (args.format == 'text') 14517 content = body.innerText || body.textContent; 14518 else 14519 content = self.serializer.serialize(body, args); 14520 14521 // Trim whitespace in beginning/end of HTML 14522 if (args.format != 'text') { 14523 args.content = tinymce.trim(content); 14524 } else { 14525 args.content = content; 14526 } 14527 14528 // Do post processing 14529 if (!args.no_events) 14530 self.onGetContent.dispatch(self, args); 14531 14532 return args.content; 14533 }, 14534 14535 isDirty : function() { 14536 var self = this; 14537 14538 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14539 }, 14540 14541 getContainer : function() { 14542 var self = this; 14543 14544 if (!self.container) 14545 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14546 14547 return self.container; 14548 }, 14549 14550 getContentAreaContainer : function() { 14551 return this.contentAreaContainer; 14552 }, 14553 14554 getElement : function() { 14555 return DOM.get(this.settings.content_element || this.id); 14556 }, 14557 14558 getWin : function() { 14559 var self = this, elm; 14560 14561 if (!self.contentWindow) { 14562 elm = DOM.get(self.id + "_ifr"); 14563 14564 if (elm) 14565 self.contentWindow = elm.contentWindow; 14566 } 14567 14568 return self.contentWindow; 14569 }, 14570 14571 getDoc : function() { 14572 var self = this, win; 14573 14574 if (!self.contentDocument) { 14575 win = self.getWin(); 14576 14577 if (win) 14578 self.contentDocument = win.document; 14579 } 14580 14581 return self.contentDocument; 14582 }, 14583 14584 getBody : function() { 14585 return this.bodyElement || this.getDoc().body; 14586 }, 14587 14588 convertURL : function(url, name, elm) { 14589 var self = this, settings = self.settings; 14590 14591 // Use callback instead 14592 if (settings.urlconverter_callback) 14593 return self.execCallback('urlconverter_callback', url, elm, true, name); 14594 14595 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14596 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14597 return url; 14598 14599 // Convert to relative 14600 if (settings.relative_urls) 14601 return self.documentBaseURI.toRelative(url); 14602 14603 // Convert to absolute 14604 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14605 14606 return url; 14607 }, 14608 14609 addVisual : function(elm) { 14610 var self = this, settings = self.settings, dom = self.dom, cls; 14611 14612 elm = elm || self.getBody(); 14613 14614 if (!is(self.hasVisual)) 14615 self.hasVisual = settings.visual; 14616 14617 each(dom.select('table,a', elm), function(elm) { 14618 var value; 14619 14620 switch (elm.nodeName) { 14621 case 'TABLE': 14622 cls = settings.visual_table_class || 'mceItemTable'; 14623 value = dom.getAttrib(elm, 'border'); 14624 14625 if (!value || value == '0') { 14626 if (self.hasVisual) 14627 dom.addClass(elm, cls); 14628 else 14629 dom.removeClass(elm, cls); 14630 } 14631 14632 return; 14633 14634 case 'A': 14635 if (!dom.getAttrib(elm, 'href', false)) { 14636 value = dom.getAttrib(elm, 'name') || elm.id; 14637 cls = 'mceItemAnchor'; 14638 14639 if (value) { 14640 if (self.hasVisual) 14641 dom.addClass(elm, cls); 14642 else 14643 dom.removeClass(elm, cls); 14644 } 14645 } 14646 14647 return; 14648 } 14649 }); 14650 14651 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14652 }, 14653 14654 remove : function() { 14655 var self = this, elm = self.getContainer(), doc = self.getDoc(); 14656 14657 if (!self.removed) { 14658 self.removed = 1; // Cancels post remove event execution 14659 14660 // Fixed bug where IE has a blinking cursor left from the editor 14661 if (isIE && doc) 14662 doc.execCommand('SelectAll'); 14663 14664 // We must save before we hide so Safari doesn't crash 14665 self.save(); 14666 14667 DOM.setStyle(self.id, 'display', self.orgDisplay); 14668 14669 // Don't clear the window or document if content editable 14670 // is enabled since other instances might still be present 14671 if (!self.settings.content_editable) { 14672 Event.unbind(self.getWin()); 14673 Event.unbind(self.getDoc()); 14674 } 14675 14676 Event.unbind(self.getBody()); 14677 Event.clear(elm); 14678 14679 self.execCallback('remove_instance_callback', self); 14680 self.onRemove.dispatch(self); 14681 14682 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14683 self.onExecCommand.listeners = []; 14684 14685 tinymce.remove(self); 14686 DOM.remove(elm); 14687 } 14688 }, 14689 14690 destroy : function(s) { 14691 var t = this; 14692 14693 // One time is enough 14694 if (t.destroyed) 14695 return; 14696 14697 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14698 if (isGecko) { 14699 Event.unbind(t.getDoc()); 14700 Event.unbind(t.getWin()); 14701 Event.unbind(t.getBody()); 14702 } 14703 14704 if (!s) { 14705 tinymce.removeUnload(t.destroy); 14706 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14707 14708 // Manual destroy 14709 if (t.theme && t.theme.destroy) 14710 t.theme.destroy(); 14711 14712 // Destroy controls, selection and dom 14713 t.controlManager.destroy(); 14714 t.selection.destroy(); 14715 t.dom.destroy(); 14716 } 14717 14718 if (t.formElement) { 14719 t.formElement.submit = t.formElement._mceOldSubmit; 14720 t.formElement._mceOldSubmit = null; 14721 } 14722 14723 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14724 14725 if (t.selection) 14726 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14727 14728 t.destroyed = 1; 14729 }, 14730 14731 // Internal functions 14732 14733 _refreshContentEditable : function() { 14734 var self = this, body, parent; 14735 14736 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14737 if (self._isHidden()) { 14738 body = self.getBody(); 14739 parent = body.parentNode; 14740 14741 parent.removeChild(body); 14742 parent.appendChild(body); 14743 14744 body.focus(); 14745 } 14746 }, 14747 14748 _isHidden : function() { 14749 var s; 14750 14751 if (!isGecko) 14752 return 0; 14753 14754 // Weird, wheres that cursor selection? 14755 s = this.selection.getSel(); 14756 return (!s || !s.rangeCount || s.rangeCount === 0); 14757 } 14758 }); 14759 })(tinymce); 14760 (function(tinymce) { 14761 var each = tinymce.each; 14762 14763 tinymce.Editor.prototype.setupEvents = function() { 14764 var self = this, settings = self.settings; 14765 14766 // Add events to the editor 14767 each([ 14768 'onPreInit', 14769 14770 'onBeforeRenderUI', 14771 14772 'onPostRender', 14773 14774 'onLoad', 14775 14776 'onInit', 14777 14778 'onRemove', 14779 14780 'onActivate', 14781 14782 'onDeactivate', 14783 14784 'onClick', 14785 14786 'onEvent', 14787 14788 'onMouseUp', 14789 14790 'onMouseDown', 14791 14792 'onDblClick', 14793 14794 'onKeyDown', 14795 14796 'onKeyUp', 14797 14798 'onKeyPress', 14799 14800 'onContextMenu', 14801 14802 'onSubmit', 14803 14804 'onReset', 14805 14806 'onPaste', 14807 14808 'onPreProcess', 14809 14810 'onPostProcess', 14811 14812 'onBeforeSetContent', 14813 14814 'onBeforeGetContent', 14815 14816 'onSetContent', 14817 14818 'onGetContent', 14819 14820 'onLoadContent', 14821 14822 'onSaveContent', 14823 14824 'onNodeChange', 14825 14826 'onChange', 14827 14828 'onBeforeExecCommand', 14829 14830 'onExecCommand', 14831 14832 'onUndo', 14833 14834 'onRedo', 14835 14836 'onVisualAid', 14837 14838 'onSetProgressState', 14839 14840 'onSetAttrib' 14841 ], function(name) { 14842 self[name] = new tinymce.util.Dispatcher(self); 14843 }); 14844 14845 // Handle legacy cleanup_callback option 14846 if (settings.cleanup_callback) { 14847 self.onBeforeSetContent.add(function(ed, o) { 14848 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14849 }); 14850 14851 self.onPreProcess.add(function(ed, o) { 14852 if (o.set) 14853 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14854 14855 if (o.get) 14856 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14857 }); 14858 14859 self.onPostProcess.add(function(ed, o) { 14860 if (o.set) 14861 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14862 14863 if (o.get) 14864 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14865 }); 14866 } 14867 14868 // Handle legacy save_callback option 14869 if (settings.save_callback) { 14870 self.onGetContent.add(function(ed, o) { 14871 if (o.save) 14872 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14873 }); 14874 } 14875 14876 // Handle legacy handle_event_callback option 14877 if (settings.handle_event_callback) { 14878 self.onEvent.add(function(ed, e, o) { 14879 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14880 e.preventDefault(); 14881 e.stopPropagation(); 14882 } 14883 }); 14884 } 14885 14886 // Handle legacy handle_node_change_callback option 14887 if (settings.handle_node_change_callback) { 14888 self.onNodeChange.add(function(ed, cm, n) { 14889 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14890 }); 14891 } 14892 14893 // Handle legacy save_callback option 14894 if (settings.save_callback) { 14895 self.onSaveContent.add(function(ed, o) { 14896 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14897 14898 if (h) 14899 o.content = h; 14900 }); 14901 } 14902 14903 // Handle legacy onchange_callback option 14904 if (settings.onchange_callback) { 14905 self.onChange.add(function(ed, l) { 14906 ed.execCallback('onchange_callback', ed, l); 14907 }); 14908 } 14909 }; 14910 14911 tinymce.Editor.prototype.bindNativeEvents = function() { 14912 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14913 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14914 14915 nativeToDispatcherMap = { 14916 mouseup : 'onMouseUp', 14917 mousedown : 'onMouseDown', 14918 click : 'onClick', 14919 keyup : 'onKeyUp', 14920 keydown : 'onKeyDown', 14921 keypress : 'onKeyPress', 14922 submit : 'onSubmit', 14923 reset : 'onReset', 14924 contextmenu : 'onContextMenu', 14925 dblclick : 'onDblClick', 14926 paste : 'onPaste' // Doesn't work in all browsers yet 14927 }; 14928 14929 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14930 function eventHandler(evt, args) { 14931 var type = evt.type; 14932 14933 // Don't fire events when it's removed 14934 if (self.removed) 14935 return; 14936 14937 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14938 if (self.onEvent.dispatch(self, evt, args) !== false) { 14939 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14940 } 14941 }; 14942 14943 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14944 function doOperaFocus(e) { 14945 self.focus(true); 14946 }; 14947 14948 function nodeChanged(ed, e) { 14949 // 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 14950 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14951 self.selection.normalize(); 14952 } 14953 14954 self.nodeChanged(); 14955 } 14956 14957 // Add DOM events 14958 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14959 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14960 14961 switch (nativeName) { 14962 case 'contextmenu': 14963 dom.bind(root, nativeName, eventHandler); 14964 break; 14965 14966 case 'paste': 14967 dom.bind(self.getBody(), nativeName, eventHandler); 14968 break; 14969 14970 case 'submit': 14971 case 'reset': 14972 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14973 break; 14974 14975 default: 14976 dom.bind(root, nativeName, eventHandler); 14977 } 14978 }); 14979 14980 // Set the editor as active when focused 14981 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14982 self.focus(true); 14983 }); 14984 14985 if (settings.content_editable && tinymce.isOpera) { 14986 dom.bind(self.getBody(), 'click', doOperaFocus); 14987 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14988 } 14989 14990 // Add node change handler 14991 self.onMouseUp.add(nodeChanged); 14992 14993 self.onKeyUp.add(function(ed, e) { 14994 var keyCode = e.keyCode; 14995 14996 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) 14997 nodeChanged(ed, e); 14998 }); 14999 15000 // Add reset handler 15001 self.onReset.add(function() { 15002 self.setContent(self.startContent, {format : 'raw'}); 15003 }); 15004 15005 // Add shortcuts 15006 function handleShortcut(e, execute) { 15007 if (e.altKey || e.ctrlKey || e.metaKey) { 15008 each(self.shortcuts, function(shortcut) { 15009 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 15010 15011 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 15012 return; 15013 15014 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 15015 e.preventDefault(); 15016 15017 if (execute) { 15018 shortcut.func.call(shortcut.scope); 15019 } 15020 15021 return true; 15022 } 15023 }); 15024 } 15025 }; 15026 15027 self.onKeyUp.add(function(ed, e) { 15028 handleShortcut(e); 15029 }); 15030 15031 self.onKeyPress.add(function(ed, e) { 15032 handleShortcut(e); 15033 }); 15034 15035 self.onKeyDown.add(function(ed, e) { 15036 handleShortcut(e, true); 15037 }); 15038 15039 if (tinymce.isOpera) { 15040 self.onClick.add(function(ed, e) { 15041 e.preventDefault(); 15042 }); 15043 } 15044 }; 15045 })(tinymce); 15046 (function(tinymce) { 15047 // Added for compression purposes 15048 var each = tinymce.each, undef, TRUE = true, FALSE = false; 15049 15050 tinymce.EditorCommands = function(editor) { 15051 var dom = editor.dom, 15052 selection = editor.selection, 15053 commands = {state: {}, exec : {}, value : {}}, 15054 settings = editor.settings, 15055 formatter = editor.formatter, 15056 bookmark; 15057 15058 function execCommand(command, ui, value) { 15059 var func; 15060 15061 command = command.toLowerCase(); 15062 if (func = commands.exec[command]) { 15063 func(command, ui, value); 15064 return TRUE; 15065 } 15066 15067 return FALSE; 15068 }; 15069 15070 function queryCommandState(command) { 15071 var func; 15072 15073 command = command.toLowerCase(); 15074 if (func = commands.state[command]) 15075 return func(command); 15076 15077 return -1; 15078 }; 15079 15080 function queryCommandValue(command) { 15081 var func; 15082 15083 command = command.toLowerCase(); 15084 if (func = commands.value[command]) 15085 return func(command); 15086 15087 return FALSE; 15088 }; 15089 15090 function addCommands(command_list, type) { 15091 type = type || 'exec'; 15092 15093 each(command_list, function(callback, command) { 15094 each(command.toLowerCase().split(','), function(command) { 15095 commands[type][command] = callback; 15096 }); 15097 }); 15098 }; 15099 15100 // Expose public methods 15101 tinymce.extend(this, { 15102 execCommand : execCommand, 15103 queryCommandState : queryCommandState, 15104 queryCommandValue : queryCommandValue, 15105 addCommands : addCommands 15106 }); 15107 15108 // Private methods 15109 15110 function execNativeCommand(command, ui, value) { 15111 if (ui === undef) 15112 ui = FALSE; 15113 15114 if (value === undef) 15115 value = null; 15116 15117 return editor.getDoc().execCommand(command, ui, value); 15118 }; 15119 15120 function isFormatMatch(name) { 15121 return formatter.match(name); 15122 }; 15123 15124 function toggleFormat(name, value) { 15125 formatter.toggle(name, value ? {value : value} : undef); 15126 }; 15127 15128 function storeSelection(type) { 15129 bookmark = selection.getBookmark(type); 15130 }; 15131 15132 function restoreSelection() { 15133 selection.moveToBookmark(bookmark); 15134 }; 15135 15136 // Add execCommand overrides 15137 addCommands({ 15138 // Ignore these, added for compatibility 15139 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 15140 15141 // Add undo manager logic 15142 'mceEndUndoLevel,mceAddUndoLevel' : function() { 15143 editor.undoManager.add(); 15144 }, 15145 15146 'Cut,Copy,Paste' : function(command) { 15147 var doc = editor.getDoc(), failed; 15148 15149 // Try executing the native command 15150 try { 15151 execNativeCommand(command); 15152 } catch (ex) { 15153 // Command failed 15154 failed = TRUE; 15155 } 15156 15157 // Present alert message about clipboard access not being available 15158 if (failed || !doc.queryCommandSupported(command)) { 15159 if (tinymce.isGecko) { 15160 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 15161 if (state) 15162 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 15163 }); 15164 } else 15165 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 15166 } 15167 }, 15168 15169 // Override unlink command 15170 unlink : function(command) { 15171 if (selection.isCollapsed()) 15172 selection.select(selection.getNode()); 15173 15174 execNativeCommand(command); 15175 selection.collapse(FALSE); 15176 }, 15177 15178 // Override justify commands to use the text formatter engine 15179 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15180 var align = command.substring(7); 15181 15182 // Remove all other alignments first 15183 each('left,center,right,full'.split(','), function(name) { 15184 if (align != name) 15185 formatter.remove('align' + name); 15186 }); 15187 15188 toggleFormat('align' + align); 15189 execCommand('mceRepaint'); 15190 }, 15191 15192 // Override list commands to fix WebKit bug 15193 'InsertUnorderedList,InsertOrderedList' : function(command) { 15194 var listElm, listParent; 15195 15196 execNativeCommand(command); 15197 15198 // WebKit produces lists within block elements so we need to split them 15199 // we will replace the native list creation logic to custom logic later on 15200 // TODO: Remove this when the list creation logic is removed 15201 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 15202 if (listElm) { 15203 listParent = listElm.parentNode; 15204 15205 // If list is within a text block then split that block 15206 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 15207 storeSelection(); 15208 dom.split(listParent, listElm); 15209 restoreSelection(); 15210 } 15211 } 15212 }, 15213 15214 // Override commands to use the text formatter engine 15215 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15216 toggleFormat(command); 15217 }, 15218 15219 // Override commands to use the text formatter engine 15220 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 15221 toggleFormat(command, value); 15222 }, 15223 15224 FontSize : function(command, ui, value) { 15225 var fontClasses, fontSizes; 15226 15227 // Convert font size 1-7 to styles 15228 if (value >= 1 && value <= 7) { 15229 fontSizes = tinymce.explode(settings.font_size_style_values); 15230 fontClasses = tinymce.explode(settings.font_size_classes); 15231 15232 if (fontClasses) 15233 value = fontClasses[value - 1] || value; 15234 else 15235 value = fontSizes[value - 1] || value; 15236 } 15237 15238 toggleFormat(command, value); 15239 }, 15240 15241 RemoveFormat : function(command) { 15242 formatter.remove(command); 15243 }, 15244 15245 mceBlockQuote : function(command) { 15246 toggleFormat('blockquote'); 15247 }, 15248 15249 FormatBlock : function(command, ui, value) { 15250 return toggleFormat(value || 'p'); 15251 }, 15252 15253 mceCleanup : function() { 15254 var bookmark = selection.getBookmark(); 15255 15256 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 15257 15258 selection.moveToBookmark(bookmark); 15259 }, 15260 15261 mceRemoveNode : function(command, ui, value) { 15262 var node = value || selection.getNode(); 15263 15264 // Make sure that the body node isn't removed 15265 if (node != editor.getBody()) { 15266 storeSelection(); 15267 editor.dom.remove(node, TRUE); 15268 restoreSelection(); 15269 } 15270 }, 15271 15272 mceSelectNodeDepth : function(command, ui, value) { 15273 var counter = 0; 15274 15275 dom.getParent(selection.getNode(), function(node) { 15276 if (node.nodeType == 1 && counter++ == value) { 15277 selection.select(node); 15278 return FALSE; 15279 } 15280 }, editor.getBody()); 15281 }, 15282 15283 mceSelectNode : function(command, ui, value) { 15284 selection.select(value); 15285 }, 15286 15287 mceInsertContent : function(command, ui, value) { 15288 var parser, serializer, parentNode, rootNode, fragment, args, 15289 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 15290 15291 //selection.normalize(); 15292 15293 // Setup parser and serializer 15294 parser = editor.parser; 15295 serializer = new tinymce.html.Serializer({}, editor.schema); 15296 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 15297 15298 // Run beforeSetContent handlers on the HTML to be inserted 15299 args = {content: value, format: 'html'}; 15300 selection.onBeforeSetContent.dispatch(selection, args); 15301 value = args.content; 15302 15303 // Add caret at end of contents if it's missing 15304 if (value.indexOf('{$caret}') == -1) 15305 value += '{$caret}'; 15306 15307 // Replace the caret marker with a span bookmark element 15308 value = value.replace(/\{\$caret\}/, bookmarkHtml); 15309 15310 // Insert node maker where we will insert the new HTML and get it's parent 15311 if (!selection.isCollapsed()) 15312 editor.getDoc().execCommand('Delete', false, null); 15313 15314 parentNode = selection.getNode(); 15315 15316 // Parse the fragment within the context of the parent node 15317 args = {context : parentNode.nodeName.toLowerCase()}; 15318 fragment = parser.parse(value, args); 15319 15320 // Move the caret to a more suitable location 15321 node = fragment.lastChild; 15322 if (node.attr('id') == 'mce_marker') { 15323 marker = node; 15324 15325 for (node = node.prev; node; node = node.walk(true)) { 15326 if (node.type == 3 || !dom.isBlock(node.name)) { 15327 node.parent.insert(marker, node, node.name === 'br'); 15328 break; 15329 } 15330 } 15331 } 15332 15333 // If parser says valid we can insert the contents into that parent 15334 if (!args.invalid) { 15335 value = serializer.serialize(fragment); 15336 15337 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 15338 node = parentNode.firstChild; 15339 node2 = parentNode.lastChild; 15340 if (!node || (node === node2 && node.nodeName === 'BR')) 15341 dom.setHTML(parentNode, value); 15342 else 15343 selection.setContent(value); 15344 } else { 15345 // If the fragment was invalid within that context then we need 15346 // to parse and process the parent it's inserted into 15347 15348 // Insert bookmark node and get the parent 15349 selection.setContent(bookmarkHtml); 15350 parentNode = selection.getNode(); 15351 rootNode = editor.getBody(); 15352 15353 // Opera will return the document node when selection is in root 15354 if (parentNode.nodeType == 9) 15355 parentNode = node = rootNode; 15356 else 15357 node = parentNode; 15358 15359 // Find the ancestor just before the root element 15360 while (node !== rootNode) { 15361 parentNode = node; 15362 node = node.parentNode; 15363 } 15364 15365 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15366 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15367 value = serializer.serialize( 15368 parser.parse( 15369 // Need to replace by using a function since $ in the contents would otherwise be a problem 15370 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15371 return serializer.serialize(fragment); 15372 }) 15373 ) 15374 ); 15375 15376 // Set the inner/outer HTML depending on if we are in the root or not 15377 if (parentNode == rootNode) 15378 dom.setHTML(rootNode, value); 15379 else 15380 dom.setOuterHTML(parentNode, value); 15381 } 15382 15383 marker = dom.get('mce_marker'); 15384 15385 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15386 nodeRect = dom.getRect(marker); 15387 viewPortRect = dom.getViewPort(editor.getWin()); 15388 15389 // Check if node is out side the viewport if it is then scroll to it 15390 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15391 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15392 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15393 viewportBodyElement.scrollLeft = nodeRect.x; 15394 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15395 } 15396 15397 // Move selection before marker and remove it 15398 rng = dom.createRng(); 15399 15400 // If previous sibling is a text node set the selection to the end of that node 15401 node = marker.previousSibling; 15402 if (node && node.nodeType == 3) { 15403 rng.setStart(node, node.nodeValue.length); 15404 } else { 15405 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15406 rng.setStartBefore(marker); 15407 rng.setEndBefore(marker); 15408 } 15409 15410 // Remove the marker node and set the new range 15411 dom.remove(marker); 15412 selection.setRng(rng); 15413 15414 // Dispatch after event and add any visual elements needed 15415 selection.onSetContent.dispatch(selection, args); 15416 editor.addVisual(); 15417 }, 15418 15419 mceInsertRawHTML : function(command, ui, value) { 15420 selection.setContent('tiny_mce_marker'); 15421 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15422 }, 15423 15424 mceToggleFormat : function(command, ui, value) { 15425 toggleFormat(value); 15426 }, 15427 15428 mceSetContent : function(command, ui, value) { 15429 editor.setContent(value); 15430 }, 15431 15432 'Indent,Outdent' : function(command) { 15433 var intentValue, indentUnit, value; 15434 15435 // Setup indent level 15436 intentValue = settings.indentation; 15437 indentUnit = /[a-z%]+$/i.exec(intentValue); 15438 intentValue = parseInt(intentValue); 15439 15440 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15441 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15442 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15443 formatter.apply('div'); 15444 } 15445 15446 each(selection.getSelectedBlocks(), function(element) { 15447 if (command == 'outdent') { 15448 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15449 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15450 } else 15451 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15452 }); 15453 } else 15454 execNativeCommand(command); 15455 }, 15456 15457 mceRepaint : function() { 15458 var bookmark; 15459 15460 if (tinymce.isGecko) { 15461 try { 15462 storeSelection(TRUE); 15463 15464 if (selection.getSel()) 15465 selection.getSel().selectAllChildren(editor.getBody()); 15466 15467 selection.collapse(TRUE); 15468 restoreSelection(); 15469 } catch (ex) { 15470 // Ignore 15471 } 15472 } 15473 }, 15474 15475 mceToggleFormat : function(command, ui, value) { 15476 formatter.toggle(value); 15477 }, 15478 15479 InsertHorizontalRule : function() { 15480 editor.execCommand('mceInsertContent', false, '<hr />'); 15481 }, 15482 15483 mceToggleVisualAid : function() { 15484 editor.hasVisual = !editor.hasVisual; 15485 editor.addVisual(); 15486 }, 15487 15488 mceReplaceContent : function(command, ui, value) { 15489 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15490 }, 15491 15492 mceInsertLink : function(command, ui, value) { 15493 var anchor; 15494 15495 if (typeof(value) == 'string') 15496 value = {href : value}; 15497 15498 anchor = dom.getParent(selection.getNode(), 'a'); 15499 15500 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15501 value.href = value.href.replace(' ', '%20'); 15502 15503 // Remove existing links if there could be child links or that the href isn't specified 15504 if (!anchor || !value.href) { 15505 formatter.remove('link'); 15506 } 15507 15508 // Apply new link to selection 15509 if (value.href) { 15510 formatter.apply('link', value, anchor); 15511 } 15512 }, 15513 15514 selectAll : function() { 15515 var root = dom.getRoot(), rng = dom.createRng(); 15516 15517 // Old IE does a better job with selectall than new versions 15518 if (selection.getRng().setStart) { 15519 rng.setStart(root, 0); 15520 rng.setEnd(root, root.childNodes.length); 15521 15522 selection.setRng(rng); 15523 } else { 15524 execNativeCommand('SelectAll'); 15525 } 15526 } 15527 }); 15528 15529 // Add queryCommandState overrides 15530 addCommands({ 15531 // Override justify commands 15532 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15533 var name = 'align' + command.substring(7); 15534 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15535 var matches = tinymce.map(nodes, function(node) { 15536 return !!formatter.matchNode(node, name); 15537 }); 15538 return tinymce.inArray(matches, TRUE) !== -1; 15539 }, 15540 15541 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15542 return isFormatMatch(command); 15543 }, 15544 15545 mceBlockQuote : function() { 15546 return isFormatMatch('blockquote'); 15547 }, 15548 15549 Outdent : function() { 15550 var node; 15551 15552 if (settings.inline_styles) { 15553 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15554 return TRUE; 15555 15556 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15557 return TRUE; 15558 } 15559 15560 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15561 }, 15562 15563 'InsertUnorderedList,InsertOrderedList' : function(command) { 15564 var list = dom.getParent(selection.getNode(), 'ul,ol'); 15565 return list && 15566 (command === 'insertunorderedlist' && list.tagName === 'UL' 15567 || command === 'insertorderedlist' && list.tagName === 'OL'); 15568 } 15569 }, 'state'); 15570 15571 // Add queryCommandValue overrides 15572 addCommands({ 15573 'FontSize,FontName' : function(command) { 15574 var value = 0, parent; 15575 15576 if (parent = dom.getParent(selection.getNode(), 'span')) { 15577 if (command == 'fontsize') 15578 value = parent.style.fontSize; 15579 else 15580 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15581 } 15582 15583 return value; 15584 } 15585 }, 'value'); 15586 15587 // Add undo manager logic 15588 addCommands({ 15589 Undo : function() { 15590 editor.undoManager.undo(); 15591 }, 15592 15593 Redo : function() { 15594 editor.undoManager.redo(); 15595 } 15596 }); 15597 }; 15598 })(tinymce); 15599 (function(tinymce) { 15600 var Dispatcher = tinymce.util.Dispatcher; 15601 15602 tinymce.UndoManager = function(editor) { 15603 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15604 15605 function getContent() { 15606 // Remove whitespace before/after and remove pure bogus nodes 15607 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15608 }; 15609 15610 function addNonTypingUndoLevel() { 15611 self.typing = false; 15612 self.add(); 15613 }; 15614 15615 // Create event instances 15616 onBeforeAdd = new Dispatcher(self); 15617 onAdd = new Dispatcher(self); 15618 onUndo = new Dispatcher(self); 15619 onRedo = new Dispatcher(self); 15620 15621 // Pass though onAdd event from UndoManager to Editor as onChange 15622 onAdd.add(function(undoman, level) { 15623 if (undoman.hasUndo()) 15624 return editor.onChange.dispatch(editor, level, undoman); 15625 }); 15626 15627 // Pass though onUndo event from UndoManager to Editor 15628 onUndo.add(function(undoman, level) { 15629 return editor.onUndo.dispatch(editor, level, undoman); 15630 }); 15631 15632 // Pass though onRedo event from UndoManager to Editor 15633 onRedo.add(function(undoman, level) { 15634 return editor.onRedo.dispatch(editor, level, undoman); 15635 }); 15636 15637 // Add initial undo level when the editor is initialized 15638 editor.onInit.add(function() { 15639 self.add(); 15640 }); 15641 15642 // Get position before an execCommand is processed 15643 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15644 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15645 self.beforeChange(); 15646 } 15647 }); 15648 15649 // Add undo level after an execCommand call was made 15650 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15651 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15652 self.add(); 15653 } 15654 }); 15655 15656 // Add undo level on save contents, drag end and blur/focusout 15657 editor.onSaveContent.add(addNonTypingUndoLevel); 15658 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15659 editor.dom.bind(editor.getBody(), 'focusout', function(e) { 15660 if (!editor.removed && self.typing) { 15661 addNonTypingUndoLevel(); 15662 } 15663 }); 15664 15665 editor.onKeyUp.add(function(editor, e) { 15666 var keyCode = e.keyCode; 15667 15668 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15669 addNonTypingUndoLevel(); 15670 } 15671 }); 15672 15673 editor.onKeyDown.add(function(editor, e) { 15674 var keyCode = e.keyCode; 15675 15676 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15677 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15678 if (self.typing) { 15679 addNonTypingUndoLevel(); 15680 } 15681 15682 return; 15683 } 15684 15685 // If key isn't shift,ctrl,alt,capslock,metakey 15686 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15687 self.beforeChange(); 15688 self.typing = true; 15689 self.add(); 15690 } 15691 }); 15692 15693 editor.onMouseDown.add(function(editor, e) { 15694 if (self.typing) { 15695 addNonTypingUndoLevel(); 15696 } 15697 }); 15698 15699 // Add keyboard shortcuts for undo/redo keys 15700 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15701 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15702 15703 self = { 15704 // Explose for debugging reasons 15705 data : data, 15706 15707 typing : false, 15708 15709 onBeforeAdd: onBeforeAdd, 15710 15711 onAdd : onAdd, 15712 15713 onUndo : onUndo, 15714 15715 onRedo : onRedo, 15716 15717 beforeChange : function() { 15718 beforeBookmark = editor.selection.getBookmark(2, true); 15719 }, 15720 15721 add : function(level) { 15722 var i, settings = editor.settings, lastLevel; 15723 15724 level = level || {}; 15725 level.content = getContent(); 15726 15727 self.onBeforeAdd.dispatch(self, level); 15728 15729 // Add undo level if needed 15730 lastLevel = data[index]; 15731 if (lastLevel && lastLevel.content == level.content) 15732 return null; 15733 15734 // Set before bookmark on previous level 15735 if (data[index]) 15736 data[index].beforeBookmark = beforeBookmark; 15737 15738 // Time to compress 15739 if (settings.custom_undo_redo_levels) { 15740 if (data.length > settings.custom_undo_redo_levels) { 15741 for (i = 0; i < data.length - 1; i++) 15742 data[i] = data[i + 1]; 15743 15744 data.length--; 15745 index = data.length; 15746 } 15747 } 15748 15749 // Get a non intrusive normalized bookmark 15750 level.bookmark = editor.selection.getBookmark(2, true); 15751 15752 // Crop array if needed 15753 if (index < data.length - 1) 15754 data.length = index + 1; 15755 15756 data.push(level); 15757 index = data.length - 1; 15758 15759 self.onAdd.dispatch(self, level); 15760 editor.isNotDirty = 0; 15761 15762 return level; 15763 }, 15764 15765 undo : function() { 15766 var level, i; 15767 15768 if (self.typing) { 15769 self.add(); 15770 self.typing = false; 15771 } 15772 15773 if (index > 0) { 15774 level = data[--index]; 15775 15776 editor.setContent(level.content, {format : 'raw'}); 15777 editor.selection.moveToBookmark(level.beforeBookmark); 15778 15779 self.onUndo.dispatch(self, level); 15780 } 15781 15782 return level; 15783 }, 15784 15785 redo : function() { 15786 var level; 15787 15788 if (index < data.length - 1) { 15789 level = data[++index]; 15790 15791 editor.setContent(level.content, {format : 'raw'}); 15792 editor.selection.moveToBookmark(level.bookmark); 15793 15794 self.onRedo.dispatch(self, level); 15795 } 15796 15797 return level; 15798 }, 15799 15800 clear : function() { 15801 data = []; 15802 index = 0; 15803 self.typing = false; 15804 }, 15805 15806 hasUndo : function() { 15807 return index > 0 || this.typing; 15808 }, 15809 15810 hasRedo : function() { 15811 return index < data.length - 1 && !this.typing; 15812 } 15813 }; 15814 15815 return self; 15816 }; 15817 })(tinymce); 15818 tinymce.ForceBlocks = function(editor) { 15819 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15820 15821 function addRootBlocks() { 15822 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15823 15824 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15825 return; 15826 15827 // Check if node is wrapped in block 15828 while (node && node != rootNode) { 15829 if (blockElements[node.nodeName]) 15830 return; 15831 15832 node = node.parentNode; 15833 } 15834 15835 // Get current selection 15836 rng = selection.getRng(); 15837 if (rng.setStart) { 15838 startContainer = rng.startContainer; 15839 startOffset = rng.startOffset; 15840 endContainer = rng.endContainer; 15841 endOffset = rng.endOffset; 15842 } else { 15843 // Force control range into text range 15844 if (rng.item) { 15845 node = rng.item(0); 15846 rng = editor.getDoc().body.createTextRange(); 15847 rng.moveToElementText(node); 15848 } 15849 15850 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15851 tmpRng = rng.duplicate(); 15852 tmpRng.collapse(true); 15853 startOffset = tmpRng.move('character', offset) * -1; 15854 15855 if (!tmpRng.collapsed) { 15856 tmpRng = rng.duplicate(); 15857 tmpRng.collapse(false); 15858 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15859 } 15860 } 15861 15862 // Wrap non block elements and text nodes 15863 node = rootNode.firstChild; 15864 while (node) { 15865 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15866 // Remove empty text nodes 15867 if (node.nodeType === 3 && node.nodeValue.length == 0) { 15868 tempNode = node; 15869 node = node.nextSibling; 15870 dom.remove(tempNode); 15871 continue; 15872 } 15873 15874 if (!rootBlockNode) { 15875 rootBlockNode = dom.create(settings.forced_root_block); 15876 node.parentNode.insertBefore(rootBlockNode, node); 15877 wrapped = true; 15878 } 15879 15880 tempNode = node; 15881 node = node.nextSibling; 15882 rootBlockNode.appendChild(tempNode); 15883 } else { 15884 rootBlockNode = null; 15885 node = node.nextSibling; 15886 } 15887 } 15888 15889 if (wrapped) { 15890 if (rng.setStart) { 15891 rng.setStart(startContainer, startOffset); 15892 rng.setEnd(endContainer, endOffset); 15893 selection.setRng(rng); 15894 } else { 15895 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15896 if (isInEditorDocument) { 15897 try { 15898 rng = editor.getDoc().body.createTextRange(); 15899 rng.moveToElementText(rootNode); 15900 rng.collapse(true); 15901 rng.moveStart('character', startOffset); 15902 15903 if (endOffset > 0) 15904 rng.moveEnd('character', endOffset); 15905 15906 rng.select(); 15907 } catch (ex) { 15908 // Ignore 15909 } 15910 } 15911 } 15912 15913 editor.nodeChanged(); 15914 } 15915 }; 15916 15917 // Force root blocks 15918 if (settings.forced_root_block) { 15919 editor.onKeyUp.add(addRootBlocks); 15920 editor.onNodeChange.add(addRootBlocks); 15921 } 15922 }; 15923 (function(tinymce) { 15924 // Shorten names 15925 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15926 15927 tinymce.create('tinymce.ControlManager', { 15928 ControlManager : function(ed, s) { 15929 var t = this, i; 15930 15931 s = s || {}; 15932 t.editor = ed; 15933 t.controls = {}; 15934 t.onAdd = new tinymce.util.Dispatcher(t); 15935 t.onPostRender = new tinymce.util.Dispatcher(t); 15936 t.prefix = s.prefix || ed.id + '_'; 15937 t._cls = {}; 15938 15939 t.onPostRender.add(function() { 15940 each(t.controls, function(c) { 15941 c.postRender(); 15942 }); 15943 }); 15944 }, 15945 15946 get : function(id) { 15947 return this.controls[this.prefix + id] || this.controls[id]; 15948 }, 15949 15950 setActive : function(id, s) { 15951 var c = null; 15952 15953 if (c = this.get(id)) 15954 c.setActive(s); 15955 15956 return c; 15957 }, 15958 15959 setDisabled : function(id, s) { 15960 var c = null; 15961 15962 if (c = this.get(id)) 15963 c.setDisabled(s); 15964 15965 return c; 15966 }, 15967 15968 add : function(c) { 15969 var t = this; 15970 15971 if (c) { 15972 t.controls[c.id] = c; 15973 t.onAdd.dispatch(c, t); 15974 } 15975 15976 return c; 15977 }, 15978 15979 createControl : function(name) { 15980 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15981 15982 // Build control factory cache 15983 if (!self.controlFactories) { 15984 self.controlFactories = []; 15985 each(editor.plugins, function(plugin) { 15986 if (plugin.createControl) { 15987 self.controlFactories.push(plugin); 15988 } 15989 }); 15990 } 15991 15992 // Create controls by asking cached factories 15993 factories = self.controlFactories; 15994 for (i = 0, l = factories.length; i < l; i++) { 15995 ctrl = factories[i].createControl(name, self); 15996 15997 if (ctrl) { 15998 return self.add(ctrl); 15999 } 16000 } 16001 16002 // Create sepearator 16003 if (name === "|" || name === "separator") { 16004 return self.createSeparator(); 16005 } 16006 16007 // Create control from button collection 16008 if (editor.buttons && (ctrl = editor.buttons[name])) { 16009 return self.createButton(name, ctrl); 16010 } 16011 16012 return self.add(ctrl); 16013 }, 16014 16015 createDropMenu : function(id, s, cc) { 16016 var t = this, ed = t.editor, c, bm, v, cls; 16017 16018 s = extend({ 16019 'class' : 'mceDropDown', 16020 constrain : ed.settings.constrain_menus 16021 }, s); 16022 16023 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 16024 if (v = ed.getParam('skin_variant')) 16025 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 16026 16027 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 16028 16029 id = t.prefix + id; 16030 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 16031 c = t.controls[id] = new cls(id, s); 16032 c.onAddItem.add(function(c, o) { 16033 var s = o.settings; 16034 16035 s.title = ed.getLang(s.title, s.title); 16036 16037 if (!s.onclick) { 16038 s.onclick = function(v) { 16039 if (s.cmd) 16040 ed.execCommand(s.cmd, s.ui || false, s.value); 16041 }; 16042 } 16043 }); 16044 16045 ed.onRemove.add(function() { 16046 c.destroy(); 16047 }); 16048 16049 // Fix for bug #1897785, #1898007 16050 if (tinymce.isIE) { 16051 c.onShowMenu.add(function() { 16052 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16053 ed.focus(); 16054 16055 bm = ed.selection.getBookmark(1); 16056 }); 16057 16058 c.onHideMenu.add(function() { 16059 if (bm) { 16060 ed.selection.moveToBookmark(bm); 16061 bm = 0; 16062 } 16063 }); 16064 } 16065 16066 return t.add(c); 16067 }, 16068 16069 createListBox : function(id, s, cc) { 16070 var t = this, ed = t.editor, cmd, c, cls; 16071 16072 if (t.get(id)) 16073 return null; 16074 16075 s.title = ed.translate(s.title); 16076 s.scope = s.scope || ed; 16077 16078 if (!s.onselect) { 16079 s.onselect = function(v) { 16080 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16081 }; 16082 } 16083 16084 s = extend({ 16085 title : s.title, 16086 'class' : 'mce_' + id, 16087 scope : s.scope, 16088 control_manager : t 16089 }, s); 16090 16091 id = t.prefix + id; 16092 16093 16094 function useNativeListForAccessibility(ed) { 16095 return ed.settings.use_accessible_selects && !tinymce.isGecko 16096 } 16097 16098 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 16099 c = new tinymce.ui.NativeListBox(id, s); 16100 else { 16101 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 16102 c = new cls(id, s, ed); 16103 } 16104 16105 t.controls[id] = c; 16106 16107 // Fix focus problem in Safari 16108 if (tinymce.isWebKit) { 16109 c.onPostRender.add(function(c, n) { 16110 // Store bookmark on mousedown 16111 Event.add(n, 'mousedown', function() { 16112 ed.bookmark = ed.selection.getBookmark(1); 16113 }); 16114 16115 // Restore on focus, since it might be lost 16116 Event.add(n, 'focus', function() { 16117 ed.selection.moveToBookmark(ed.bookmark); 16118 ed.bookmark = null; 16119 }); 16120 }); 16121 } 16122 16123 if (c.hideMenu) 16124 ed.onMouseDown.add(c.hideMenu, c); 16125 16126 return t.add(c); 16127 }, 16128 16129 createButton : function(id, s, cc) { 16130 var t = this, ed = t.editor, o, c, cls; 16131 16132 if (t.get(id)) 16133 return null; 16134 16135 s.title = ed.translate(s.title); 16136 s.label = ed.translate(s.label); 16137 s.scope = s.scope || ed; 16138 16139 if (!s.onclick && !s.menu_button) { 16140 s.onclick = function() { 16141 ed.execCommand(s.cmd, s.ui || false, s.value); 16142 }; 16143 } 16144 16145 s = extend({ 16146 title : s.title, 16147 'class' : 'mce_' + id, 16148 unavailable_prefix : ed.getLang('unavailable', ''), 16149 scope : s.scope, 16150 control_manager : t 16151 }, s); 16152 16153 id = t.prefix + id; 16154 16155 if (s.menu_button) { 16156 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 16157 c = new cls(id, s, ed); 16158 ed.onMouseDown.add(c.hideMenu, c); 16159 } else { 16160 cls = t._cls.button || tinymce.ui.Button; 16161 c = new cls(id, s, ed); 16162 } 16163 16164 return t.add(c); 16165 }, 16166 16167 createMenuButton : function(id, s, cc) { 16168 s = s || {}; 16169 s.menu_button = 1; 16170 16171 return this.createButton(id, s, cc); 16172 }, 16173 16174 createSplitButton : function(id, s, cc) { 16175 var t = this, ed = t.editor, cmd, c, cls; 16176 16177 if (t.get(id)) 16178 return null; 16179 16180 s.title = ed.translate(s.title); 16181 s.scope = s.scope || ed; 16182 16183 if (!s.onclick) { 16184 s.onclick = function(v) { 16185 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16186 }; 16187 } 16188 16189 if (!s.onselect) { 16190 s.onselect = function(v) { 16191 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16192 }; 16193 } 16194 16195 s = extend({ 16196 title : s.title, 16197 'class' : 'mce_' + id, 16198 scope : s.scope, 16199 control_manager : t 16200 }, s); 16201 16202 id = t.prefix + id; 16203 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 16204 c = t.add(new cls(id, s, ed)); 16205 ed.onMouseDown.add(c.hideMenu, c); 16206 16207 return c; 16208 }, 16209 16210 createColorSplitButton : function(id, s, cc) { 16211 var t = this, ed = t.editor, cmd, c, cls, bm; 16212 16213 if (t.get(id)) 16214 return null; 16215 16216 s.title = ed.translate(s.title); 16217 s.scope = s.scope || ed; 16218 16219 if (!s.onclick) { 16220 s.onclick = function(v) { 16221 if (tinymce.isIE) 16222 bm = ed.selection.getBookmark(1); 16223 16224 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16225 }; 16226 } 16227 16228 if (!s.onselect) { 16229 s.onselect = function(v) { 16230 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16231 }; 16232 } 16233 16234 s = extend({ 16235 title : s.title, 16236 'class' : 'mce_' + id, 16237 'menu_class' : ed.getParam('skin') + 'Skin', 16238 scope : s.scope, 16239 more_colors_title : ed.getLang('more_colors') 16240 }, s); 16241 16242 id = t.prefix + id; 16243 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 16244 c = new cls(id, s, ed); 16245 ed.onMouseDown.add(c.hideMenu, c); 16246 16247 // Remove the menu element when the editor is removed 16248 ed.onRemove.add(function() { 16249 c.destroy(); 16250 }); 16251 16252 // Fix for bug #1897785, #1898007 16253 if (tinymce.isIE) { 16254 c.onShowMenu.add(function() { 16255 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16256 ed.focus(); 16257 bm = ed.selection.getBookmark(1); 16258 }); 16259 16260 c.onHideMenu.add(function() { 16261 if (bm) { 16262 ed.selection.moveToBookmark(bm); 16263 bm = 0; 16264 } 16265 }); 16266 } 16267 16268 return t.add(c); 16269 }, 16270 16271 createToolbar : function(id, s, cc) { 16272 var c, t = this, cls; 16273 16274 id = t.prefix + id; 16275 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 16276 c = new cls(id, s, t.editor); 16277 16278 if (t.get(id)) 16279 return null; 16280 16281 return t.add(c); 16282 }, 16283 16284 createToolbarGroup : function(id, s, cc) { 16285 var c, t = this, cls; 16286 id = t.prefix + id; 16287 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 16288 c = new cls(id, s, t.editor); 16289 16290 if (t.get(id)) 16291 return null; 16292 16293 return t.add(c); 16294 }, 16295 16296 createSeparator : function(cc) { 16297 var cls = cc || this._cls.separator || tinymce.ui.Separator; 16298 16299 return new cls(); 16300 }, 16301 16302 setControlType : function(n, c) { 16303 return this._cls[n.toLowerCase()] = c; 16304 }, 16305 16306 destroy : function() { 16307 each(this.controls, function(c) { 16308 c.destroy(); 16309 }); 16310 16311 this.controls = null; 16312 } 16313 }); 16314 })(tinymce); 16315 (function(tinymce) { 16316 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 16317 16318 tinymce.create('tinymce.WindowManager', { 16319 WindowManager : function(ed) { 16320 var t = this; 16321 16322 t.editor = ed; 16323 t.onOpen = new Dispatcher(t); 16324 t.onClose = new Dispatcher(t); 16325 t.params = {}; 16326 t.features = {}; 16327 }, 16328 16329 open : function(s, p) { 16330 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 16331 16332 // Default some options 16333 s = s || {}; 16334 p = p || {}; 16335 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 16336 sh = isOpera ? vp.h : screen.height; 16337 s.name = s.name || 'mc_' + new Date().getTime(); 16338 s.width = parseInt(s.width || 320); 16339 s.height = parseInt(s.height || 240); 16340 s.resizable = true; 16341 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 16342 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 16343 p.inline = false; 16344 p.mce_width = s.width; 16345 p.mce_height = s.height; 16346 p.mce_auto_focus = s.auto_focus; 16347 16348 if (mo) { 16349 if (isIE) { 16350 s.center = true; 16351 s.help = false; 16352 s.dialogWidth = s.width + 'px'; 16353 s.dialogHeight = s.height + 'px'; 16354 s.scroll = s.scrollbars || false; 16355 } 16356 } 16357 16358 // Build features string 16359 each(s, function(v, k) { 16360 if (tinymce.is(v, 'boolean')) 16361 v = v ? 'yes' : 'no'; 16362 16363 if (!/^(name|url)$/.test(k)) { 16364 if (isIE && mo) 16365 f += (f ? ';' : '') + k + ':' + v; 16366 else 16367 f += (f ? ',' : '') + k + '=' + v; 16368 } 16369 }); 16370 16371 t.features = s; 16372 t.params = p; 16373 t.onOpen.dispatch(t, s, p); 16374 16375 u = s.url || s.file; 16376 u = tinymce._addVer(u); 16377 16378 try { 16379 if (isIE && mo) { 16380 w = 1; 16381 window.showModalDialog(u, window, f); 16382 } else 16383 w = window.open(u, s.name, f); 16384 } catch (ex) { 16385 // Ignore 16386 } 16387 16388 if (!w) 16389 alert(t.editor.getLang('popup_blocked')); 16390 }, 16391 16392 close : function(w) { 16393 w.close(); 16394 this.onClose.dispatch(this); 16395 }, 16396 16397 createInstance : function(cl, a, b, c, d, e) { 16398 var f = tinymce.resolve(cl); 16399 16400 return new f(a, b, c, d, e); 16401 }, 16402 16403 confirm : function(t, cb, s, w) { 16404 w = w || window; 16405 16406 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16407 }, 16408 16409 alert : function(tx, cb, s, w) { 16410 var t = this; 16411 16412 w = w || window; 16413 w.alert(t._decode(t.editor.getLang(tx, tx))); 16414 16415 if (cb) 16416 cb.call(s || t); 16417 }, 16418 16419 resizeBy : function(dw, dh, win) { 16420 win.resizeBy(dw, dh); 16421 }, 16422 16423 // Internal functions 16424 16425 _decode : function(s) { 16426 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16427 } 16428 }); 16429 }(tinymce)); 16430 (function(tinymce) { 16431 tinymce.Formatter = function(ed) { 16432 var formats = {}, 16433 each = tinymce.each, 16434 dom = ed.dom, 16435 selection = ed.selection, 16436 TreeWalker = tinymce.dom.TreeWalker, 16437 rangeUtils = new tinymce.dom.RangeUtils(dom), 16438 isValid = ed.schema.isValidChild, 16439 isArray = tinymce.isArray, 16440 isBlock = dom.isBlock, 16441 forcedRootBlock = ed.settings.forced_root_block, 16442 nodeIndex = dom.nodeIndex, 16443 INVISIBLE_CHAR = '\uFEFF', 16444 MCE_ATTR_RE = /^(src|href|style)$/, 16445 FALSE = false, 16446 TRUE = true, 16447 formatChangeData, 16448 undef, 16449 getContentEditable = dom.getContentEditable; 16450 16451 function isTextBlock(name) { 16452 if (name.nodeType) { 16453 name = name.nodeName; 16454 } 16455 16456 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 16457 } 16458 16459 function getParents(node, selector) { 16460 return dom.getParents(node, selector, dom.getRoot()); 16461 }; 16462 16463 function isCaretNode(node) { 16464 return node.nodeType === 1 && node.id === '_mce_caret'; 16465 }; 16466 16467 function defaultFormats() { 16468 register({ 16469 alignleft : [ 16470 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16471 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16472 ], 16473 16474 aligncenter : [ 16475 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16476 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16477 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16478 ], 16479 16480 alignright : [ 16481 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16482 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16483 ], 16484 16485 alignfull : [ 16486 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16487 ], 16488 16489 bold : [ 16490 {inline : 'strong', remove : 'all'}, 16491 {inline : 'span', styles : {fontWeight : 'bold'}}, 16492 {inline : 'b', remove : 'all'} 16493 ], 16494 16495 italic : [ 16496 {inline : 'em', remove : 'all'}, 16497 {inline : 'span', styles : {fontStyle : 'italic'}}, 16498 {inline : 'i', remove : 'all'} 16499 ], 16500 16501 underline : [ 16502 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16503 {inline : 'u', remove : 'all'} 16504 ], 16505 16506 strikethrough : [ 16507 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16508 {inline : 'strike', remove : 'all'} 16509 ], 16510 16511 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16512 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16513 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16514 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16515 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16516 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16517 subscript : {inline : 'sub'}, 16518 superscript : {inline : 'sup'}, 16519 16520 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16521 onmatch : function(node) { 16522 return true; 16523 }, 16524 16525 onformat : function(elm, fmt, vars) { 16526 each(vars, function(value, key) { 16527 dom.setAttrib(elm, key, value); 16528 }); 16529 } 16530 }, 16531 16532 removeformat : [ 16533 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16534 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16535 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16536 ] 16537 }); 16538 16539 // Register default block formats 16540 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16541 register(name, {block : name, remove : 'all'}); 16542 }); 16543 16544 // Register user defined formats 16545 register(ed.settings.formats); 16546 }; 16547 16548 function addKeyboardShortcuts() { 16549 // Add some inline shortcuts 16550 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16551 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16552 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16553 16554 // BlockFormat shortcuts keys 16555 for (var i = 1; i <= 6; i++) { 16556 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16557 } 16558 16559 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16560 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16561 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16562 }; 16563 16564 // Public functions 16565 16566 function get(name) { 16567 return name ? formats[name] : formats; 16568 }; 16569 16570 function register(name, format) { 16571 if (name) { 16572 if (typeof(name) !== 'string') { 16573 each(name, function(format, name) { 16574 register(name, format); 16575 }); 16576 } else { 16577 // Force format into array and add it to internal collection 16578 format = format.length ? format : [format]; 16579 16580 each(format, function(format) { 16581 // Set deep to false by default on selector formats this to avoid removing 16582 // alignment on images inside paragraphs when alignment is changed on paragraphs 16583 if (format.deep === undef) 16584 format.deep = !format.selector; 16585 16586 // Default to true 16587 if (format.split === undef) 16588 format.split = !format.selector || format.inline; 16589 16590 // Default to true 16591 if (format.remove === undef && format.selector && !format.inline) 16592 format.remove = 'none'; 16593 16594 // Mark format as a mixed format inline + block level 16595 if (format.selector && format.inline) { 16596 format.mixed = true; 16597 format.block_expand = true; 16598 } 16599 16600 // Split classes if needed 16601 if (typeof(format.classes) === 'string') 16602 format.classes = format.classes.split(/\s+/); 16603 }); 16604 16605 formats[name] = format; 16606 } 16607 } 16608 }; 16609 16610 var getTextDecoration = function(node) { 16611 var decoration; 16612 16613 ed.dom.getParent(node, function(n) { 16614 decoration = ed.dom.getStyle(n, 'text-decoration'); 16615 return decoration && decoration !== 'none'; 16616 }); 16617 16618 return decoration; 16619 }; 16620 16621 var processUnderlineAndColor = function(node) { 16622 var textDecoration; 16623 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16624 textDecoration = getTextDecoration(node.parentNode); 16625 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16626 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16627 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16628 ed.dom.setStyle(node, 'text-decoration', null); 16629 } 16630 } 16631 }; 16632 16633 function apply(name, vars, node) { 16634 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16635 16636 function setElementFormat(elm, fmt) { 16637 fmt = fmt || format; 16638 16639 if (elm) { 16640 if (fmt.onformat) { 16641 fmt.onformat(elm, fmt, vars, node); 16642 } 16643 16644 each(fmt.styles, function(value, name) { 16645 dom.setStyle(elm, name, replaceVars(value, vars)); 16646 }); 16647 16648 each(fmt.attributes, function(value, name) { 16649 dom.setAttrib(elm, name, replaceVars(value, vars)); 16650 }); 16651 16652 each(fmt.classes, function(value) { 16653 value = replaceVars(value, vars); 16654 16655 if (!dom.hasClass(elm, value)) 16656 dom.addClass(elm, value); 16657 }); 16658 } 16659 }; 16660 function adjustSelectionToVisibleSelection() { 16661 function findSelectionEnd(start, end) { 16662 var walker = new TreeWalker(end); 16663 for (node = walker.current(); node; node = walker.prev()) { 16664 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16665 return node; 16666 } 16667 } 16668 }; 16669 16670 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16671 // as this isn't visible to the user. 16672 var rng = ed.selection.getRng(); 16673 var start = rng.startContainer; 16674 var end = rng.endContainer; 16675 16676 if (start != end && rng.endOffset === 0) { 16677 var newEnd = findSelectionEnd(start, end); 16678 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16679 16680 rng.setEnd(newEnd, endOffset); 16681 } 16682 16683 return rng; 16684 } 16685 16686 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16687 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16688 16689 // find the index of the first child list. 16690 each(node.childNodes, function(n, index) { 16691 if (n.nodeName === "UL" || n.nodeName === "OL") { 16692 listIndex = index; 16693 list = n; 16694 return false; 16695 } 16696 }); 16697 16698 // get the index of the bookmarks 16699 each(node.childNodes, function(n, index) { 16700 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16701 if (n.id == bookmark.id + "_start") { 16702 startIndex = index; 16703 } else if (n.id == bookmark.id + "_end") { 16704 endIndex = index; 16705 } 16706 } 16707 }); 16708 16709 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16710 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16711 each(tinymce.grep(node.childNodes), process); 16712 return 0; 16713 } else { 16714 currentWrapElm = dom.clone(wrapElm, FALSE); 16715 16716 // create a list of the nodes on the same side of the list as the selection 16717 each(tinymce.grep(node.childNodes), function(n, index) { 16718 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16719 nodes.push(n); 16720 n.parentNode.removeChild(n); 16721 } 16722 }); 16723 16724 // insert the wrapping element either before or after the list. 16725 if (startIndex < listIndex) { 16726 node.insertBefore(currentWrapElm, list); 16727 } else if (startIndex > listIndex) { 16728 node.insertBefore(currentWrapElm, list.nextSibling); 16729 } 16730 16731 // add the new nodes to the list. 16732 newWrappers.push(currentWrapElm); 16733 16734 each(nodes, function(node) { 16735 currentWrapElm.appendChild(node); 16736 }); 16737 16738 return currentWrapElm; 16739 } 16740 }; 16741 16742 function applyRngStyle(rng, bookmark, node_specific) { 16743 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16744 16745 // Setup wrapper element 16746 wrapName = format.inline || format.block; 16747 wrapElm = dom.create(wrapName); 16748 setElementFormat(wrapElm); 16749 16750 rangeUtils.walk(rng, function(nodes) { 16751 var currentWrapElm; 16752 16753 function process(node) { 16754 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16755 16756 lastContentEditable = contentEditable; 16757 nodeName = node.nodeName.toLowerCase(); 16758 parentName = node.parentNode.nodeName.toLowerCase(); 16759 16760 // Node has a contentEditable value 16761 if (node.nodeType === 1 && getContentEditable(node)) { 16762 lastContentEditable = contentEditable; 16763 contentEditable = getContentEditable(node) === "true"; 16764 hasContentEditableState = true; // We don't want to wrap the container only it's children 16765 } 16766 16767 // Stop wrapping on br elements 16768 if (isEq(nodeName, 'br')) { 16769 currentWrapElm = 0; 16770 16771 // Remove any br elements when we wrap things 16772 if (format.block) 16773 dom.remove(node); 16774 16775 return; 16776 } 16777 16778 // If node is wrapper type 16779 if (format.wrapper && matchNode(node, name, vars)) { 16780 currentWrapElm = 0; 16781 return; 16782 } 16783 16784 // Can we rename the block 16785 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16786 node = dom.rename(node, wrapName); 16787 setElementFormat(node); 16788 newWrappers.push(node); 16789 currentWrapElm = 0; 16790 return; 16791 } 16792 16793 // Handle selector patterns 16794 if (format.selector) { 16795 // Look for matching formats 16796 each(formatList, function(format) { 16797 // Check collapsed state if it exists 16798 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16799 return; 16800 } 16801 16802 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16803 setElementFormat(node, format); 16804 found = true; 16805 } 16806 }); 16807 16808 // Continue processing if a selector match wasn't found and a inline element is defined 16809 if (!format.inline || found) { 16810 currentWrapElm = 0; 16811 return; 16812 } 16813 } 16814 16815 // Is it valid to wrap this item 16816 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16817 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) { 16818 // Start wrapping 16819 if (!currentWrapElm) { 16820 // Wrap the node 16821 currentWrapElm = dom.clone(wrapElm, FALSE); 16822 node.parentNode.insertBefore(currentWrapElm, node); 16823 newWrappers.push(currentWrapElm); 16824 } 16825 16826 currentWrapElm.appendChild(node); 16827 } else if (nodeName == 'li' && bookmark) { 16828 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16829 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16830 } else { 16831 // Start a new wrapper for possible children 16832 currentWrapElm = 0; 16833 16834 each(tinymce.grep(node.childNodes), process); 16835 16836 if (hasContentEditableState) { 16837 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16838 } 16839 16840 // End the last wrapper 16841 currentWrapElm = 0; 16842 } 16843 }; 16844 16845 // Process siblings from range 16846 each(nodes, process); 16847 }); 16848 16849 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16850 if (format.wrap_links === false) { 16851 each(newWrappers, function(node) { 16852 function process(node) { 16853 var i, currentWrapElm, children; 16854 16855 if (node.nodeName === 'A') { 16856 currentWrapElm = dom.clone(wrapElm, FALSE); 16857 newWrappers.push(currentWrapElm); 16858 16859 children = tinymce.grep(node.childNodes); 16860 for (i = 0; i < children.length; i++) 16861 currentWrapElm.appendChild(children[i]); 16862 16863 node.appendChild(currentWrapElm); 16864 } 16865 16866 each(tinymce.grep(node.childNodes), process); 16867 }; 16868 16869 process(node); 16870 }); 16871 } 16872 16873 // Cleanup 16874 16875 each(newWrappers, function(node) { 16876 var childCount; 16877 16878 function getChildCount(node) { 16879 var count = 0; 16880 16881 each(node.childNodes, function(node) { 16882 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16883 count++; 16884 }); 16885 16886 return count; 16887 }; 16888 16889 function mergeStyles(node) { 16890 var child, clone; 16891 16892 each(node.childNodes, function(node) { 16893 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16894 child = node; 16895 return FALSE; // break loop 16896 } 16897 }); 16898 16899 // If child was found and of the same type as the current node 16900 if (child && matchName(child, format)) { 16901 clone = dom.clone(child, FALSE); 16902 setElementFormat(clone); 16903 16904 dom.replace(clone, node, TRUE); 16905 dom.remove(child, 1); 16906 } 16907 16908 return clone || node; 16909 }; 16910 16911 childCount = getChildCount(node); 16912 16913 // Remove empty nodes but only if there is multiple wrappers and they are not block 16914 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16915 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16916 dom.remove(node, 1); 16917 return; 16918 } 16919 16920 if (format.inline || format.wrapper) { 16921 // Merges the current node with it's children of similar type to reduce the number of elements 16922 if (!format.exact && childCount === 1) 16923 node = mergeStyles(node); 16924 16925 // Remove/merge children 16926 each(formatList, function(format) { 16927 // Merge all children of similar type will move styles from child to parent 16928 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16929 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16930 each(dom.select(format.inline, node), function(child) { 16931 var parent; 16932 16933 // When wrap_links is set to false we don't want 16934 // to remove the format on children within links 16935 if (format.wrap_links === false) { 16936 parent = child.parentNode; 16937 16938 do { 16939 if (parent.nodeName === 'A') 16940 return; 16941 } while (parent = parent.parentNode); 16942 } 16943 16944 removeFormat(format, vars, child, format.exact ? child : null); 16945 }); 16946 }); 16947 16948 // Remove child if direct parent is of same type 16949 if (matchNode(node.parentNode, name, vars)) { 16950 dom.remove(node, 1); 16951 node = 0; 16952 return TRUE; 16953 } 16954 16955 // Look for parent with similar style format 16956 if (format.merge_with_parents) { 16957 dom.getParent(node.parentNode, function(parent) { 16958 if (matchNode(parent, name, vars)) { 16959 dom.remove(node, 1); 16960 node = 0; 16961 return TRUE; 16962 } 16963 }); 16964 } 16965 16966 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16967 if (node && format.merge_siblings !== false) { 16968 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16969 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16970 } 16971 } 16972 }); 16973 }; 16974 16975 if (format) { 16976 if (node) { 16977 if (node.nodeType) { 16978 rng = dom.createRng(); 16979 rng.setStartBefore(node); 16980 rng.setEndAfter(node); 16981 applyRngStyle(expandRng(rng, formatList), null, true); 16982 } else { 16983 applyRngStyle(node, null, true); 16984 } 16985 } else { 16986 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16987 // Obtain selection node before selection is unselected by applyRngStyle() 16988 var curSelNode = ed.selection.getNode(); 16989 16990 // 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 16991 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16992 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16993 apply(formatList[0].defaultBlock); 16994 } 16995 16996 // Apply formatting to selection 16997 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16998 bookmark = selection.getBookmark(); 16999 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 17000 17001 // Colored nodes should be underlined so that the color of the underline matches the text color. 17002 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 17003 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 17004 processUnderlineAndColor(curSelNode); 17005 } 17006 17007 selection.moveToBookmark(bookmark); 17008 moveStart(selection.getRng(TRUE)); 17009 ed.nodeChanged(); 17010 } else 17011 performCaretAction('apply', name, vars); 17012 } 17013 } 17014 }; 17015 17016 function remove(name, vars, node) { 17017 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 17018 17019 // Merges the styles for each node 17020 function process(node) { 17021 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 17022 17023 // Skip on text nodes as they have neither format to remove nor children 17024 if (node.nodeType === 3) { 17025 return; 17026 } 17027 17028 // Node has a contentEditable value 17029 if (node.nodeType === 1 && getContentEditable(node)) { 17030 lastContentEditable = contentEditable; 17031 contentEditable = getContentEditable(node) === "true"; 17032 hasContentEditableState = true; // We don't want to wrap the container only it's children 17033 } 17034 17035 // Grab the children first since the nodelist might be changed 17036 children = tinymce.grep(node.childNodes); 17037 17038 // Process current node 17039 if (contentEditable && !hasContentEditableState) { 17040 for (i = 0, l = formatList.length; i < l; i++) { 17041 if (removeFormat(formatList[i], vars, node, node)) 17042 break; 17043 } 17044 } 17045 17046 // Process the children 17047 if (format.deep) { 17048 if (children.length) { 17049 for (i = 0, l = children.length; i < l; i++) 17050 process(children[i]); 17051 17052 if (hasContentEditableState) { 17053 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 17054 } 17055 } 17056 } 17057 }; 17058 17059 function findFormatRoot(container) { 17060 var formatRoot; 17061 17062 // Find format root 17063 each(getParents(container.parentNode).reverse(), function(parent) { 17064 var format; 17065 17066 // Find format root element 17067 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 17068 // Is the node matching the format we are looking for 17069 format = matchNode(parent, name, vars); 17070 if (format && format.split !== false) 17071 formatRoot = parent; 17072 } 17073 }); 17074 17075 return formatRoot; 17076 }; 17077 17078 function wrapAndSplit(format_root, container, target, split) { 17079 var parent, clone, lastClone, firstClone, i, formatRootParent; 17080 17081 // Format root found then clone formats and split it 17082 if (format_root) { 17083 formatRootParent = format_root.parentNode; 17084 17085 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 17086 clone = dom.clone(parent, FALSE); 17087 17088 for (i = 0; i < formatList.length; i++) { 17089 if (removeFormat(formatList[i], vars, clone, clone)) { 17090 clone = 0; 17091 break; 17092 } 17093 } 17094 17095 // Build wrapper node 17096 if (clone) { 17097 if (lastClone) 17098 clone.appendChild(lastClone); 17099 17100 if (!firstClone) 17101 firstClone = clone; 17102 17103 lastClone = clone; 17104 } 17105 } 17106 17107 // Never split block elements if the format is mixed 17108 if (split && (!format.mixed || !isBlock(format_root))) 17109 container = dom.split(format_root, container); 17110 17111 // Wrap container in cloned formats 17112 if (lastClone) { 17113 target.parentNode.insertBefore(lastClone, target); 17114 firstClone.appendChild(target); 17115 } 17116 } 17117 17118 return container; 17119 }; 17120 17121 function splitToFormatRoot(container) { 17122 return wrapAndSplit(findFormatRoot(container), container, container, true); 17123 }; 17124 17125 function unwrap(start) { 17126 var node = dom.get(start ? '_start' : '_end'), 17127 out = node[start ? 'firstChild' : 'lastChild']; 17128 17129 // If the end is placed within the start the result will be removed 17130 // So this checks if the out node is a bookmark node if it is it 17131 // checks for another more suitable node 17132 if (isBookmarkNode(out)) 17133 out = out[start ? 'firstChild' : 'lastChild']; 17134 17135 dom.remove(node, true); 17136 17137 return out; 17138 }; 17139 17140 function removeRngStyle(rng) { 17141 var startContainer, endContainer, node; 17142 17143 rng = expandRng(rng, formatList, TRUE); 17144 17145 if (format.split) { 17146 startContainer = getContainer(rng, TRUE); 17147 endContainer = getContainer(rng); 17148 17149 if (startContainer != endContainer) { 17150 // 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 17151 // This will happen if you tripple click a table cell and use remove formatting 17152 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 17153 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 17154 } 17155 17156 // Wrap start/end nodes in span element since these might be cloned/moved 17157 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 17158 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 17159 17160 // Split start/end 17161 splitToFormatRoot(startContainer); 17162 splitToFormatRoot(endContainer); 17163 17164 // Unwrap start/end to get real elements again 17165 startContainer = unwrap(TRUE); 17166 endContainer = unwrap(); 17167 } else 17168 startContainer = endContainer = splitToFormatRoot(startContainer); 17169 17170 // Update range positions since they might have changed after the split operations 17171 rng.startContainer = startContainer.parentNode; 17172 rng.startOffset = nodeIndex(startContainer); 17173 rng.endContainer = endContainer.parentNode; 17174 rng.endOffset = nodeIndex(endContainer) + 1; 17175 } 17176 17177 // Remove items between start/end 17178 rangeUtils.walk(rng, function(nodes) { 17179 each(nodes, function(node) { 17180 process(node); 17181 17182 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 17183 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 17184 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 17185 } 17186 }); 17187 }); 17188 }; 17189 17190 // Handle node 17191 if (node) { 17192 if (node.nodeType) { 17193 rng = dom.createRng(); 17194 rng.setStartBefore(node); 17195 rng.setEndAfter(node); 17196 removeRngStyle(rng); 17197 } else { 17198 removeRngStyle(node); 17199 } 17200 17201 return; 17202 } 17203 17204 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 17205 bookmark = selection.getBookmark(); 17206 removeRngStyle(selection.getRng(TRUE)); 17207 selection.moveToBookmark(bookmark); 17208 17209 // 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 17210 if (format.inline && match(name, vars, selection.getStart())) { 17211 moveStart(selection.getRng(true)); 17212 } 17213 17214 ed.nodeChanged(); 17215 } else 17216 performCaretAction('remove', name, vars); 17217 }; 17218 17219 function toggle(name, vars, node) { 17220 var fmt = get(name); 17221 17222 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 17223 remove(name, vars, node); 17224 else 17225 apply(name, vars, node); 17226 }; 17227 17228 function matchNode(node, name, vars, similar) { 17229 var formatList = get(name), format, i, classes; 17230 17231 function matchItems(node, format, item_name) { 17232 var key, value, items = format[item_name], i; 17233 17234 // Custom match 17235 if (format.onmatch) { 17236 return format.onmatch(node, format, item_name); 17237 } 17238 17239 // Check all items 17240 if (items) { 17241 // Non indexed object 17242 if (items.length === undef) { 17243 for (key in items) { 17244 if (items.hasOwnProperty(key)) { 17245 if (item_name === 'attributes') 17246 value = dom.getAttrib(node, key); 17247 else 17248 value = getStyle(node, key); 17249 17250 if (similar && !value && !format.exact) 17251 return; 17252 17253 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 17254 return; 17255 } 17256 } 17257 } else { 17258 // Only one match needed for indexed arrays 17259 for (i = 0; i < items.length; i++) { 17260 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 17261 return format; 17262 } 17263 } 17264 } 17265 17266 return format; 17267 }; 17268 17269 if (formatList && node) { 17270 // Check each format in list 17271 for (i = 0; i < formatList.length; i++) { 17272 format = formatList[i]; 17273 17274 // Name name, attributes, styles and classes 17275 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 17276 // Match classes 17277 if (classes = format.classes) { 17278 for (i = 0; i < classes.length; i++) { 17279 if (!dom.hasClass(node, classes[i])) 17280 return; 17281 } 17282 } 17283 17284 return format; 17285 } 17286 } 17287 } 17288 }; 17289 17290 function match(name, vars, node) { 17291 var startNode; 17292 17293 function matchParents(node) { 17294 // Find first node with similar format settings 17295 node = dom.getParent(node, function(node) { 17296 return !!matchNode(node, name, vars, true); 17297 }); 17298 17299 // Do an exact check on the similar format element 17300 return matchNode(node, name, vars); 17301 }; 17302 17303 // Check specified node 17304 if (node) 17305 return matchParents(node); 17306 17307 // Check selected node 17308 node = selection.getNode(); 17309 if (matchParents(node)) 17310 return TRUE; 17311 17312 // Check start node if it's different 17313 startNode = selection.getStart(); 17314 if (startNode != node) { 17315 if (matchParents(startNode)) 17316 return TRUE; 17317 } 17318 17319 return FALSE; 17320 }; 17321 17322 function matchAll(names, vars) { 17323 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 17324 17325 // Check start of selection for formats 17326 startElement = selection.getStart(); 17327 dom.getParent(startElement, function(node) { 17328 var i, name; 17329 17330 for (i = 0; i < names.length; i++) { 17331 name = names[i]; 17332 17333 if (!checkedMap[name] && matchNode(node, name, vars)) { 17334 checkedMap[name] = true; 17335 matchedFormatNames.push(name); 17336 } 17337 } 17338 }, dom.getRoot()); 17339 17340 return matchedFormatNames; 17341 }; 17342 17343 function canApply(name) { 17344 var formatList = get(name), startNode, parents, i, x, selector; 17345 17346 if (formatList) { 17347 startNode = selection.getStart(); 17348 parents = getParents(startNode); 17349 17350 for (x = formatList.length - 1; x >= 0; x--) { 17351 selector = formatList[x].selector; 17352 17353 // Format is not selector based, then always return TRUE 17354 if (!selector) 17355 return TRUE; 17356 17357 for (i = parents.length - 1; i >= 0; i--) { 17358 if (dom.is(parents[i], selector)) 17359 return TRUE; 17360 } 17361 } 17362 } 17363 17364 return FALSE; 17365 }; 17366 17367 function formatChanged(formats, callback, similar) { 17368 var currentFormats; 17369 17370 // Setup format node change logic 17371 if (!formatChangeData) { 17372 formatChangeData = {}; 17373 currentFormats = {}; 17374 17375 ed.onNodeChange.addToTop(function(ed, cm, node) { 17376 var parents = getParents(node), matchedFormats = {}; 17377 17378 // Check for new formats 17379 each(formatChangeData, function(callbacks, format) { 17380 each(parents, function(node) { 17381 if (matchNode(node, format, {}, callbacks.similar)) { 17382 if (!currentFormats[format]) { 17383 // Execute callbacks 17384 each(callbacks, function(callback) { 17385 callback(true, {node: node, format: format, parents: parents}); 17386 }); 17387 17388 currentFormats[format] = callbacks; 17389 } 17390 17391 matchedFormats[format] = callbacks; 17392 return false; 17393 } 17394 }); 17395 }); 17396 17397 // Check if current formats still match 17398 each(currentFormats, function(callbacks, format) { 17399 if (!matchedFormats[format]) { 17400 delete currentFormats[format]; 17401 17402 each(callbacks, function(callback) { 17403 callback(false, {node: node, format: format, parents: parents}); 17404 }); 17405 } 17406 }); 17407 }); 17408 } 17409 17410 // Add format listeners 17411 each(formats.split(','), function(format) { 17412 if (!formatChangeData[format]) { 17413 formatChangeData[format] = []; 17414 formatChangeData[format].similar = similar; 17415 } 17416 17417 formatChangeData[format].push(callback); 17418 }); 17419 17420 return this; 17421 }; 17422 17423 // Expose to public 17424 tinymce.extend(this, { 17425 get : get, 17426 register : register, 17427 apply : apply, 17428 remove : remove, 17429 toggle : toggle, 17430 match : match, 17431 matchAll : matchAll, 17432 matchNode : matchNode, 17433 canApply : canApply, 17434 formatChanged: formatChanged 17435 }); 17436 17437 // Initialize 17438 defaultFormats(); 17439 addKeyboardShortcuts(); 17440 17441 // Private functions 17442 17443 function matchName(node, format) { 17444 // Check for inline match 17445 if (isEq(node, format.inline)) 17446 return TRUE; 17447 17448 // Check for block match 17449 if (isEq(node, format.block)) 17450 return TRUE; 17451 17452 // Check for selector match 17453 if (format.selector) 17454 return dom.is(node, format.selector); 17455 }; 17456 17457 function isEq(str1, str2) { 17458 str1 = str1 || ''; 17459 str2 = str2 || ''; 17460 17461 str1 = '' + (str1.nodeName || str1); 17462 str2 = '' + (str2.nodeName || str2); 17463 17464 return str1.toLowerCase() == str2.toLowerCase(); 17465 }; 17466 17467 function getStyle(node, name) { 17468 var styleVal = dom.getStyle(node, name); 17469 17470 // Force the format to hex 17471 if (name == 'color' || name == 'backgroundColor') 17472 styleVal = dom.toHex(styleVal); 17473 17474 // Opera will return bold as 700 17475 if (name == 'fontWeight' && styleVal == 700) 17476 styleVal = 'bold'; 17477 17478 return '' + styleVal; 17479 }; 17480 17481 function replaceVars(value, vars) { 17482 if (typeof(value) != "string") 17483 value = value(vars); 17484 else if (vars) { 17485 value = value.replace(/%(\w+)/g, function(str, name) { 17486 return vars[name] || str; 17487 }); 17488 } 17489 17490 return value; 17491 }; 17492 17493 function isWhiteSpaceNode(node) { 17494 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17495 }; 17496 17497 function wrap(node, name, attrs) { 17498 var wrapper = dom.create(name, attrs); 17499 17500 node.parentNode.insertBefore(wrapper, node); 17501 wrapper.appendChild(node); 17502 17503 return wrapper; 17504 }; 17505 17506 function expandRng(rng, format, remove) { 17507 var sibling, lastIdx, leaf, endPoint, 17508 startContainer = rng.startContainer, 17509 startOffset = rng.startOffset, 17510 endContainer = rng.endContainer, 17511 endOffset = rng.endOffset; 17512 17513 // This function walks up the tree if there is no siblings before/after the node 17514 function findParentContainer(start) { 17515 var container, parent, child, sibling, siblingName, root; 17516 17517 container = parent = start ? startContainer : endContainer; 17518 siblingName = start ? 'previousSibling' : 'nextSibling'; 17519 root = dom.getRoot(); 17520 17521 function isBogusBr(node) { 17522 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 17523 }; 17524 17525 // If it's a text node and the offset is inside the text 17526 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17527 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17528 return container; 17529 } 17530 } 17531 17532 for (;;) { 17533 // Stop expanding on block elements 17534 if (!format[0].block_expand && isBlock(parent)) 17535 return parent; 17536 17537 // Walk left/right 17538 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17539 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 17540 return parent; 17541 } 17542 } 17543 17544 // Check if we can move up are we at root level or body level 17545 if (parent.parentNode == root) { 17546 container = parent; 17547 break; 17548 } 17549 17550 parent = parent.parentNode; 17551 } 17552 17553 return container; 17554 }; 17555 17556 // This function walks down the tree to find the leaf at the selection. 17557 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17558 function findLeaf(node, offset) { 17559 if (offset === undef) 17560 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17561 while (node && node.hasChildNodes()) { 17562 node = node.childNodes[offset]; 17563 if (node) 17564 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17565 } 17566 return { node: node, offset: offset }; 17567 } 17568 17569 // If index based start position then resolve it 17570 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17571 lastIdx = startContainer.childNodes.length - 1; 17572 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17573 17574 if (startContainer.nodeType == 3) 17575 startOffset = 0; 17576 } 17577 17578 // If index based end position then resolve it 17579 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17580 lastIdx = endContainer.childNodes.length - 1; 17581 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17582 17583 if (endContainer.nodeType == 3) 17584 endOffset = endContainer.nodeValue.length; 17585 } 17586 17587 // Expands the node to the closes contentEditable false element if it exists 17588 function findParentContentEditable(node) { 17589 var parent = node; 17590 17591 while (parent) { 17592 if (parent.nodeType === 1 && getContentEditable(parent)) { 17593 return getContentEditable(parent) === "false" ? parent : node; 17594 } 17595 17596 parent = parent.parentNode; 17597 } 17598 17599 return node; 17600 }; 17601 17602 function findWordEndPoint(container, offset, start) { 17603 var walker, node, pos, lastTextNode; 17604 17605 function findSpace(node, offset) { 17606 var pos, pos2, str = node.nodeValue; 17607 17608 if (typeof(offset) == "undefined") { 17609 offset = start ? str.length : 0; 17610 } 17611 17612 if (start) { 17613 pos = str.lastIndexOf(' ', offset); 17614 pos2 = str.lastIndexOf('\u00a0', offset); 17615 pos = pos > pos2 ? pos : pos2; 17616 17617 // Include the space on remove to avoid tag soup 17618 if (pos !== -1 && !remove) { 17619 pos++; 17620 } 17621 } else { 17622 pos = str.indexOf(' ', offset); 17623 pos2 = str.indexOf('\u00a0', offset); 17624 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17625 } 17626 17627 return pos; 17628 }; 17629 17630 if (container.nodeType === 3) { 17631 pos = findSpace(container, offset); 17632 17633 if (pos !== -1) { 17634 return {container : container, offset : pos}; 17635 } 17636 17637 lastTextNode = container; 17638 } 17639 17640 // Walk the nodes inside the block 17641 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17642 while (node = walker[start ? 'prev' : 'next']()) { 17643 if (node.nodeType === 3) { 17644 lastTextNode = node; 17645 pos = findSpace(node); 17646 17647 if (pos !== -1) { 17648 return {container : node, offset : pos}; 17649 } 17650 } else if (isBlock(node)) { 17651 break; 17652 } 17653 } 17654 17655 if (lastTextNode) { 17656 if (start) { 17657 offset = 0; 17658 } else { 17659 offset = lastTextNode.length; 17660 } 17661 17662 return {container: lastTextNode, offset: offset}; 17663 } 17664 }; 17665 17666 function findSelectorEndPoint(container, sibling_name) { 17667 var parents, i, y, curFormat; 17668 17669 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17670 container = container[sibling_name]; 17671 17672 parents = getParents(container); 17673 for (i = 0; i < parents.length; i++) { 17674 for (y = 0; y < format.length; y++) { 17675 curFormat = format[y]; 17676 17677 // If collapsed state is set then skip formats that doesn't match that 17678 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17679 continue; 17680 17681 if (dom.is(parents[i], curFormat.selector)) 17682 return parents[i]; 17683 } 17684 } 17685 17686 return container; 17687 }; 17688 17689 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17690 var node; 17691 17692 // Expand to block of similar type 17693 if (!format[0].wrapper) 17694 node = dom.getParent(container, format[0].block); 17695 17696 // Expand to first wrappable block element or any block element 17697 if (!node) 17698 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock); 17699 17700 // Exclude inner lists from wrapping 17701 if (node && format[0].wrapper) 17702 node = getParents(node, 'ul,ol').reverse()[0] || node; 17703 17704 // Didn't find a block element look for first/last wrappable element 17705 if (!node) { 17706 node = container; 17707 17708 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17709 node = node[sibling_name]; 17710 17711 // Break on BR but include it will be removed later on 17712 // we can't remove it now since we need to check if it can be wrapped 17713 if (isEq(node, 'br')) 17714 break; 17715 } 17716 } 17717 17718 return node || container; 17719 }; 17720 17721 // Expand to closest contentEditable element 17722 startContainer = findParentContentEditable(startContainer); 17723 endContainer = findParentContentEditable(endContainer); 17724 17725 // Exclude bookmark nodes if possible 17726 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17727 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17728 startContainer = startContainer.nextSibling || startContainer; 17729 17730 if (startContainer.nodeType == 3) 17731 startOffset = 0; 17732 } 17733 17734 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17735 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17736 endContainer = endContainer.previousSibling || endContainer; 17737 17738 if (endContainer.nodeType == 3) 17739 endOffset = endContainer.length; 17740 } 17741 17742 if (format[0].inline) { 17743 if (rng.collapsed) { 17744 // Expand left to closest word boundery 17745 endPoint = findWordEndPoint(startContainer, startOffset, true); 17746 if (endPoint) { 17747 startContainer = endPoint.container; 17748 startOffset = endPoint.offset; 17749 } 17750 17751 // Expand right to closest word boundery 17752 endPoint = findWordEndPoint(endContainer, endOffset); 17753 if (endPoint) { 17754 endContainer = endPoint.container; 17755 endOffset = endPoint.offset; 17756 } 17757 } 17758 17759 // Avoid applying formatting to a trailing space. 17760 leaf = findLeaf(endContainer, endOffset); 17761 if (leaf.node) { 17762 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17763 leaf = findLeaf(leaf.node.previousSibling); 17764 17765 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17766 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17767 17768 if (leaf.offset > 1) { 17769 endContainer = leaf.node; 17770 endContainer.splitText(leaf.offset - 1); 17771 } 17772 } 17773 } 17774 } 17775 17776 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17777 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17778 // This will reduce the number of wrapper elements that needs to be created 17779 // Move start point up the tree 17780 if (format[0].inline || format[0].block_expand) { 17781 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17782 startContainer = findParentContainer(true); 17783 } 17784 17785 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17786 endContainer = findParentContainer(); 17787 } 17788 } 17789 17790 // Expand start/end container to matching selector 17791 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17792 // Find new startContainer/endContainer if there is better one 17793 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17794 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17795 } 17796 17797 // Expand start/end container to matching block element or text node 17798 if (format[0].block || format[0].selector) { 17799 // Find new startContainer/endContainer if there is better one 17800 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17801 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17802 17803 // Non block element then try to expand up the leaf 17804 if (format[0].block) { 17805 if (!isBlock(startContainer)) 17806 startContainer = findParentContainer(true); 17807 17808 if (!isBlock(endContainer)) 17809 endContainer = findParentContainer(); 17810 } 17811 } 17812 17813 // Setup index for startContainer 17814 if (startContainer.nodeType == 1) { 17815 startOffset = nodeIndex(startContainer); 17816 startContainer = startContainer.parentNode; 17817 } 17818 17819 // Setup index for endContainer 17820 if (endContainer.nodeType == 1) { 17821 endOffset = nodeIndex(endContainer) + 1; 17822 endContainer = endContainer.parentNode; 17823 } 17824 17825 // Return new range like object 17826 return { 17827 startContainer : startContainer, 17828 startOffset : startOffset, 17829 endContainer : endContainer, 17830 endOffset : endOffset 17831 }; 17832 } 17833 17834 function removeFormat(format, vars, node, compare_node) { 17835 var i, attrs, stylesModified; 17836 17837 // Check if node matches format 17838 if (!matchName(node, format)) 17839 return FALSE; 17840 17841 // Should we compare with format attribs and styles 17842 if (format.remove != 'all') { 17843 // Remove styles 17844 each(format.styles, function(value, name) { 17845 value = replaceVars(value, vars); 17846 17847 // Indexed array 17848 if (typeof(name) === 'number') { 17849 name = value; 17850 compare_node = 0; 17851 } 17852 17853 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17854 dom.setStyle(node, name, ''); 17855 17856 stylesModified = 1; 17857 }); 17858 17859 // Remove style attribute if it's empty 17860 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17861 node.removeAttribute('style'); 17862 node.removeAttribute('data-mce-style'); 17863 } 17864 17865 // Remove attributes 17866 each(format.attributes, function(value, name) { 17867 var valueOut; 17868 17869 value = replaceVars(value, vars); 17870 17871 // Indexed array 17872 if (typeof(name) === 'number') { 17873 name = value; 17874 compare_node = 0; 17875 } 17876 17877 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17878 // Keep internal classes 17879 if (name == 'class') { 17880 value = dom.getAttrib(node, name); 17881 if (value) { 17882 // Build new class value where everything is removed except the internal prefixed classes 17883 valueOut = ''; 17884 each(value.split(/\s+/), function(cls) { 17885 if (/mce\w+/.test(cls)) 17886 valueOut += (valueOut ? ' ' : '') + cls; 17887 }); 17888 17889 // We got some internal classes left 17890 if (valueOut) { 17891 dom.setAttrib(node, name, valueOut); 17892 return; 17893 } 17894 } 17895 } 17896 17897 // IE6 has a bug where the attribute doesn't get removed correctly 17898 if (name == "class") 17899 node.removeAttribute('className'); 17900 17901 // Remove mce prefixed attributes 17902 if (MCE_ATTR_RE.test(name)) 17903 node.removeAttribute('data-mce-' + name); 17904 17905 node.removeAttribute(name); 17906 } 17907 }); 17908 17909 // Remove classes 17910 each(format.classes, function(value) { 17911 value = replaceVars(value, vars); 17912 17913 if (!compare_node || dom.hasClass(compare_node, value)) 17914 dom.removeClass(node, value); 17915 }); 17916 17917 // Check for non internal attributes 17918 attrs = dom.getAttribs(node); 17919 for (i = 0; i < attrs.length; i++) { 17920 if (attrs[i].nodeName.indexOf('_') !== 0) 17921 return FALSE; 17922 } 17923 } 17924 17925 // Remove the inline child if it's empty for example <b> or <span> 17926 if (format.remove != 'none') { 17927 removeNode(node, format); 17928 return TRUE; 17929 } 17930 }; 17931 17932 function removeNode(node, format) { 17933 var parentNode = node.parentNode, rootBlockElm; 17934 17935 function find(node, next, inc) { 17936 node = getNonWhiteSpaceSibling(node, next, inc); 17937 17938 return !node || (node.nodeName == 'BR' || isBlock(node)); 17939 }; 17940 17941 if (format.block) { 17942 if (!forcedRootBlock) { 17943 // Append BR elements if needed before we remove the block 17944 if (isBlock(node) && !isBlock(parentNode)) { 17945 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17946 node.insertBefore(dom.create('br'), node.firstChild); 17947 17948 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17949 node.appendChild(dom.create('br')); 17950 } 17951 } else { 17952 // Wrap the block in a forcedRootBlock if we are at the root of document 17953 if (parentNode == dom.getRoot()) { 17954 if (!format.list_block || !isEq(node, format.list_block)) { 17955 each(tinymce.grep(node.childNodes), function(node) { 17956 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17957 if (!rootBlockElm) 17958 rootBlockElm = wrap(node, forcedRootBlock); 17959 else 17960 rootBlockElm.appendChild(node); 17961 } else 17962 rootBlockElm = 0; 17963 }); 17964 } 17965 } 17966 } 17967 } 17968 17969 // Never remove nodes that isn't the specified inline element if a selector is specified too 17970 if (format.selector && format.inline && !isEq(format.inline, node)) 17971 return; 17972 17973 dom.remove(node, 1); 17974 }; 17975 17976 function getNonWhiteSpaceSibling(node, next, inc) { 17977 if (node) { 17978 next = next ? 'nextSibling' : 'previousSibling'; 17979 17980 for (node = inc ? node : node[next]; node; node = node[next]) { 17981 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17982 return node; 17983 } 17984 } 17985 }; 17986 17987 function isBookmarkNode(node) { 17988 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17989 }; 17990 17991 function mergeSiblings(prev, next) { 17992 var marker, sibling, tmpSibling; 17993 17994 function compareElements(node1, node2) { 17995 // Not the same name 17996 if (node1.nodeName != node2.nodeName) 17997 return FALSE; 17998 17999 function getAttribs(node) { 18000 var attribs = {}; 18001 18002 each(dom.getAttribs(node), function(attr) { 18003 var name = attr.nodeName.toLowerCase(); 18004 18005 // Don't compare internal attributes or style 18006 if (name.indexOf('_') !== 0 && name !== 'style') 18007 attribs[name] = dom.getAttrib(node, name); 18008 }); 18009 18010 return attribs; 18011 }; 18012 18013 function compareObjects(obj1, obj2) { 18014 var value, name; 18015 18016 for (name in obj1) { 18017 // Obj1 has item obj2 doesn't have 18018 if (obj1.hasOwnProperty(name)) { 18019 value = obj2[name]; 18020 18021 // Obj2 doesn't have obj1 item 18022 if (value === undef) 18023 return FALSE; 18024 18025 // Obj2 item has a different value 18026 if (obj1[name] != value) 18027 return FALSE; 18028 18029 // Delete similar value 18030 delete obj2[name]; 18031 } 18032 } 18033 18034 // Check if obj 2 has something obj 1 doesn't have 18035 for (name in obj2) { 18036 // Obj2 has item obj1 doesn't have 18037 if (obj2.hasOwnProperty(name)) 18038 return FALSE; 18039 } 18040 18041 return TRUE; 18042 }; 18043 18044 // Attribs are not the same 18045 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 18046 return FALSE; 18047 18048 // Styles are not the same 18049 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 18050 return FALSE; 18051 18052 return TRUE; 18053 }; 18054 18055 function findElementSibling(node, sibling_name) { 18056 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 18057 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 18058 return node; 18059 18060 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 18061 return sibling; 18062 } 18063 18064 return node; 18065 }; 18066 18067 // Check if next/prev exists and that they are elements 18068 if (prev && next) { 18069 // If previous sibling is empty then jump over it 18070 prev = findElementSibling(prev, 'previousSibling'); 18071 next = findElementSibling(next, 'nextSibling'); 18072 18073 // Compare next and previous nodes 18074 if (compareElements(prev, next)) { 18075 // Append nodes between 18076 for (sibling = prev.nextSibling; sibling && sibling != next;) { 18077 tmpSibling = sibling; 18078 sibling = sibling.nextSibling; 18079 prev.appendChild(tmpSibling); 18080 } 18081 18082 // Remove next node 18083 dom.remove(next); 18084 18085 // Move children into prev node 18086 each(tinymce.grep(next.childNodes), function(node) { 18087 prev.appendChild(node); 18088 }); 18089 18090 return prev; 18091 } 18092 } 18093 18094 return next; 18095 }; 18096 18097 function getContainer(rng, start) { 18098 var container, offset, lastIdx, walker; 18099 18100 container = rng[start ? 'startContainer' : 'endContainer']; 18101 offset = rng[start ? 'startOffset' : 'endOffset']; 18102 18103 if (container.nodeType == 1) { 18104 lastIdx = container.childNodes.length - 1; 18105 18106 if (!start && offset) 18107 offset--; 18108 18109 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 18110 } 18111 18112 // If start text node is excluded then walk to the next node 18113 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 18114 container = new TreeWalker(container, ed.getBody()).next() || container; 18115 } 18116 18117 // If end text node is excluded then walk to the previous node 18118 if (container.nodeType === 3 && !start && offset === 0) { 18119 container = new TreeWalker(container, ed.getBody()).prev() || container; 18120 } 18121 18122 return container; 18123 }; 18124 18125 function performCaretAction(type, name, vars) { 18126 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 18127 18128 // Creates a caret container bogus element 18129 function createCaretContainer(fill) { 18130 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 18131 18132 if (fill) { 18133 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 18134 } 18135 18136 return caretContainer; 18137 }; 18138 18139 function isCaretContainerEmpty(node, nodes) { 18140 while (node) { 18141 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 18142 return false; 18143 } 18144 18145 // Collect nodes 18146 if (nodes && node.nodeType === 1) { 18147 nodes.push(node); 18148 } 18149 18150 node = node.firstChild; 18151 } 18152 18153 return true; 18154 }; 18155 18156 // Returns any parent caret container element 18157 function getParentCaretContainer(node) { 18158 while (node) { 18159 if (node.id === caretContainerId) { 18160 return node; 18161 } 18162 18163 node = node.parentNode; 18164 } 18165 }; 18166 18167 // Finds the first text node in the specified node 18168 function findFirstTextNode(node) { 18169 var walker; 18170 18171 if (node) { 18172 walker = new TreeWalker(node, node); 18173 18174 for (node = walker.current(); node; node = walker.next()) { 18175 if (node.nodeType === 3) { 18176 return node; 18177 } 18178 } 18179 } 18180 }; 18181 18182 // Removes the caret container for the specified node or all on the current document 18183 function removeCaretContainer(node, move_caret) { 18184 var child, rng; 18185 18186 if (!node) { 18187 node = getParentCaretContainer(selection.getStart()); 18188 18189 if (!node) { 18190 while (node = dom.get(caretContainerId)) { 18191 removeCaretContainer(node, false); 18192 } 18193 } 18194 } else { 18195 rng = selection.getRng(true); 18196 18197 if (isCaretContainerEmpty(node)) { 18198 if (move_caret !== false) { 18199 rng.setStartBefore(node); 18200 rng.setEndBefore(node); 18201 } 18202 18203 dom.remove(node); 18204 } else { 18205 child = findFirstTextNode(node); 18206 18207 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 18208 child = child.deleteData(0, 1); 18209 } 18210 18211 dom.remove(node, 1); 18212 } 18213 18214 selection.setRng(rng); 18215 } 18216 }; 18217 18218 // Applies formatting to the caret postion 18219 function applyCaretFormat() { 18220 var rng, caretContainer, textNode, offset, bookmark, container, text; 18221 18222 rng = selection.getRng(true); 18223 offset = rng.startOffset; 18224 container = rng.startContainer; 18225 text = container.nodeValue; 18226 18227 caretContainer = getParentCaretContainer(selection.getStart()); 18228 if (caretContainer) { 18229 textNode = findFirstTextNode(caretContainer); 18230 } 18231 18232 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 18233 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 18234 // Get bookmark of caret position 18235 bookmark = selection.getBookmark(); 18236 18237 // Collapse bookmark range (WebKit) 18238 rng.collapse(true); 18239 18240 // Expand the range to the closest word and split it at those points 18241 rng = expandRng(rng, get(name)); 18242 rng = rangeUtils.split(rng); 18243 18244 // Apply the format to the range 18245 apply(name, vars, rng); 18246 18247 // Move selection back to caret position 18248 selection.moveToBookmark(bookmark); 18249 } else { 18250 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 18251 caretContainer = createCaretContainer(true); 18252 textNode = caretContainer.firstChild; 18253 18254 rng.insertNode(caretContainer); 18255 offset = 1; 18256 18257 apply(name, vars, caretContainer); 18258 } else { 18259 apply(name, vars, caretContainer); 18260 } 18261 18262 // Move selection to text node 18263 selection.setCursorLocation(textNode, offset); 18264 } 18265 }; 18266 18267 function removeCaretFormat() { 18268 var rng = selection.getRng(true), container, offset, bookmark, 18269 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 18270 18271 container = rng.startContainer; 18272 offset = rng.startOffset; 18273 node = container; 18274 18275 if (container.nodeType == 3) { 18276 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 18277 hasContentAfter = true; 18278 } 18279 18280 node = node.parentNode; 18281 } 18282 18283 while (node) { 18284 if (matchNode(node, name, vars)) { 18285 formatNode = node; 18286 break; 18287 } 18288 18289 if (node.nextSibling) { 18290 hasContentAfter = true; 18291 } 18292 18293 parents.push(node); 18294 node = node.parentNode; 18295 } 18296 18297 // Node doesn't have the specified format 18298 if (!formatNode) { 18299 return; 18300 } 18301 18302 // Is there contents after the caret then remove the format on the element 18303 if (hasContentAfter) { 18304 // Get bookmark of caret position 18305 bookmark = selection.getBookmark(); 18306 18307 // Collapse bookmark range (WebKit) 18308 rng.collapse(true); 18309 18310 // Expand the range to the closest word and split it at those points 18311 rng = expandRng(rng, get(name), true); 18312 rng = rangeUtils.split(rng); 18313 18314 // Remove the format from the range 18315 remove(name, vars, rng); 18316 18317 // Move selection back to caret position 18318 selection.moveToBookmark(bookmark); 18319 } else { 18320 caretContainer = createCaretContainer(); 18321 18322 node = caretContainer; 18323 for (i = parents.length - 1; i >= 0; i--) { 18324 node.appendChild(dom.clone(parents[i], false)); 18325 node = node.firstChild; 18326 } 18327 18328 // Insert invisible character into inner most format element 18329 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 18330 node = node.firstChild; 18331 18332 var block = dom.getParent(formatNode, isTextBlock); 18333 18334 if (block && dom.isEmpty(block)) { 18335 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 18336 formatNode.parentNode.replaceChild(caretContainer, formatNode); 18337 } else { 18338 // Insert caret container after the formated node 18339 dom.insertAfter(caretContainer, formatNode); 18340 } 18341 18342 // Move selection to text node 18343 selection.setCursorLocation(node, 1); 18344 18345 // If the formatNode is empty, we can remove it safely. 18346 if (dom.isEmpty(formatNode)) { 18347 dom.remove(formatNode); 18348 } 18349 } 18350 }; 18351 18352 // Checks if the parent caret container node isn't empty if that is the case it 18353 // will remove the bogus state on all children that isn't empty 18354 function unmarkBogusCaretParents() { 18355 var i, caretContainer, node; 18356 18357 caretContainer = getParentCaretContainer(selection.getStart()); 18358 if (caretContainer && !dom.isEmpty(caretContainer)) { 18359 tinymce.walk(caretContainer, function(node) { 18360 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 18361 dom.setAttrib(node, 'data-mce-bogus', null); 18362 } 18363 }, 'childNodes'); 18364 } 18365 }; 18366 18367 // Only bind the caret events once 18368 if (!self._hasCaretEvents) { 18369 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 18370 ed.onBeforeGetContent.addToTop(function() { 18371 var nodes = [], i; 18372 18373 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 18374 // Mark children 18375 i = nodes.length; 18376 while (i--) { 18377 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 18378 } 18379 } 18380 }); 18381 18382 // Remove caret container on mouse up and on key up 18383 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 18384 ed[name].addToTop(function() { 18385 removeCaretContainer(); 18386 unmarkBogusCaretParents(); 18387 }); 18388 }); 18389 18390 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18391 ed.onKeyDown.addToTop(function(ed, e) { 18392 var keyCode = e.keyCode; 18393 18394 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18395 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18396 } 18397 18398 unmarkBogusCaretParents(); 18399 }); 18400 18401 // Remove bogus state if they got filled by contents using editor.selection.setContent 18402 selection.onSetContent.add(unmarkBogusCaretParents); 18403 18404 self._hasCaretEvents = true; 18405 } 18406 18407 // Do apply or remove caret format 18408 if (type == "apply") { 18409 applyCaretFormat(); 18410 } else { 18411 removeCaretFormat(); 18412 } 18413 }; 18414 18415 function moveStart(rng) { 18416 var container = rng.startContainer, 18417 offset = rng.startOffset, isAtEndOfText, 18418 walker, node, nodes, tmpNode; 18419 18420 // Convert text node into index if possible 18421 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18422 // Get the parent container location and walk from there 18423 offset = nodeIndex(container); 18424 container = container.parentNode; 18425 isAtEndOfText = true; 18426 } 18427 18428 // Move startContainer/startOffset in to a suitable node 18429 if (container.nodeType == 1) { 18430 nodes = container.childNodes; 18431 container = nodes[Math.min(offset, nodes.length - 1)]; 18432 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18433 18434 // If offset is at end of the parent node walk to the next one 18435 if (offset > nodes.length - 1 || isAtEndOfText) 18436 walker.next(); 18437 18438 for (node = walker.current(); node; node = walker.next()) { 18439 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18440 // IE has a "neat" feature where it moves the start node into the closest element 18441 // we can avoid this by inserting an element before it and then remove it after we set the selection 18442 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18443 node.parentNode.insertBefore(tmpNode, node); 18444 18445 // Set selection and remove tmpNode 18446 rng.setStart(node, 0); 18447 selection.setRng(rng); 18448 dom.remove(tmpNode); 18449 18450 return; 18451 } 18452 } 18453 } 18454 }; 18455 }; 18456 })(tinymce); 18457 tinymce.onAddEditor.add(function(tinymce, ed) { 18458 var filters, fontSizes, dom, settings = ed.settings; 18459 18460 function replaceWithSpan(node, styles) { 18461 tinymce.each(styles, function(value, name) { 18462 if (value) 18463 dom.setStyle(node, name, value); 18464 }); 18465 18466 dom.rename(node, 'span'); 18467 }; 18468 18469 function convert(editor, params) { 18470 dom = editor.dom; 18471 18472 if (settings.convert_fonts_to_spans) { 18473 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18474 filters[node.nodeName.toLowerCase()](ed.dom, node); 18475 }); 18476 } 18477 }; 18478 18479 if (settings.inline_styles) { 18480 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18481 18482 filters = { 18483 font : function(dom, node) { 18484 replaceWithSpan(node, { 18485 backgroundColor : node.style.backgroundColor, 18486 color : node.color, 18487 fontFamily : node.face, 18488 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18489 }); 18490 }, 18491 18492 u : function(dom, node) { 18493 replaceWithSpan(node, { 18494 textDecoration : 'underline' 18495 }); 18496 }, 18497 18498 strike : function(dom, node) { 18499 replaceWithSpan(node, { 18500 textDecoration : 'line-through' 18501 }); 18502 } 18503 }; 18504 18505 ed.onPreProcess.add(convert); 18506 ed.onSetContent.add(convert); 18507 18508 ed.onInit.add(function() { 18509 ed.selection.onSetContent.add(convert); 18510 }); 18511 } 18512 }); 18513 (function(tinymce) { 18514 var TreeWalker = tinymce.dom.TreeWalker; 18515 18516 tinymce.EnterKey = function(editor) { 18517 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18518 18519 function handleEnterKey(evt) { 18520 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 18521 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18522 18523 // Returns true if the block can be split into two blocks or not 18524 function canSplitBlock(node) { 18525 return node && 18526 dom.isBlock(node) && 18527 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18528 !/^(fixed|absolute)/i.test(node.style.position) && 18529 dom.getContentEditable(node) !== "true"; 18530 }; 18531 18532 // Renders empty block on IE 18533 function renderBlockOnIE(block) { 18534 var oldRng; 18535 18536 if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) { 18537 oldRng = selection.getRng(); 18538 block.appendChild(dom.create('span', null, '\u00a0')); 18539 selection.select(block); 18540 block.lastChild.outerHTML = ''; 18541 selection.setRng(oldRng); 18542 } 18543 }; 18544 18545 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18546 function trimInlineElementsOnLeftSideOfBlock(block) { 18547 var node = block, firstChilds = [], i; 18548 18549 // Find inner most first child ex: <p><i><b>*</b></i></p> 18550 while (node = node.firstChild) { 18551 if (dom.isBlock(node)) { 18552 return; 18553 } 18554 18555 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18556 firstChilds.push(node); 18557 } 18558 } 18559 18560 i = firstChilds.length; 18561 while (i--) { 18562 node = firstChilds[i]; 18563 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18564 dom.remove(node); 18565 } else { 18566 // Remove <a> </a> see #5381 18567 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 18568 dom.remove(node); 18569 } 18570 } 18571 } 18572 }; 18573 18574 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18575 function moveToCaretPosition(root) { 18576 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18577 18578 rng = dom.createRng(); 18579 18580 if (root.hasChildNodes()) { 18581 walker = new TreeWalker(root, root); 18582 18583 while (node = walker.current()) { 18584 if (node.nodeType == 3) { 18585 rng.setStart(node, 0); 18586 rng.setEnd(node, 0); 18587 break; 18588 } 18589 18590 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18591 rng.setStartBefore(node); 18592 rng.setEndBefore(node); 18593 break; 18594 } 18595 18596 lastNode = node; 18597 node = walker.next(); 18598 } 18599 18600 if (!node) { 18601 rng.setStart(lastNode, 0); 18602 rng.setEnd(lastNode, 0); 18603 } 18604 } else { 18605 if (root.nodeName == 'BR') { 18606 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18607 // Trick on older IE versions to render the caret before the BR between two lists 18608 if (!documentMode || documentMode < 9) { 18609 tempElm = dom.create('br'); 18610 root.parentNode.insertBefore(tempElm, root); 18611 } 18612 18613 rng.setStartBefore(root); 18614 rng.setEndBefore(root); 18615 } else { 18616 rng.setStartAfter(root); 18617 rng.setEndAfter(root); 18618 } 18619 } else { 18620 rng.setStart(root, 0); 18621 rng.setEnd(root, 0); 18622 } 18623 } 18624 18625 selection.setRng(rng); 18626 18627 // Remove tempElm created for old IE:s 18628 dom.remove(tempElm); 18629 18630 viewPort = dom.getViewPort(editor.getWin()); 18631 18632 // 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 18633 y = dom.getPos(root).y; 18634 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18635 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 18636 } 18637 }; 18638 18639 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18640 // This function will also copy any text formatting from the parent block and add it to the new one 18641 function createNewBlock(name) { 18642 var node = container, block, clonedNode, caretNode; 18643 18644 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18645 caretNode = block; 18646 18647 // Clone any parent styles 18648 if (settings.keep_styles !== false) { 18649 do { 18650 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18651 // Never clone a caret containers 18652 if (node.id == '_mce_caret') { 18653 continue; 18654 } 18655 18656 clonedNode = node.cloneNode(false); 18657 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18658 18659 if (block.hasChildNodes()) { 18660 clonedNode.appendChild(block.firstChild); 18661 block.appendChild(clonedNode); 18662 } else { 18663 caretNode = clonedNode; 18664 block.appendChild(clonedNode); 18665 } 18666 } 18667 } while (node = node.parentNode); 18668 } 18669 18670 // BR is needed in empty blocks on non IE browsers 18671 if (!tinymce.isIE || tinymce.isIE11) { 18672 caretNode.innerHTML = '<br data-mce-bogus="1">'; 18673 } 18674 18675 return block; 18676 }; 18677 18678 // Returns true/false if the caret is at the start/end of the parent block element 18679 function isCaretAtStartOrEndOfBlock(start) { 18680 var walker, node, name; 18681 18682 // Caret is in the middle of a text node like "a|b" 18683 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18684 return false; 18685 } 18686 18687 // If after the last element in block node edge case for #5091 18688 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18689 return true; 18690 } 18691 18692 // If the caret if before the first element in parentBlock 18693 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18694 return true; 18695 } 18696 18697 // Caret can be before/after a table 18698 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18699 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18700 } 18701 18702 // Walk the DOM and look for text nodes or non empty elements 18703 walker = new TreeWalker(container, parentBlock); 18704 18705 // If caret is in beginning or end of a text block then jump to the next/previous node 18706 if (container.nodeType == 3) { 18707 if (start && offset == 0) { 18708 walker.prev(); 18709 } else if (!start && offset == container.nodeValue.length) { 18710 walker.next(); 18711 } 18712 } 18713 18714 while (node = walker.current()) { 18715 if (node.nodeType === 1) { 18716 // Ignore bogus elements 18717 if (!node.getAttribute('data-mce-bogus')) { 18718 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18719 name = node.nodeName.toLowerCase(); 18720 if (nonEmptyElementsMap[name] && name !== 'br') { 18721 return false; 18722 } 18723 } 18724 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18725 return false; 18726 } 18727 18728 if (start) { 18729 walker.prev(); 18730 } else { 18731 walker.next(); 18732 } 18733 } 18734 18735 return true; 18736 }; 18737 18738 // Wraps any text nodes or inline elements in the specified forced root block name 18739 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18740 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18741 18742 // Not in a block element or in a table cell or caption 18743 parentBlock = dom.getParent(container, dom.isBlock); 18744 if (!parentBlock || !canSplitBlock(parentBlock)) { 18745 parentBlock = parentBlock || editableRoot; 18746 18747 if (!parentBlock.hasChildNodes()) { 18748 newBlock = dom.create(blockName); 18749 parentBlock.appendChild(newBlock); 18750 rng.setStart(newBlock, 0); 18751 rng.setEnd(newBlock, 0); 18752 return newBlock; 18753 } 18754 18755 // Find parent that is the first child of parentBlock 18756 node = container; 18757 while (node.parentNode != parentBlock) { 18758 node = node.parentNode; 18759 } 18760 18761 // Loop left to find start node start wrapping at 18762 while (node && !dom.isBlock(node)) { 18763 startNode = node; 18764 node = node.previousSibling; 18765 } 18766 18767 if (startNode) { 18768 newBlock = dom.create(blockName); 18769 startNode.parentNode.insertBefore(newBlock, startNode); 18770 18771 // Start wrapping until we hit a block 18772 node = startNode; 18773 while (node && !dom.isBlock(node)) { 18774 next = node.nextSibling; 18775 newBlock.appendChild(node); 18776 node = next; 18777 } 18778 18779 // Restore range to it's past location 18780 rng.setStart(container, offset); 18781 rng.setEnd(container, offset); 18782 } 18783 } 18784 18785 return container; 18786 }; 18787 18788 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18789 function handleEmptyListItem() { 18790 function isFirstOrLastLi(first) { 18791 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18792 18793 // Find first/last element since there might be whitespace there 18794 while (node) { 18795 if (node.nodeType == 1) { 18796 break; 18797 } 18798 18799 node = node[first ? 'nextSibling' : 'previousSibling']; 18800 } 18801 18802 return node === parentBlock; 18803 }; 18804 18805 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18806 18807 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18808 // Is first and last list item then replace the OL/UL with a text block 18809 dom.replace(newBlock, containerBlock); 18810 } else if (isFirstOrLastLi(true)) { 18811 // First LI in list then remove LI and add text block before list 18812 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18813 } else if (isFirstOrLastLi()) { 18814 // Last LI in list then temove LI and add text block after list 18815 dom.insertAfter(newBlock, containerBlock); 18816 renderBlockOnIE(newBlock); 18817 } else { 18818 // Middle LI in list the split the list and insert a text block in the middle 18819 // Extract after fragment and insert it after the current block 18820 tmpRng = rng.cloneRange(); 18821 tmpRng.setStartAfter(parentBlock); 18822 tmpRng.setEndAfter(containerBlock); 18823 fragment = tmpRng.extractContents(); 18824 dom.insertAfter(fragment, containerBlock); 18825 dom.insertAfter(newBlock, containerBlock); 18826 } 18827 18828 dom.remove(parentBlock); 18829 moveToCaretPosition(newBlock); 18830 undoManager.add(); 18831 }; 18832 18833 // Walks the parent block to the right and look for any contents 18834 function hasRightSideContent() { 18835 var walker = new TreeWalker(container, parentBlock), node; 18836 18837 while (node = walker.next()) { 18838 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 18839 return true; 18840 } 18841 } 18842 } 18843 18844 // Inserts a BR element if the forced_root_block option is set to false or empty string 18845 function insertBr() { 18846 var brElm, extraBr, marker; 18847 18848 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18849 // Insert extra BR element at the end block elements 18850 if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) { 18851 brElm = dom.create('br'); 18852 rng.insertNode(brElm); 18853 rng.setStartAfter(brElm); 18854 rng.setEndAfter(brElm); 18855 extraBr = true; 18856 } 18857 } 18858 18859 brElm = dom.create('br'); 18860 rng.insertNode(brElm); 18861 18862 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18863 if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18864 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18865 } 18866 18867 // Insert temp marker and scroll to that 18868 marker = dom.create('span', {}, ' '); 18869 brElm.parentNode.insertBefore(marker, brElm); 18870 selection.scrollIntoView(marker); 18871 dom.remove(marker); 18872 18873 if (!extraBr) { 18874 rng.setStartAfter(brElm); 18875 rng.setEndAfter(brElm); 18876 } else { 18877 rng.setStartBefore(brElm); 18878 rng.setEndBefore(brElm); 18879 } 18880 18881 selection.setRng(rng); 18882 undoManager.add(); 18883 }; 18884 18885 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18886 function trimLeadingLineBreaks(node) { 18887 do { 18888 if (node.nodeType === 3) { 18889 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18890 } 18891 18892 node = node.firstChild; 18893 } while (node); 18894 }; 18895 18896 function getEditableRoot(node) { 18897 var root = dom.getRoot(), parent, editableRoot; 18898 18899 // Get all parents until we hit a non editable parent or the root 18900 parent = node; 18901 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18902 if (dom.getContentEditable(parent) === "true") { 18903 editableRoot = parent; 18904 } 18905 18906 parent = parent.parentNode; 18907 } 18908 18909 return parent !== root ? editableRoot : root; 18910 }; 18911 18912 // 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 18913 function addBrToBlockIfNeeded(block) { 18914 var lastChild; 18915 18916 // IE will render the blocks correctly other browsers needs a BR 18917 if (!tinymce.isIE || tinymce.isIE11) { 18918 block.normalize(); // Remove empty text nodes that got left behind by the extract 18919 18920 // Check if the block is empty or contains a floated last child 18921 lastChild = block.lastChild; 18922 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18923 dom.add(block, 'br'); 18924 } 18925 } 18926 }; 18927 18928 // Delete any selected contents 18929 if (!rng.collapsed) { 18930 editor.execCommand('Delete'); 18931 return; 18932 } 18933 18934 // Event is blocked by some other handler for example the lists plugin 18935 if (evt.isDefaultPrevented()) { 18936 return; 18937 } 18938 18939 // Setup range items and newBlockName 18940 container = rng.startContainer; 18941 offset = rng.startOffset; 18942 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 18943 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18944 documentMode = dom.doc.documentMode; 18945 shiftKey = evt.shiftKey; 18946 18947 // Resolve node index 18948 if (container.nodeType == 1 && container.hasChildNodes()) { 18949 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18950 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18951 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18952 offset = container.nodeValue.length; 18953 } else { 18954 offset = 0; 18955 } 18956 } 18957 18958 // Get editable root node normaly the body element but sometimes a div or span 18959 editableRoot = getEditableRoot(container); 18960 18961 // If there is no editable root then enter is done inside a contentEditable false element 18962 if (!editableRoot) { 18963 return; 18964 } 18965 18966 undoManager.beforeChange(); 18967 18968 // If editable root isn't block nor the root of the editor 18969 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18970 if (!newBlockName || shiftKey) { 18971 insertBr(); 18972 } 18973 18974 return; 18975 } 18976 18977 // Wrap the current node and it's sibling in a default block if it's needed. 18978 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18979 // This won't happen if root blocks are disabled or the shiftKey is pressed 18980 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 18981 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18982 } 18983 18984 // Find parent block and setup empty block paddings 18985 parentBlock = dom.getParent(container, dom.isBlock); 18986 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18987 18988 // Setup block names 18989 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18990 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18991 18992 // Enter inside block contained within a LI then split or insert before/after LI 18993 if (containerBlockName == 'LI' && !evt.ctrlKey) { 18994 parentBlock = containerBlock; 18995 parentBlockName = containerBlockName; 18996 } 18997 18998 // Handle enter in LI 18999 if (parentBlockName == 'LI') { 19000 if (!newBlockName && shiftKey) { 19001 insertBr(); 19002 return; 19003 } 19004 19005 // Handle enter inside an empty list item 19006 if (dom.isEmpty(parentBlock)) { 19007 // Let the list plugin or browser handle nested lists for now 19008 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 19009 return false; 19010 } 19011 19012 handleEmptyListItem(); 19013 return; 19014 } 19015 } 19016 19017 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 19018 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 19019 if (!shiftKey) { 19020 insertBr(); 19021 return; 19022 } 19023 } else { 19024 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 19025 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 19026 insertBr(); 19027 return; 19028 } 19029 } 19030 19031 // Default block name if it's not configured 19032 newBlockName = newBlockName || 'P'; 19033 19034 // Insert new block before/after the parent block depending on caret location 19035 if (isCaretAtStartOrEndOfBlock()) { 19036 // 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 19037 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 19038 newBlock = createNewBlock(newBlockName); 19039 } else { 19040 newBlock = createNewBlock(); 19041 } 19042 19043 // Split the current container block element if enter is pressed inside an empty inner block element 19044 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 19045 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 19046 newBlock = dom.split(containerBlock, parentBlock); 19047 } else { 19048 dom.insertAfter(newBlock, parentBlock); 19049 } 19050 19051 moveToCaretPosition(newBlock); 19052 } else if (isCaretAtStartOrEndOfBlock(true)) { 19053 // Insert new block before 19054 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 19055 renderBlockOnIE(newBlock); 19056 } else { 19057 // Extract after fragment and insert it after the current block 19058 tmpRng = rng.cloneRange(); 19059 tmpRng.setEndAfter(parentBlock); 19060 fragment = tmpRng.extractContents(); 19061 trimLeadingLineBreaks(fragment); 19062 newBlock = fragment.firstChild; 19063 dom.insertAfter(fragment, parentBlock); 19064 trimInlineElementsOnLeftSideOfBlock(newBlock); 19065 addBrToBlockIfNeeded(parentBlock); 19066 moveToCaretPosition(newBlock); 19067 } 19068 19069 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 19070 undoManager.add(); 19071 } 19072 19073 editor.onKeyDown.add(function(ed, evt) { 19074 if (evt.keyCode == 13) { 19075 if (handleEnterKey(evt) !== false) { 19076 evt.preventDefault(); 19077 } 19078 } 19079 }); 19080 }; 19081 })(tinymce);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |