1// FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 2(function(win) { 3 var whiteSpaceRe = /^\s*|\s*$/g, 4 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 5 6 var tinymce = { 7 majorVersion : '3', 8 9 minorVersion : '5.11', 10 11 releaseDate : '2014-05-08', 12 13 _init : function() { 14 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 15 16 t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1); 17 18 t.isOpera = win.opera && opera.buildNumber; 19 20 t.isWebKit = /WebKit/.test(ua); 21 22 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11; 23 24 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 25 26 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 27 28 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 29 30 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 31 32 t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua); 33 34 t.isMac = ua.indexOf('Mac') != -1; 35 36 t.isAir = /adobeair/i.test(ua); 37 38 t.isIDevice = /(iPad|iPhone)/.test(ua); 39 40 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 41 42 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 43 if (win.tinyMCEPreInit) { 44 t.suffix = tinyMCEPreInit.suffix; 45 t.baseURL = tinyMCEPreInit.base; 46 t.query = tinyMCEPreInit.query; 47 return; 48 } 49 50 // Get suffix and base 51 t.suffix = ''; 52 53 // If base element found, add that infront of baseURL 54 nl = d.getElementsByTagName('base'); 55 for (i=0; i<nl.length; i++) { 56 v = nl[i].href; 57 if (v) { 58 // Host only value like http://site.com or http://site.com:8008 59 if (/^https?:\/\/[^\/]+$/.test(v)) 60 v += '/'; 61 62 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 63 } 64 } 65 66 function getBase(n) { 67 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 68 if (/_(src|dev)\.js/g.test(n.src)) 69 t.suffix = '_src'; 70 71 if ((p = n.src.indexOf('?')) != -1) 72 t.query = n.src.substring(p + 1); 73 74 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 75 76 // If path to script is relative and a base href was found add that one infront 77 // the src property will always be an absolute one on non IE browsers and IE 8 78 // so this logic will basically only be executed on older IE versions 79 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 80 t.baseURL = base + t.baseURL; 81 82 return t.baseURL; 83 } 84 85 return null; 86 }; 87 88 // Check document 89 nl = d.getElementsByTagName('script'); 90 for (i=0; i<nl.length; i++) { 91 if (getBase(nl[i])) 92 return; 93 } 94 95 // Check head 96 n = d.getElementsByTagName('head')[0]; 97 if (n) { 98 nl = n.getElementsByTagName('script'); 99 for (i=0; i<nl.length; i++) { 100 if (getBase(nl[i])) 101 return; 102 } 103 } 104 105 return; 106 }, 107 108 is : function(o, t) { 109 if (!t) 110 return o !== undef; 111 112 if (t == 'array' && tinymce.isArray(o)) 113 return true; 114 115 return typeof(o) == t; 116 }, 117 118 isArray: Array.isArray || function(obj) { 119 return Object.prototype.toString.call(obj) === "[object Array]"; 120 }, 121 122 makeMap : function(items, delim, map) { 123 var i; 124 125 items = items || []; 126 delim = delim || ','; 127 128 if (typeof(items) == "string") 129 items = items.split(delim); 130 131 map = map || {}; 132 133 i = items.length; 134 while (i--) 135 map[items[i]] = {}; 136 137 return map; 138 }, 139 140 each : function(o, cb, s) { 141 var n, l; 142 143 if (!o) 144 return 0; 145 146 s = s || o; 147 148 if (o.length !== undef) { 149 // Indexed arrays, needed for Safari 150 for (n=0, l = o.length; n < l; n++) { 151 if (cb.call(s, o[n], n, o) === false) 152 return 0; 153 } 154 } else { 155 // Hashtables 156 for (n in o) { 157 if (o.hasOwnProperty(n)) { 158 if (cb.call(s, o[n], n, o) === false) 159 return 0; 160 } 161 } 162 } 163 164 return 1; 165 }, 166 167 168 map : function(a, f) { 169 var o = []; 170 171 tinymce.each(a, function(v) { 172 o.push(f(v)); 173 }); 174 175 return o; 176 }, 177 178 grep : function(a, f) { 179 var o = []; 180 181 tinymce.each(a, function(v) { 182 if (!f || f(v)) 183 o.push(v); 184 }); 185 186 return o; 187 }, 188 189 inArray : function(a, v) { 190 var i, l; 191
192 if (a) { 193 for (i = 0, l = a.length; i < l; i++) { 194 if (a[i] === v) 195 return i; 196 } 197 } 198 199 return -1; 200 }, 201 202 extend : function(obj, ext) { 203 var i, l, name, args = arguments, value; 204 205 for (i = 1, l = args.length; i < l; i++) { 206 ext = args[i]; 207 for (name in ext) { 208 if (ext.hasOwnProperty(name)) { 209 value = ext[name]; 210 211 if (value !== undef) { 212 obj[name] = value; 213 } 214 } 215 } 216 } 217 218 return obj; 219 }, 220 221 222 trim : function(s) { 223 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 224 }, 225 226 create : function(s, p, root) { 227 var t = this, sp, ns, cn, scn, c, de = 0; 228 229 // Parse : <prefix> <class>:<super class> 230 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 231 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 232 233 // Create namespace for new class 234 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 235 236 // Class already exists 237 if (ns[cn]) 238 return; 239 240 // Make pure static class 241 if (s[2] == 'static') { 242 ns[cn] = p; 243 244 if (this.onCreate) 245 this.onCreate(s[2], s[3], ns[cn]); 246 247 return; 248 } 249 250 // Create default constructor 251 if (!p[cn]) { 252 p[cn] = function() {}; 253 de = 1; 254 } 255 256 // Add constructor and methods 257 ns[cn] = p[cn]; 258 t.extend(ns[cn].prototype, p); 259 260 // Extend 261 if (s[5]) { 262 sp = t.resolve(s[5]).prototype; 263 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 264 265 // Extend constructor 266 c = ns[cn]; 267 if (de) { 268 // Add passthrough constructor 269 ns[cn] = function() { 270 return sp[scn].apply(this, arguments); 271 }; 272 } else { 273 // Add inherit constructor 274 ns[cn] = function() { 275 this.parent = sp[scn]; 276 return c.apply(this, arguments); 277 }; 278 } 279 ns[cn].prototype[cn] = ns[cn]; 280 281 // Add super methods 282 t.each(sp, function(f, n) { 283 ns[cn].prototype[n] = sp[n]; 284 }); 285 286 // Add overridden methods 287 t.each(p, function(f, n) { 288 // Extend methods if needed 289 if (sp[n]) { 290 ns[cn].prototype[n] = function() { 291 this.parent = sp[n]; 292 return f.apply(this, arguments); 293 }; 294 } else { 295 if (n != cn) 296 ns[cn].prototype[n] = f; 297 } 298 }); 299 } 300 301 // Add static methods 302 t.each(p['static'], function(f, n) { 303 ns[cn][n] = f; 304 }); 305 306 if (this.onCreate) 307 this.onCreate(s[2], s[3], ns[cn].prototype); 308 }, 309 310 walk : function(o, f, n, s) { 311 s = s || this; 312 313 if (o) { 314 if (n) 315 o = o[n]; 316 317 tinymce.each(o, function(o, i) { 318 if (f.call(s, o, i, n) === false) 319 return false; 320 321 tinymce.walk(o, f, n, s); 322 }); 323 } 324 }, 325 326 createNS : function(n, o) { 327 var i, v; 328 329 o = o || win; 330 331 n = n.split('.'); 332 for (i=0; i<n.length; i++) { 333 v = n[i]; 334 335 if (!o[v]) 336 o[v] = {}; 337 338 o = o[v]; 339 } 340 341 return o; 342 }, 343 344 resolve : function(n, o) { 345 var i, l; 346 347 o = o || win; 348 349 n = n.split('.'); 350 for (i = 0, l = n.length; i < l; i++) { 351 o = o[n[i]]; 352 353 if (!o) 354 break; 355 } 356 357 return o; 358 }, 359 360 addUnload : function(f, s) { 361 var t = this, unload; 362 363 unload = function() { 364 var li = t.unloads, o, n; 365 366 if (li) { 367 // Call unload handlers 368 for (n in li) { 369 o = li[n]; 370 371 if (o && o.func) 372 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 373 } 374 375 // Detach unload function 376 if (win.detachEvent) { 377 win.detachEvent('onbeforeunload', fakeUnload); 378 win.detachEvent('onunload', unload); 379 } else if (win.removeEventListener) 380 win.removeEventListener('unload', unload, false); 381 382 // Destroy references 383 t.unloads = o = li = w = unload = 0; 384 385 // Run garbarge collector on IE 386 if (win.CollectGarbage) 387 CollectGarbage(); 388 } 389 }; 390 391 function fakeUnload() { 392 var d = document; 393 394 function stop() { 395 // Prevent memory leak 396 d.detachEvent('onstop', stop); 397 398 // Call unload handler 399 if (unload) 400 unload(); 401 402 d = 0; 403 }; 404 405 // Is there things still loading, then do some magic 406 if (d.readyState == 'interactive') { 407 // Fire unload when the currently loading page is stopped 408 if (d) 409 d.attachEvent('onstop', stop); 410 411 // Remove onstop listener after a while to prevent the unload function 412 // to execute if the user presses cancel in an onbeforeunload 413 // confirm dialog and then presses the browser stop button 414 win.setTimeout(function() { 415 if (d) 416 d.detachEvent('onstop', stop); 417 }, 0); 418 } 419 }; 420 421 f = {func : f, scope : s || this}; 422 423 if (!t.unloads) { 424 // Attach unload handler 425 if (win.attachEvent) { 426 win.attachEvent('onunload', unload); 427 win.attachEvent('onbeforeunload', fakeUnload); 428 } else if (win.addEventListener) 429 win.addEventListener('unload', unload, false); 430 431 // Setup initial unload handler array 432 t.unloads = [f]; 433 } else 434 t.unloads.push(f); 435 436 return f; 437 }, 438 439 removeUnload : function(f) { 440 var u = this.unloads, r = null; 441 442 tinymce.each(u, function(o, i) { 443 if (o && o.func == f) { 444 u.splice(i, 1); 445 r = f; 446 return false;
447 } 448 }); 449 450 return r; 451 }, 452 453 explode : function(s, d) { 454 if (!s || tinymce.is(s, 'array')) { 455 return s; 456 } 457 458 return tinymce.map(s.split(d || ','), tinymce.trim); 459 }, 460 461 _addVer : function(u) { 462 var v; 463 464 if (!this.query) 465 return u; 466 467 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 468 469 if (u.indexOf('#') == -1) 470 return u + v; 471 472 return u.replace('#', v + '#'); 473 }, 474 475 // Fix function for IE 9 where regexps isn't working correctly 476 // Todo: remove me once MS fixes the bug 477 _replace : function(find, replace, str) { 478 // On IE9 we have to fake $x replacement 479 if (isRegExpBroken) { 480 return str.replace(find, function() { 481 var val = replace, args = arguments, i; 482 483 for (i = 0; i < args.length - 2; i++) { 484 if (args[i] === undef) { 485 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 486 } else { 487 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 488 } 489 } 490 491 return val; 492 }); 493 } 494 495 return str.replace(find, replace); 496 } 497 498 }; 499 500 // Initialize the API 501 tinymce._init(); 502 503 // Expose tinymce namespace to the global namespace (window) 504 win.tinymce = win.tinyMCE = tinymce; 505 506 // Describe the different namespaces 507 508 })(window); 509 510 511 512tinymce.create('tinymce.util.Dispatcher', { 513 scope : null, 514 listeners : null, 515 inDispatch: false, 516 517 Dispatcher : function(scope) { 518 this.scope = scope || this; 519 this.listeners = []; 520 }, 521 522 add : function(callback, scope) { 523 this.listeners.push({cb : callback, scope : scope || this.scope}); 524 525 return callback; 526 }, 527 528 addToTop : function(callback, scope) { 529 var self = this, listener = {cb : callback, scope : scope || self.scope}; 530 531 // Create new listeners if addToTop is executed in a dispatch loop 532 if (self.inDispatch) { 533 self.listeners = [listener].concat(self.listeners); 534 } else { 535 self.listeners.unshift(listener); 536 } 537 538 return callback; 539 }, 540 541 remove : function(callback) { 542 var listeners = this.listeners, output = null; 543 544 tinymce.each(listeners, function(listener, i) { 545 if (callback == listener.cb) { 546 output = listener; 547 listeners.splice(i, 1); 548 return false; 549 } 550 }); 551 552 return output; 553 }, 554 555 dispatch : function() { 556 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 557 558 self.inDispatch = true; 559 560 // Needs to be a real loop since the listener count might change while looping 561 // And this is also more efficient 562 for (i = 0; i < listeners.length; i++) { 563 listener = listeners[i]; 564 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 565 566 if (returnValue === false) 567 break; 568 } 569 570 self.inDispatch = false; 571 572 return returnValue; 573 } 574 575 }); 576 577(function() { 578 var each = tinymce.each; 579 580 tinymce.create('tinymce.util.URI', { 581 URI : function(u, s) { 582 var t = this, o, a, b, base_url; 583 584 // Trim whitespace 585 u = tinymce.trim(u); 586 587 // Default settings 588 s = t.settings = s || {}; 589 590 // Strange app protocol that isn't http/https or local anchor 591 // For example: mailto,skype,tel etc. 592 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 593 t.source = u; 594 return; 595 } 596 597 // Absolute path with no host, fake host and protocol 598 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 599 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 600 601 // Relative path http:// or protocol relative //path 602 if (!/^[\w\-]*:?\/\//.test(u)) { 603 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 604 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 605 } 606 607 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 608 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 609 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 610 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 611 var s = u[i]; 612 613 // Zope 3 workaround, they use @@something 614 if (s) 615 s = s.replace(/\(mce_at\)/g, '@@'); 616 617 t[v] = s; 618 }); 619 620 b = s.base_uri; 621 if (b) { 622 if (!t.protocol) 623 t.protocol = b.protocol; 624 625 if (!t.userInfo) 626 t.userInfo = b.userInfo; 627 628 if (!t.port && t.host === 'mce_host') 629 t.port = b.port;
630 631 if (!t.host || t.host === 'mce_host') 632 t.host = b.host; 633 634 t.source = ''; 635 } 636 637 //t.path = t.path || '/'; 638 }, 639 640 setPath : function(p) { 641 var t = this; 642 643 p = /^(.*?)\/?(\w+)?$/.exec(p); 644 645 // Update path parts 646 t.path = p[0]; 647 t.directory = p[1]; 648 t.file = p[2]; 649 650 // Rebuild source 651 t.source = ''; 652 t.getURI(); 653 }, 654 655 toRelative : function(u) { 656 var t = this, o; 657 658 if (u === "./") 659 return u; 660 661 u = new tinymce.util.URI(u, {base_uri : t}); 662 663 // Not on same domain/port or protocol 664 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 665 return u.getURI(); 666 667 var tu = t.getURI(), uu = u.getURI(); 668 669 // Allow usage of the base_uri when relative_urls = true 670 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 671 return tu; 672 673 o = t.toRelPath(t.path, u.path); 674 675 // Add query 676 if (u.query) 677 o += '?' + u.query; 678 679 // Add anchor 680 if (u.anchor) 681 o += '#' + u.anchor; 682 683 return o; 684 }, 685 686 toAbsolute : function(u, nh) { 687 u = new tinymce.util.URI(u, {base_uri : this}); 688 689 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 690 }, 691 692 toRelPath : function(base, path) { 693 var items, bp = 0, out = '', i, l; 694 695 // Split the paths 696 base = base.substring(0, base.lastIndexOf('/')); 697 base = base.split('/'); 698 items = path.split('/'); 699 700 if (base.length >= items.length) { 701 for (i = 0, l = base.length; i < l; i++) { 702 if (i >= items.length || base[i] != items[i]) { 703 bp = i + 1; 704 break; 705 } 706 } 707 } 708 709 if (base.length < items.length) { 710 for (i = 0, l = items.length; i < l; i++) { 711 if (i >= base.length || base[i] != items[i]) { 712 bp = i + 1; 713 break; 714 } 715 } 716 } 717 718 if (bp === 1) 719 return path; 720 721 for (i = 0, l = base.length - (bp - 1); i < l; i++) 722 out += "../"; 723 724 for (i = bp - 1, l = items.length; i < l; i++) { 725 if (i != bp - 1) 726 out += "/" + items[i]; 727 else 728 out += items[i]; 729 } 730 731 return out; 732 }, 733 734 toAbsPath : function(base, path) { 735 var i, nb = 0, o = [], tr, outPath; 736 737 // Split paths 738 tr = /\/$/.test(path) ? '/' : ''; 739 base = base.split('/'); 740 path = path.split('/'); 741 742 // Remove empty chunks 743 each(base, function(k) { 744 if (k) 745 o.push(k); 746 }); 747 748 base = o; 749 750 // Merge relURLParts chunks 751 for (i = path.length - 1, o = []; i >= 0; i--) { 752 // Ignore empty or . 753 if (path[i].length === 0 || path[i] === ".") 754 continue; 755 756 // Is parent 757 if (path[i] === '..') { 758 nb++; 759 continue; 760 } 761 762 // Move up 763 if (nb > 0) { 764 nb--; 765 continue; 766 } 767 768 o.push(path[i]); 769 } 770 771 i = base.length - nb; 772 773 // If /a/b/c or / 774 if (i <= 0) 775 outPath = o.reverse().join('/'); 776 else 777 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 778 779 // Add front / if it's needed 780 if (outPath.indexOf('/') !== 0) 781 outPath = '/' + outPath; 782 783 // Add traling / if it's needed 784 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 785 outPath += tr; 786 787 return outPath; 788 }, 789 790 getURI : function(nh) { 791 var s, t = this; 792 793 // Rebuild source 794 if (!t.source || nh) { 795 s = ''; 796 797 if (!nh) { 798 if (t.protocol) 799 s += t.protocol + '://'; 800 801 if (t.userInfo) 802 s += t.userInfo + '@'; 803 804 if (t.host) 805 s += t.host; 806 807 if (t.port) 808 s += ':' + t.port; 809 } 810 811 if (t.path) 812 s += t.path; 813 814 if (t.query) 815 s += '?' + t.query; 816 817 if (t.anchor) 818 s += '#' + t.anchor; 819 820 t.source = s; 821 } 822 823 return t.source; 824 } 825 }); 826})(); 827 828(function() { 829 var each = tinymce.each; 830 831 tinymce.create('static tinymce.util.Cookie', { 832 getHash : function(n) { 833 var v = this.get(n), h; 834 835 if (v) { 836 each(v.split('&'), function(v) { 837 v = v.split('='); 838 h = h || {}; 839 h[unescape(v[0])] = unescape(v[1]); 840 }); 841 } 842 843 return h; 844 }, 845 846 setHash : function(n, v, e, p, d, s) { 847 var o = ''; 848 849 each(v, function(v, k) { 850 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 851 }); 852 853 this.set(n, o, e, p, d, s); 854 }, 855 856 get : function(n) { 857 var c = document.cookie, e, p = n + "=", b; 858 859 // Strict mode 860 if (!c) 861 return; 862 863 b = c.indexOf("; " + p); 864 865 if (b == -1) { 866 b = c.indexOf(p); 867 868 if (b !== 0) 869 return null; 870 } else 871 b += 2; 872 873 e = c.indexOf(";", b); 874 875 if (e == -1) 876 e = c.length; 877 878 return unescape(c.substring(b + p.length, e)); 879 }, 880 881 set : function(n, v, e, p, d, s) { 882 document.cookie = n + "=" + escape(v) + 883 ((e) ? "; expires=" + e.toGMTString() : "") + 884 ((p) ? "; path=" + escape(p) : "") + 885 ((d) ? "; domain=" + d : "") + 886 ((s) ? "; secure" : ""); 887 }, 888 889 remove : function(name, path, domain) { 890 var date = new Date(); 891 892 date.setTime(date.getTime() - 1000); 893 894 this.set(name, '', date, path, domain); 895 } 896 }); 897})(); 898 899(function() { 900 function serialize(o, quote) { 901 var i, v, t, name; 902 903 quote = quote || '"'; 904 905 if (o == null) 906 return 'null';
907 908 t = typeof o; 909 910 if (t == 'string') { 911 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 912 913 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 914 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 915 if (quote === '"' && a === "'") 916 return a; 917 918 i = v.indexOf(b); 919 920 if (i + 1) 921 return '\\' + v.charAt(i + 1); 922 923 a = b.charCodeAt().toString(16); 924 925 return '\\u' + '0000'.substring(a.length) + a; 926 }) + quote; 927 } 928 929 if (t == 'object') { 930 if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { 931 for (i=0, v = '['; i<o.length; i++) 932 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 933 934 return v + ']'; 935 } 936 937 v = '{'; 938 939 for (name in o) { 940 if (o.hasOwnProperty(name)) { 941 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 942 } 943 } 944 945 return v + '}'; 946 } 947 948 return '' + o; 949 }; 950 951 tinymce.util.JSON = { 952 serialize: serialize, 953 954 parse: function(s) { 955 try { 956 return eval('(' + s + ')'); 957 } catch (ex) { 958 // Ignore 959 } 960 } 961 962 }; 963})(); 964 965tinymce.create('static tinymce.util.XHR', { 966 send : function(o) { 967 var x, t, w = window, c = 0; 968 969 function ready() { 970 if (!o.async || x.readyState == 4 || c++ > 10000) { 971 if (o.success && c < 10000 && x.status == 200) 972 o.success.call(o.success_scope, '' + x.responseText, x, o); 973 else if (o.error) 974 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 975 976 x = null; 977 } else 978 w.setTimeout(ready, 10); 979 }; 980 981 // Default settings 982 o.scope = o.scope || this; 983 o.success_scope = o.success_scope || o.scope; 984 o.error_scope = o.error_scope || o.scope; 985 o.async = o.async === false ? false : true; 986 o.data = o.data || ''; 987 988 function get(s) { 989 x = 0; 990 991 try { 992 x = new ActiveXObject(s); 993 } catch (ex) { 994 } 995 996 return x; 997 }; 998 999 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1000 1001 if (x) { 1002 if (x.overrideMimeType) 1003 x.overrideMimeType(o.content_type); 1004 1005 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1006 1007 if (o.content_type) 1008 x.setRequestHeader('Content-Type', o.content_type); 1009 1010 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1011 1012 x.send(o.data); 1013 1014 // Syncronous request 1015 if (!o.async) 1016 return ready(); 1017 1018 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1019 t = w.setTimeout(ready, 10); 1020 } 1021 } 1022}); 1023 1024(function() { 1025 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1026 1027 tinymce.create('tinymce.util.JSONRequest', { 1028 JSONRequest : function(s) { 1029 this.settings = extend({ 1030 }, s); 1031 this.count = 0; 1032 }, 1033 1034 send : function(o) { 1035 var ecb = o.error, scb = o.success; 1036 1037 o = extend(this.settings, o); 1038 1039 o.success = function(c, x) { 1040 c = JSON.parse(c); 1041 1042 if (typeof(c) == 'undefined') { 1043 c = { 1044 error : 'JSON Parse error.' 1045 }; 1046 } 1047 1048 if (c.error) 1049 ecb.call(o.error_scope || o.scope, c.error, x); 1050 else 1051 scb.call(o.success_scope || o.scope, c.result); 1052 }; 1053 1054 o.error = function(ty, x) { 1055 if (ecb) 1056 ecb.call(o.error_scope || o.scope, ty, x); 1057 }; 1058 1059 o.data = JSON.serialize({ 1060 id : o.id || 'c' + (this.count++), 1061 method : o.method, 1062 params : o.params 1063 }); 1064 1065 // JSON content type for Ruby on rails. Bug: #1883287 1066 o.content_type = 'application/json'; 1067 1068 XHR.send(o); 1069 }, 1070 1071 'static' : { 1072 sendRPC : function(o) { 1073 return new tinymce.util.JSONRequest().send(o); 1074 } 1075 } 1076 }); 1077}()); 1078(function(tinymce){ 1079 tinymce.VK = { 1080 BACKSPACE: 8, 1081 DELETE: 46, 1082 DOWN: 40, 1083 ENTER: 13, 1084 LEFT: 37, 1085 RIGHT: 39, 1086 SPACEBAR: 32, 1087 TAB: 9, 1088 UP: 38, 1089 1090 modifierPressed: function (e) { 1091 return e.shiftKey || e.ctrlKey || e.altKey; 1092 }, 1093 1094 metaKeyPressed: function(e) { 1095 // Check if ctrl or meta key is pressed also check if alt is false for Polish users 1096 return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey; 1097 } 1098 }; 1099})(tinymce); 1100 1101tinymce.util.Quirks = function(editor) { 1102 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, 1103 settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each; 1104 1105 function setEditorCommandState(cmd, state) { 1106 try { 1107 editor.getDoc().execCommand(cmd, false, state); 1108 } catch (ex) { 1109 // Ignore 1110 } 1111 } 1112 1113 function getDocumentMode() { 1114 var documentMode = editor.getDoc().documentMode; 1115 1116 return documentMode ? documentMode : 6; 1117 }; 1118 1119 function isDefaultPrevented(e) { 1120 return e.isDefaultPrevented(); 1121 }; 1122 1123 function cleanupStylesWhenDeleting() { 1124 function removeMergedFormatSpans(isDelete) { 1125 var rng, blockElm, wrapperElm, bookmark, container, offset, elm; 1126 1127 function isAtStartOrEndOfElm() { 1128 if (container.nodeType == 3) { 1129 if (isDelete && offset == container.length) { 1130 return true; 1131 } 1132 1133 if (!isDelete && offset === 0) { 1134 return true; 1135 } 1136 } 1137 } 1138 1139 rng = selection.getRng(); 1140 var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset]; 1141 1142 if (!rng.collapsed) { 1143 isDelete = true; 1144 } 1145 1146 container = rng[(isDelete ? 'start' : 'end') + 'Container']; 1147 offset = rng[(isDelete ? 'start' : 'end') + 'Offset']; 1148 1149 if (container.nodeType == 3) { 1150 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1151 1152 // On delete clone the root span of the next block element 1153 if (isDelete) { 1154 blockElm = dom.getNext(blockElm, dom.isBlock); 1155 } 1156 1157 if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) { 1158 // Wrap children of block in a EM and let WebKit stick is 1159 // runtime styles junk into that EM 1160 wrapperElm = dom.create('em', {'id': '__mceDel'}); 1161 1162 each(tinymce.grep(blockElm.childNodes), function(node) { 1163 wrapperElm.appendChild(node); 1164 }); 1165 1166 blockElm.appendChild(wrapperElm); 1167 } 1168 } 1169 1170 // Do the backspace/delete action 1171 rng = dom.createRng(); 1172 rng.setStart(tmpRng[0], tmpRng[1]); 1173 rng.setEnd(tmpRng[2], tmpRng[3]); 1174 selection.setRng(rng); 1175 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1176 1177 // Remove temp wrapper element 1178 if (wrapperElm) { 1179 bookmark = selection.getBookmark(); 1180 1181 while (elm = dom.get('__mceDel')) { 1182 dom.remove(elm, true); 1183 } 1184 1185 selection.moveToBookmark(bookmark); 1186 } 1187 } 1188 1189 editor.onKeyDown.add(function(editor, e) { 1190 var isDelete; 1191 1192 isDelete = e.keyCode == DELETE; 1193 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1194 e.preventDefault(); 1195 removeMergedFormatSpans(isDelete); 1196 } 1197 }); 1198 1199 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1200 }; 1201 1202 function emptyEditorWhenDeleting() { 1203 function serializeRng(rng) { 1204 var body = dom.create("body"); 1205 var contents = rng.cloneContents(); 1206 body.appendChild(contents);
1207 return selection.serializer.serialize(body, {format: 'html'}); 1208 } 1209 1210 function allContentsSelected(rng) { 1211 var selection = serializeRng(rng); 1212 1213 var allRng = dom.createRng(); 1214 allRng.selectNode(editor.getBody()); 1215 1216 var allSelection = serializeRng(allRng); 1217 return selection === allSelection; 1218 } 1219 1220 editor.onKeyDown.add(function(editor, e) { 1221 var keyCode = e.keyCode, isCollapsed; 1222 1223 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1224 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { 1225 isCollapsed = editor.selection.isCollapsed(); 1226 1227 // Selection is collapsed but the editor isn't empty 1228 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1229 return; 1230 } 1231 1232 // IE deletes all contents correctly when everything is selected 1233 if (tinymce.isIE && !isCollapsed) { 1234 return; 1235 } 1236 1237 // Selection isn't collapsed but not all the contents is selected 1238 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1239 return; 1240 } 1241 1242 // Manually empty the editor 1243 editor.setContent(''); 1244 editor.selection.setCursorLocation(editor.getBody(), 0); 1245 editor.nodeChanged(); 1246 } 1247 }); 1248 }; 1249 1250 function selectAll() { 1251 editor.onKeyDown.add(function(editor, e) { 1252 if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { 1253 e.preventDefault(); 1254 editor.execCommand('SelectAll'); 1255 } 1256 }); 1257 }; 1258 1259 function inputMethodFocus() { 1260 if (!editor.settings.content_editable) { 1261 // Case 1 IME doesn't initialize if you focus the document 1262 dom.bind(editor.getDoc(), 'focusin', function(e) { 1263 selection.setRng(selection.getRng()); 1264 }); 1265 1266 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1267 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1268 if (e.target == editor.getDoc().documentElement) { 1269 editor.getWin().focus(); 1270 selection.setRng(selection.getRng()); 1271 } 1272 }); 1273 } 1274 }; 1275 1276 function removeHrOnBackspace() { 1277 editor.onKeyDown.add(function(editor, e) { 1278 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 1279 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1280 var node = selection.getNode(); 1281 var previousSibling = node.previousSibling; 1282 1283 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1284 dom.remove(previousSibling); 1285 tinymce.dom.Event.cancel(e); 1286 } 1287 } 1288 } 1289 }) 1290 } 1291 1292 function focusBody() { 1293 // Fix for a focus bug in FF 3.x where the body element 1294 // wouldn't get proper focus if the user clicked on the HTML element 1295 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1296 editor.onMouseDown.add(function(editor, e) { 1297 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { 1298 var body = editor.getBody(); 1299 1300 // Blur the body it's focused but not correctly focused 1301 body.blur(); 1302 1303 // Refocus the body after a little while 1304 setTimeout(function() { 1305 body.focus(); 1306 }, 0); 1307 } 1308 }); 1309 } 1310 }; 1311 1312function selectControlElements() { 1313 editor.onClick.add(function(editor, e) { 1314 e = e.target; 1315 1316 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1317 // WebKit can't even do simple things like selecting an image 1318 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1319 if (/^(IMG|HR)$/.test(e.nodeName)) { 1320 try { 1321 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1322 } 1323 catch (x) { 1324 // On Chrome 58 and up, the above throws an exception. Try this instead. 1325 selection.select(e); 1326 } 1327 } 1328 1329 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1330 selection.select(e); 1331 } 1332 1333 editor.nodeChanged(); 1334 }); 1335 }; 1336 1337 function removeStylesWhenDeletingAccrossBlockElements() { 1338 function getAttributeApplyFunction() { 1339 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1340 1341 return function() { 1342 var target = selection.getStart(); 1343 1344 if (target !== editor.getBody()) { 1345 dom.setAttrib(target, "style", null); 1346 1347 each(template, function(attr) { 1348 target.setAttributeNode(attr.cloneNode(true)); 1349 }); 1350 } 1351 }; 1352 } 1353 1354 function isSelectionAcrossElements() { 1355 return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); 1356 } 1357 1358 function blockEvent(editor, e) { 1359 e.preventDefault(); 1360 return false;
1361 } 1362 1363 editor.onKeyPress.add(function(editor, e) { 1364 var applyAttributes; 1365 1366 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1367 applyAttributes = getAttributeApplyFunction(); 1368 editor.getDoc().execCommand('delete', false, null); 1369 applyAttributes(); 1370 e.preventDefault(); 1371 return false; 1372 } 1373 }); 1374 1375 dom.bind(editor.getDoc(), 'cut', function(e) { 1376 var applyAttributes; 1377 1378 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { 1379 applyAttributes = getAttributeApplyFunction(); 1380 editor.onKeyUp.addToTop(blockEvent); 1381 1382 setTimeout(function() { 1383 applyAttributes(); 1384 editor.onKeyUp.remove(blockEvent); 1385 }, 0); 1386 } 1387 }); 1388 } 1389 1390 function selectionChangeNodeChanged() { 1391 var lastRng, selectionTimer; 1392 1393 dom.bind(editor.getDoc(), 'selectionchange', function() { 1394 if (selectionTimer) { 1395 clearTimeout(selectionTimer); 1396 selectionTimer = 0; 1397 } 1398 1399 selectionTimer = window.setTimeout(function() { 1400 var rng = selection.getRng(); 1401 1402 // Compare the ranges to see if it was a real change or not 1403 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1404 editor.nodeChanged(); 1405 lastRng = rng; 1406 } 1407 }, 50); 1408 }); 1409 } 1410 1411 function ensureBodyHasRoleApplication() { 1412 document.body.setAttribute("role", "application"); 1413 } 1414 1415 function disableBackspaceIntoATable() { 1416 editor.onKeyDown.add(function(editor, e) { 1417 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 1418 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1419 var previousSibling = selection.getNode().previousSibling; 1420 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1421 return tinymce.dom.Event.cancel(e); 1422 } 1423 } 1424 } 1425 }) 1426 } 1427 1428 function addNewLinesBeforeBrInPre() { 1429 // IE8+ rendering mode does the right thing with BR in PRE 1430 if (getDocumentMode() > 7) { 1431 return; 1432 } 1433 1434 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1435 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1436 setEditorCommandState('RespectVisibilityInDesign', true); 1437 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1438 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1439 1440 // Adds a \n before all BR elements in PRE to get them visual 1441 parser.addNodeFilter('pre', function(nodes, name) { 1442 var i = nodes.length, brNodes, j, brElm, sibling; 1443 1444 while (i--) { 1445 brNodes = nodes[i].getAll('br'); 1446 j = brNodes.length; 1447 while (j--) { 1448 brElm = brNodes[j]; 1449 1450 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1451 sibling = brElm.prev; 1452 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1453 sibling.value += '\n'; 1454 } else { 1455 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1456 } 1457 } 1458 } 1459 }); 1460 1461 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1462 serializer.addNodeFilter('pre', function(nodes, name) { 1463 var i = nodes.length, brNodes, j, brElm, sibling; 1464 1465 while (i--) { 1466 brNodes = nodes[i].getAll('br'); 1467 j = brNodes.length; 1468 while (j--) { 1469 brElm = brNodes[j]; 1470 sibling = brElm.prev; 1471 if (sibling && sibling.type == 3) { 1472 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1473 } 1474 } 1475 } 1476 }); 1477 } 1478 1479 function removePreSerializedStylesWhenSelectingControls() { 1480 dom.bind(editor.getBody(), 'mouseup', function(e) { 1481 var value, node = selection.getNode(); 1482 1483 // Moved styles to attributes on IMG eements 1484 if (node.nodeName == 'IMG') { 1485 // Convert style width to width attribute 1486 if (value = dom.getStyle(node, 'width')) { 1487 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1488 dom.setStyle(node, 'width', ''); 1489 } 1490 1491 // Convert style height to height attribute 1492 if (value = dom.getStyle(node, 'height')) { 1493 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1494 dom.setStyle(node, 'height', ''); 1495 } 1496 } 1497 }); 1498 } 1499 1500 function keepInlineElementOnDeleteBackspace() { 1501 editor.onKeyDown.add(function(editor, e) { 1502 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1503 1504 isDelete = e.keyCode == DELETE; 1505 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1506 rng = selection.getRng(); 1507 container = rng.startContainer; 1508 offset = rng.startOffset; 1509 collapsed = rng.collapsed; 1510 1511 // Override delete if the start container is a text node and is at the beginning of text or 1512 // just before/after the last character to be deleted in collapsed mode 1513 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1514 // Edge case when deleting <p><b><img> |x</b></p> 1515 sibling = container.previousSibling; 1516 if (sibling && sibling.nodeName == "IMG") { 1517 return; 1518 } 1519 1520 nonEmptyElements = editor.schema.getNonEmptyElements(); 1521 1522 // Prevent default logic since it's broken 1523 e.preventDefault(); 1524 1525 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1526 brElm = dom.create('br', {id: '__tmp'}); 1527 container.parentNode.insertBefore(brElm, container); 1528 1529 // Do the browser delete 1530 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1531 1532 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1533 container = selection.getRng().startContainer; 1534 sibling = container.previousSibling; 1535 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1536 dom.remove(sibling); 1537 } 1538 1539 // Remove the temp element we inserted 1540 dom.remove('__tmp'); 1541 } 1542 } 1543 }); 1544 } 1545
1546 function removeBlockQuoteOnBackSpace() { 1547 // Add block quote deletion handler 1548 editor.onKeyDown.add(function(editor, e) { 1549 var rng, container, offset, root, parent; 1550 1551 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { 1552 return; 1553 } 1554 1555 rng = selection.getRng(); 1556 container = rng.startContainer; 1557 offset = rng.startOffset; 1558 root = dom.getRoot(); 1559 parent = container; 1560 1561 if (!rng.collapsed || offset !== 0) { 1562 return; 1563 } 1564 1565 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1566 parent = parent.parentNode; 1567 } 1568 1569 // Is the cursor at the beginning of a blockquote? 1570 if (parent.tagName === 'BLOCKQUOTE') { 1571 // Remove the blockquote 1572 editor.formatter.toggle('blockquote', null, parent); 1573 1574 // Move the caret to the beginning of container 1575 rng = dom.createRng(); 1576 rng.setStart(container, 0); 1577 rng.setEnd(container, 0); 1578 selection.setRng(rng); 1579 } 1580 }); 1581 }; 1582 1583 function setGeckoEditingOptions() { 1584 function setOpts() { 1585 editor._refreshContentEditable(); 1586 1587 setEditorCommandState("StyleWithCSS", false); 1588 setEditorCommandState("enableInlineTableEditing", false); 1589 1590 if (!settings.object_resizing) { 1591 setEditorCommandState("enableObjectResizing", false); 1592 } 1593 }; 1594 1595 if (!settings.readonly) { 1596 editor.onBeforeExecCommand.add(setOpts); 1597 editor.onMouseDown.add(setOpts); 1598 } 1599 }; 1600 1601 function addBrAfterLastLinks() { 1602 function fixLinks(editor, o) { 1603 each(dom.select('a'), function(node) { 1604 var parentNode = node.parentNode, root = dom.getRoot(); 1605 1606 if (parentNode.lastChild === node) { 1607 while (parentNode && !dom.isBlock(parentNode)) { 1608 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1609 return; 1610 } 1611 1612 parentNode = parentNode.parentNode; 1613 } 1614 1615 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1616 } 1617 }); 1618 }; 1619 1620 editor.onExecCommand.add(function(editor, cmd) { 1621 if (cmd === 'CreateLink') { 1622 fixLinks(editor); 1623 } 1624 }); 1625 1626 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1627 }; 1628 1629 function setDefaultBlockType() { 1630 if (settings.forced_root_block) { 1631 editor.onInit.add(function() { 1632 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1633 }); 1634 } 1635 } 1636 1637 function removeGhostSelection() { 1638 function repaint(sender, args) { 1639 if (!sender || !args.initial) { 1640 editor.execCommand('mceRepaint'); 1641 } 1642 }; 1643 1644 editor.onUndo.add(repaint); 1645 editor.onRedo.add(repaint); 1646 editor.onSetContent.add(repaint); 1647 }; 1648 1649 function deleteControlItemOnBackSpace() { 1650 editor.onKeyDown.add(function(editor, e) { 1651 var rng; 1652 1653 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { 1654 rng = editor.getDoc().selection.createRange(); 1655 if (rng && rng.item) { 1656 e.preventDefault(); 1657 editor.undoManager.beforeChange(); 1658 dom.remove(rng.item(0)); 1659 editor.undoManager.add(); 1660 } 1661 } 1662 }); 1663 }; 1664 1665 function renderEmptyBlocksFix() { 1666 var emptyBlocksCSS; 1667 1668 // IE10+ 1669 if (getDocumentMode() >= 10) { 1670 emptyBlocksCSS = ''; 1671 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1672 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1673 }); 1674 1675 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1676 } 1677 }; 1678 1679 function fakeImageResize() { 1680 var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio, 1681 resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc(); 1682 1683 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1684 return; 1685 } 1686 1687 // Try disabling object resizing if WebKit implements resizing in the future 1688 setEditorCommandState("enableObjectResizing", false); 1689 1690 // Details about each resize handle how to scale etc 1691 resizeHandles = { 1692 // Name: x multiplier, y multiplier, delta size x, delta size y 1693 n: [.5, 0, 0, -1], 1694 e: [1, .5, 1, 0], 1695 s: [.5, 1, 0, 1], 1696 w: [0, .5, -1, 0], 1697 nw: [0, 0, -1, -1], 1698 ne: [1, 0, 1, -1], 1699 se: [1, 1, 1, 1], 1700 sw : [0, 1, -1, 1] 1701 }; 1702 1703 function resizeElement(e) { 1704 var deltaX, deltaY; 1705 1706 // Calc new width/height 1707 deltaX = e.screenX - startX; 1708 deltaY = e.screenY - startY; 1709 1710 // Calc new size 1711 width = deltaX * selectedHandle[2] + startW; 1712 height = deltaY * selectedHandle[3] + startH; 1713 1714 // Never scale down lower than 5 pixels 1715 width = width < 5 ? 5 : width; 1716 height = height < 5 ? 5 : height; 1717 1718 // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image 1719 if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) { 1720 width = Math.round(height / ratio); 1721 height = Math.round(width * ratio); 1722 } 1723 1724 // Update ghost size 1725 dom.setStyles(selectedElmGhost, { 1726 width: width, 1727 height: height 1728 }); 1729 1730 // Update ghost X position if needed 1731 if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { 1732 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); 1733 } 1734 1735 // Update ghost Y position if needed 1736 if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { 1737 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); 1738 } 1739 } 1740 1741 function endResize() { 1742 function setSizeProp(name, value) { 1743 if (value) { 1744 // Resize by using style or attribute 1745 if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { 1746 dom.setStyle(selectedElm, name, value); 1747 } else { 1748 dom.setAttrib(selectedElm, name, value); 1749 } 1750 } 1751 } 1752 1753 // Set width/height properties 1754 setSizeProp('width', width); 1755 setSizeProp('height', height); 1756 1757 dom.unbind(editableDoc, 'mousemove', resizeElement); 1758 dom.unbind(editableDoc, 'mouseup', endResize); 1759 1760 if (rootDocument != editableDoc) { 1761 dom.unbind(rootDocument, 'mousemove', resizeElement); 1762 dom.unbind(rootDocument, 'mouseup', endResize); 1763 } 1764 1765 // Remove ghost and update resize handle positions 1766 dom.remove(selectedElmGhost);
1767 showResizeRect(selectedElm); 1768 } 1769 1770 function showResizeRect(targetElm) { 1771 var position, targetWidth, targetHeight; 1772 1773 hideResizeRect(); 1774 1775 // Get position and size of target 1776 position = dom.getPos(targetElm); 1777 selectedElmX = position.x; 1778 selectedElmY = position.y; 1779 targetWidth = targetElm.offsetWidth; 1780 targetHeight = targetElm.offsetHeight; 1781 1782 // Reset width/height if user selects a new image/table 1783 if (selectedElm != targetElm) { 1784 selectedElm = targetElm; 1785 width = height = 0; 1786 } 1787 1788 each(resizeHandles, function(handle, name) { 1789 var handleElm; 1790 1791 // Get existing or render resize handle 1792 handleElm = dom.get('mceResizeHandle' + name); 1793 if (!handleElm) { 1794 handleElm = dom.add(editableDoc.documentElement, 'div', { 1795 id: 'mceResizeHandle' + name, 1796 'class': 'mceResizeHandle', 1797 style: 'cursor:' + name + '-resize; margin:0; padding:0' 1798 }); 1799 1800 dom.bind(handleElm, 'mousedown', function(e) { 1801 e.preventDefault(); 1802 1803 endResize(); 1804 1805 startX = e.screenX; 1806 startY = e.screenY; 1807 startW = selectedElm.clientWidth; 1808 startH = selectedElm.clientHeight; 1809 ratio = startH / startW; 1810 selectedHandle = handle; 1811 1812 selectedElmGhost = selectedElm.cloneNode(true); 1813 dom.addClass(selectedElmGhost, 'mceClonedResizable'); 1814 dom.setStyles(selectedElmGhost, { 1815 left: selectedElmX, 1816 top: selectedElmY, 1817 margin: 0 1818 }); 1819 1820 editableDoc.documentElement.appendChild(selectedElmGhost); 1821 1822 dom.bind(editableDoc, 'mousemove', resizeElement); 1823 dom.bind(editableDoc, 'mouseup', endResize); 1824 1825 if (rootDocument != editableDoc) { 1826 dom.bind(rootDocument, 'mousemove', resizeElement); 1827 dom.bind(rootDocument, 'mouseup', endResize); 1828 } 1829 }); 1830 } else { 1831 dom.show(handleElm); 1832 } 1833 1834 // Position element 1835 dom.setStyles(handleElm, { 1836 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), 1837 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) 1838 }); 1839 }); 1840 1841 // Only add resize rectangle on WebKit and only on images 1842 if (!tinymce.isOpera && selectedElm.nodeName == "IMG") { 1843 selectedElm.setAttribute('data-mce-selected', '1'); 1844 } 1845 } 1846 1847 function hideResizeRect() { 1848 if (selectedElm) { 1849 selectedElm.removeAttribute('data-mce-selected'); 1850 } 1851 1852 for (var name in resizeHandles) { 1853 dom.hide('mceResizeHandle' + name); 1854 } 1855 } 1856 1857 // Add CSS for resize handles, cloned element and selected 1858 editor.contentStyles.push( 1859 '.mceResizeHandle {' + 1860 'position: absolute;' + 1861 'border: 1px solid black;' + 1862 'background: #FFF;' + 1863 'width: 5px;' + 1864 'height: 5px;' + 1865 'z-index: 10000' + 1866 '}' + 1867 '.mceResizeHandle:hover {' + 1868 'background: #000' + 1869 '}' + 1870 'img[data-mce-selected] {' + 1871 'outline: 1px solid black' + 1872 '}' + 1873 'img.mceClonedResizable, table.mceClonedResizable {' + 1874 'position: absolute;' + 1875 'outline: 1px dashed black;' + 1876 'opacity: .5;' + 1877 'z-index: 10000' + 1878 '}' 1879 ); 1880 1881 function updateResizeRect() { 1882 var controlElm = dom.getParent(selection.getNode(), 'table,img'); 1883 1884 // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v 1885 each(dom.select('img[data-mce-selected]'), function(img) { 1886 img.removeAttribute('data-mce-selected'); 1887 }); 1888 1889 if (controlElm) { 1890 showResizeRect(controlElm); 1891 } else { 1892 hideResizeRect(); 1893 } 1894 } 1895 1896 // Show/hide resize rect when image is selected 1897 editor.onNodeChange.add(updateResizeRect); 1898 1899 // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container 1900 dom.bind(editableDoc, 'selectionchange', updateResizeRect); 1901 1902 // Remove the internal attribute when serializing the DOM 1903 editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) { 1904 var i = nodes.length; 1905 1906 while (i--) { 1907 nodes[i].attr(name, null); 1908 } 1909 }); 1910 } 1911 1912 function keepNoScriptContents() { 1913 if (getDocumentMode() < 9) { 1914 parser.addNodeFilter('noscript', function(nodes) { 1915 var i = nodes.length, node, textNode; 1916 1917 while (i--) { 1918 node = nodes[i]; 1919 textNode = node.firstChild; 1920 1921 if (textNode) { 1922 node.attr('data-mce-innertext', textNode.value); 1923 } 1924 } 1925 }); 1926 1927 serializer.addNodeFilter('noscript', function(nodes) { 1928 var i = nodes.length, node, textNode, value; 1929 1930 while (i--) { 1931 node = nodes[i]; 1932 textNode = nodes[i].firstChild; 1933 1934 if (textNode) { 1935 textNode.value = tinymce.html.Entities.decode(textNode.value); 1936 } else { 1937 // Old IE can't retain noscript value so an attribute is used to store it 1938 value = node.attributes.map['data-mce-innertext']; 1939 if (value) { 1940 node.attr('data-mce-innertext', null); 1941 textNode = new tinymce.html.Node('#text', 3); 1942 textNode.value = value; 1943 textNode.raw = true; 1944 node.append(textNode); 1945 } 1946 } 1947 } 1948 }); 1949 } 1950 } 1951 1952 function bodyHeight() { 1953 editor.contentStyles.push('body {min-height: 100px}'); 1954 editor.onClick.add(function(ed, e) { 1955 if (e.target.nodeName == 'HTML') { 1956 editor.execCommand('SelectAll'); 1957 editor.selection.collapse(true); 1958 editor.nodeChanged(); 1959 } 1960 }); 1961 } 1962 1963 function fixControlSelection() { 1964 editor.onInit.add(function() { 1965 var selectedRng; 1966
1967 editor.getBody().addEventListener('mscontrolselect', function(e) { 1968 setTimeout(function() { 1969 if (editor.selection.getNode() != e.target) { 1970 selectedRng = editor.selection.getRng(); 1971 selection.fakeRng = editor.dom.createRng(); 1972 selection.fakeRng.setStartBefore(e.target); 1973 selection.fakeRng.setEndAfter(e.target); 1974 } 1975 }, 0); 1976 }, false); 1977 1978 editor.getDoc().addEventListener('selectionchange', function(e) { 1979 if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) { 1980 selection.fakeRng = selectedRng = null; 1981 } 1982 }, false); 1983 }); 1984 } 1985 1986 // All browsers 1987 disableBackspaceIntoATable(); 1988 removeBlockQuoteOnBackSpace(); 1989 emptyEditorWhenDeleting(); 1990 1991 // WebKit 1992 if (tinymce.isWebKit) { 1993 keepInlineElementOnDeleteBackspace(); 1994 cleanupStylesWhenDeleting(); 1995 inputMethodFocus(); 1996 selectControlElements(); 1997 setDefaultBlockType(); 1998 1999 // iOS 2000 if (tinymce.isIDevice) { 2001 selectionChangeNodeChanged(); 2002 } else { 2003 fakeImageResize(); 2004 selectAll(); 2005 } 2006 } 2007 2008 // IE 2009 if (tinymce.isIE && !tinymce.isIE11) { 2010 removeHrOnBackspace(); 2011 ensureBodyHasRoleApplication(); 2012 addNewLinesBeforeBrInPre(); 2013 removePreSerializedStylesWhenSelectingControls(); 2014 deleteControlItemOnBackSpace(); 2015 renderEmptyBlocksFix(); 2016 keepNoScriptContents(); 2017 } 2018 2019 // IE 11+ 2020 if (tinymce.isIE11) { 2021 bodyHeight(); 2022 fixControlSelection(); 2023 } 2024 2025 // Gecko 2026 if (tinymce.isGecko && !tinymce.isIE11) { 2027 removeHrOnBackspace(); 2028 focusBody(); 2029 removeStylesWhenDeletingAccrossBlockElements(); 2030 setGeckoEditingOptions(); 2031 addBrAfterLastLinks(); 2032 removeGhostSelection(); 2033 } 2034 2035 // Opera 2036 if (tinymce.isOpera) { 2037 fakeImageResize(); 2038 } 2039}; 2040(function(tinymce) { 2041 var namedEntities, baseEntities, reverseEntities, 2042 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2043 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 2044 rawCharsRegExp = /[<>&\"\']/g, 2045 entityRegExp = /&(#x|#)?([\w]+);/g, 2046 asciiMap = { 2047 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 2048 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 2049 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 2050 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 2051 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 2052 }; 2053 2054 // Raw entities 2055 baseEntities = { 2056 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 2057 "'" : ''', 2058 '<' : '<', 2059 '>' : '>', 2060 '&' : '&' 2061 }; 2062 2063 // Reverse lookup table for raw entities 2064 reverseEntities = { 2065 '<' : '<', 2066 '>' : '>', 2067 '&' : '&', 2068 '"' : '"', 2069 ''' : "'" 2070 }; 2071 2072 // Decodes text by using the browser 2073 function nativeDecode(text) { 2074 var elm; 2075 2076 elm = document.createElement("div"); 2077 elm.innerHTML = text; 2078 2079 return elm.textContent || elm.innerText || text; 2080 }; 2081 2082 // Build a two way lookup table for the entities 2083 function buildEntitiesLookup(items, radix) { 2084 var i, chr, entity, lookup = {}; 2085 2086 if (items) { 2087 items = items.split(','); 2088 radix = radix || 10; 2089 2090 // Build entities lookup table 2091 for (i = 0; i < items.length; i += 2) { 2092 chr = String.fromCharCode(parseInt(items[i], radix)); 2093 2094 // Only add non base entities 2095 if (!baseEntities[chr]) { 2096 entity = '&' + items[i + 1] + ';'; 2097 lookup[chr] = entity; 2098 lookup[entity] = chr; 2099 } 2100 } 2101 2102 return lookup; 2103 } 2104 }; 2105 2106 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 2107 namedEntities = buildEntitiesLookup( 2108 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 2109 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 2110 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 2111 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 2112 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 2113 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 2114 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 2115 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 2116 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 2117 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 2118 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 2119 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 2120 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 2121 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 2122 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 2123 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 2124 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 2125 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 2126 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 2127 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8
2127k4,' + 2128 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 2129 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 2130 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 2131 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 2132 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 2133 2134 tinymce.html = tinymce.html || {}; 2135 2136 tinymce.html.Entities = { 2137 encodeRaw : function(text, attr) { 2138 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2139 return baseEntities[chr] || chr; 2140 }); 2141 }, 2142 2143 encodeAllRaw : function(text) { 2144 return ('' + text).replace(rawCharsRegExp, function(chr) { 2145 return baseEntities[chr] || chr; 2146 }); 2147 }, 2148 2149 encodeNumeric : function(text, attr) { 2150 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2151 // Multi byte sequence convert it to a single entity 2152 if (chr.length > 1) 2153 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 2154 2155 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 2156 }); 2157 }, 2158 2159 encodeNamed : function(text, attr, entities) { 2160 entities = entities || namedEntities; 2161 2162 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2163 return baseEntities[chr] || entities[chr] || chr; 2164 }); 2165 }, 2166 2167 getEncodeFunc : function(name, entities) { 2168 var Entities = tinymce.html.Entities; 2169 2170 entities = buildEntitiesLookup(entities) || namedEntities; 2171 2172 function encodeNamedAndNumeric(text, attr) { 2173 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 2174 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 2175 }); 2176 }; 2177 2178 function encodeCustomNamed(text, attr) { 2179 return Entities.encodeNamed(text, attr, entities); 2180 }; 2181 2182 // Replace + with , to be compatible with previous TinyMCE versions 2183 name = tinymce.makeMap(name.replace(/\+/g, ',')); 2184 2185 // Named and numeric encoder 2186 if (name.named && name.numeric) 2187 return encodeNamedAndNumeric; 2188 2189 // Named encoder 2190 if (name.named) { 2191 // Custom names 2192 if (entities) 2193 return encodeCustomNamed; 2194 2195 return Entities.encodeNamed; 2196 } 2197 2198 // Numeric 2199 if (name.numeric) 2200 return Entities.encodeNumeric; 2201 2202 // Raw encoder 2203 return Entities.encodeRaw; 2204 }, 2205 2206 decode : function(text) { 2207 return text.replace(entityRegExp, function(all, numeric, value) { 2208 if (numeric) { 2209 value = parseInt(value, numeric.length === 2 ? 16 : 10); 2210 2211 // Support upper UTF 2212 if (value > 0xFFFF) { 2213 value -= 0x10000; 2214 2215 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 2216 } else 2217 return asciiMap[value] || String.fromCharCode(value); 2218 } 2219 2220 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 2221 }); 2222 } 2223 }; 2224})(tinymce); 2225 2226tinymce.html.Styles = function(settings, schema) { 2227 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 2228 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 2229 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 2230 trimRightRegExp = /\s+$/, 2231 urlColorRegExp = /rgb/, 2232 undef, i, encodingLookup = {}, encodingItems; 2233 2234 settings = settings || {}; 2235 2236 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 2237 for (i = 0; i < encodingItems.length; i++) { 2238 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 2239 encodingLookup['\uFEFF' + i] = encodingItems[i]; 2240 } 2241 2242 function toHex(match, r, g, b) { 2243 function hex(val) { 2244 val = parseInt(val).toString(16); 2245 2246 return val.length > 1 ? val : '0' + val; // 0 -> 00 2247 }; 2248 2249 return '#' + hex(r) + hex(g) + hex(b); 2250 }; 2251 2252 return { 2253 toHex : function(color) { 2254 return color.replace(rgbRegExp, toHex); 2255 }, 2256 2257 parse : function(css) { 2258 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 2259 2260 function compress(prefix, suffix) { 2261 var top, right, bottom, left; 2262 2263 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 2264 // So lets asume it shouldn't be there 2265 if (styles['border-image'] === 'none') { 2266 delete styles['border-image']; 2267 } 2268 2269 // Get values and check it it needs compressing 2270 top = styles[prefix + '-top' + suffix]; 2271 if (!top) 2272 return; 2273 2274 right = styles[prefix + '-right' + suffix]; 2275 if (top != right) 2276 return; 2277 2278 bottom = styles[prefix + '-bottom' + suffix]; 2279 if (right != bottom) 2280 return; 2281 2282 left = styles[prefix + '-left' + suffix]; 2283 if (bottom != left) 2284 return; 2285 2286 // Compress 2287 styles[prefix + suffix] = left; 2288 delete styles[prefix + '-top' + suffix]; 2289 delete styles[prefix + '-right' + suffix]; 2290 delete styles[prefix + '-bottom' + suffix]; 2291 delete styles[prefix + '-left' + suffix]; 2292 }; 2293 2294 function canCompress(key) { 2295 var value = styles[key], i; 2296 2297 if (!value || value.indexOf(' ') < 0) 2298 return; 2299 2300 value = value.split(' '); 2301 i = value.length; 2302 while (i--) { 2303 if (value[i] !== value[0]) 2304 return false;
2305 } 2306 2307 styles[key] = value[0]; 2308 2309 return true; 2310 }; 2311 2312 function compress2(target, a, b, c) { 2313 if (!canCompress(a)) 2314 return; 2315 2316 if (!canCompress(b)) 2317 return; 2318 2319 if (!canCompress(c)) 2320 return; 2321 2322 // Compress 2323 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2324 delete styles[a]; 2325 delete styles[b]; 2326 delete styles[c]; 2327 }; 2328 2329 // Encodes the specified string by replacing all \" \' ; : with _<num> 2330 function encode(str) { 2331 isEncoded = true; 2332 2333 return encodingLookup[str]; 2334 }; 2335 2336 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2337 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2338 function decode(str, keep_slashes) { 2339 if (isEncoded) { 2340 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2341 return encodingLookup[str]; 2342 }); 2343 } 2344 2345 if (!keep_slashes) 2346 str = str.replace(/\\([\'\";:])/g, "$1"); 2347 2348 return str; 2349 }; 2350 2351 function processUrl(match, url, url2, url3, str, str2) { 2352 str = str || str2; 2353 2354 if (str) { 2355 str = decode(str); 2356 2357 // Force strings into single quote format 2358 return "'" + str.replace(/\'/g, "\\'") + "'"; 2359 } 2360 2361 url = decode(url || url2 || url3); 2362 2363 // Convert the URL to relative/absolute depending on config 2364 if (urlConverter) 2365 url = urlConverter.call(urlConverterScope, url, 'style'); 2366 2367 // Output new URL format 2368 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2369 }; 2370 2371 if (css) { 2372 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2373 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2374 return str.replace(/[;:]/g, encode); 2375 }); 2376 2377 // Parse styles 2378 while (matches = styleRegExp.exec(css)) { 2379 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2380 value = matches[2].replace(trimRightRegExp, ''); 2381 2382 if (name && value.length > 0) { 2383 // Opera will produce 700 instead of bold in their style values 2384 if (name === 'font-weight' && value === '700') 2385 value = 'bold'; 2386 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2387 value = value.toLowerCase(); 2388 2389 // Convert RGB colors to HEX 2390 value = value.replace(rgbRegExp, toHex); 2391 2392 // Convert URLs and force them into url('value') format 2393 value = value.replace(urlOrStrRegExp, processUrl); 2394 styles[name] = isEncoded ? decode(value, true) : value; 2395 } 2396 2397 styleRegExp.lastIndex = matches.index + matches[0].length; 2398 } 2399 2400 // Compress the styles to reduce it's size for example IE will expand styles 2401 compress("border", ""); 2402 compress("border", "-width"); 2403 compress("border", "-color"); 2404 compress("border", "-style"); 2405 compress("padding", ""); 2406 compress("margin", ""); 2407 compress2('border', 'border-width', 'border-style', 'border-color'); 2408 2409 // Remove pointless border, IE produces these 2410 if (styles.border === 'medium none') 2411 delete styles.border; 2412 } 2413 2414 return styles; 2415 }, 2416 2417 serialize : function(styles, element_name) { 2418 var css = '', name, value; 2419 2420 function serializeStyles(name) { 2421 var styleList, i, l, value; 2422 2423 styleList = schema.styles[name]; 2424 if (styleList) { 2425 for (i = 0, l = styleList.length; i < l; i++) { 2426 name = styleList[i]; 2427 value = styles[name]; 2428 2429 if (value !== undef && value.length > 0) 2430 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2431 } 2432 } 2433 }; 2434 2435 // Serialize styles according to schema 2436 if (element_name && schema && schema.styles) { 2437 // Serialize global styles and element specific styles 2438 serializeStyles('*'); 2439 serializeStyles(element_name); 2440 } else { 2441 // Output the styles in the order they are inside the object 2442 for (name in styles) { 2443 value = styles[name]; 2444 2445 if (value !== undef && value.length > 0) 2446 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2447 } 2448 } 2449 2450 return css; 2451 } 2452 }; 2453}; 2454 2455(function(tinymce) { 2456 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2457 2458 function split(str, delim) { 2459 return str.split(delim || ','); 2460 }; 2461 2462 function unpack(lookup, data) { 2463 var key, elements = {}; 2464 2465 function replace(value) { 2466 return value.replace(/[A-Z]+/g, function(key) { 2467 return replace(lookup[key]); 2468 }); 2469 }; 2470 2471 // Unpack lookup 2472 for (key in lookup) { 2473 if (lookup.hasOwnProperty(key)) 2474 lookup[key] = replace(lookup[key]); 2475 } 2476 2477 // Unpack and parse data into object map 2478 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2479 attributes = split(attributes, '|'); 2480 2481 elements[name] = { 2482 attributes : makeMap(attributes), 2483 attributesOrder : attributes, 2484 children : makeMap(children, '|', {'#comment' : {}}) 2485 } 2486 }); 2487 2488 return elements; 2489 }; 2490 2491 function getHTML5() { 2492 var html5 = mapCache.html5; 2493 2494 if (!html5) { 2495 html5 = mapCache.html5 = unpack({ 2496 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2497 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|' + 2498 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2499 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|' + 2500 '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|' + 2501 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2502 }, 'html[A|manifest][body|head]' + 2503 'head[A][base|command|link|meta|noscript|script|style|title]' + 2504 'title[A][#]' + 2505 'base[A|href|target][]' + 2506 'link[A|href|rel|media|type|sizes][]' + 2507 'meta[A|http-equiv|name|content|charset][]' +
2508 'style[A|type|media|scoped][#]' + 2509 'script[A|charset|type|src|defer|async][#]' + 2510 'noscript[A][C]' + 2511 'body[A][C]' + 2512 'section[A][C]' + 2513 'nav[A][C]' + 2514 'article[A][C]' + 2515 'aside[A][C]' + 2516 'h1[A][B]' + 2517 'h2[A][B]' + 2518 'h3[A][B]' + 2519 'h4[A][B]' + 2520 'h5[A][B]' + 2521 'h6[A][B]' + 2522 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2523 'header[A][C]' + 2524 'footer[A][C]' + 2525 'address[A][C]' + 2526 'p[A][B]' + 2527 'br[A][]' + 2528 'pre[A][B]' + 2529 'dialog[A][dd|dt]' + 2530 'blockquote[A|cite][C]' + 2531 'ol[A|start|reversed][li]' + 2532 'ul[A][li]' + 2533 'li[A|value][C]' + 2534 'dl[A][dd|dt]' + 2535 'dt[A][B]' + 2536 'dd[A][C]' + 2537 'a[A|href|target|ping|rel|media|type][B]' + 2538 'em[A][B]' + 2539 'strong[A][B]' + 2540 'small[A][B]' + 2541 'cite[A][B]' + 2542 'q[A|cite][B]' + 2543 'dfn[A][B]' + 2544 'abbr[A][B]' + 2545 'code[A][B]' + 2546 'var[A][B]' + 2547 'samp[A][B]' + 2548 'kbd[A][B]' + 2549 'sub[A][B]' + 2550 'sup[A][B]' + 2551 'i[A][B]' + 2552 'b[A][B]' + 2553 'mark[A][B]' + 2554 'progress[A|value|max][B]' + 2555 'meter[A|value|min|max|low|high|optimum][B]' + 2556 'time[A|datetime][B]' + 2557 'ruby[A][B|rt|rp]' + 2558 'rt[A][B]' + 2559 'rp[A][B]' + 2560 'bdo[A][B]' + 2561 'span[A][B]' + 2562 'ins[A|cite|datetime][B]' + 2563 'del[A|cite|datetime][B]' + 2564 'figure[A][C|legend|figcaption]' + 2565 'figcaption[A][C]' + 2566 'img[A|alt|src|height|width|usemap|ismap][]' + 2567 'iframe[A|name|src|height|width|sandbox|seamless|allowfullscreen][]' + 2568 'embed[A|src|height|width|type][]' + 2569 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2570 'param[A|name|value][]' + 2571 'details[A|open][C|legend]' + 2572 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2573 'menu[A|type|label][C|li]' + 2574 'legend[A][C|B]' + 2575 'div[A][C]' + 2576 'source[A|src|type|media][]' + 2577 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2578 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2579 'hr[A][]' + 2580 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2581 'fieldset[A|disabled|form|name][C|legend]' + 2582 'label[A|form|for][B]' + 2583 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2584 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2585 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2586 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2587 'datalist[A][B|option]' + 2588 'optgroup[A|disabled|label][option]' + 2589 'option[A|disabled|selected|label|value][]' + 2590 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2591 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2592 'output[A|for|form|name][B]' + 2593 'canvas[A|width|height][]' + 2594 'map[A|name][B|C]' + 2595 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2596 'mathml[A][]' + 2597 'svg[A][]' + 2598 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2599 'caption[A][C]' + 2600 'colgroup[A|span][col]' + 2601 'col[A|span][]' + 2602 'thead[A][tr]' + 2603 'tfoot[A][tr]' + 2604 'tbody[A][tr]' + 2605 'tr[A][th|td]' + 2606 'th[A|headers|rowspan|colspan|scope][B]' + 2607 'td[A|headers|rowspan|colspan][C]' + 2608 'wbr[A][]' 2609 ); 2610 } 2611 2612 return html5; 2613 }; 2614 2615 function getHTML4() { 2616 var html4 = mapCache.html4; 2617 2618 if (!html4) { 2619 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2620 html4 = mapCache.html4 = unpack({ 2621 Z : 'H|K|N|O|P', 2622 Y : 'X|form|R|Q', 2623 ZG : 'E|span|width|align|char|charoff|valign', 2624 X : 'p|T|div|U|W|isindex|fieldset|table', 2625 ZF : 'E|align|char|charoff|valign', 2626 W : 'pre|hr|blockquote|address|center|noframes', 2627 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2628 ZD : '[E][S]', 2629 U : 'ul|ol|dl|menu|dir', 2630 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2631 T : 'h1|h2|h3|h4|h5|h6', 2632 ZB : 'X|S|Q', 2633 S : 'R|P', 2634 ZA : 'a|G|J|M|O|P', 2635 R : 'a|H|K|N|O', 2636 Q : 'noscript|P', 2637 P : 'ins|del|script', 2638 O : 'input|select|textarea|label|button', 2639 N : 'M|L', 2640 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2641 L : 'sub|sup', 2642 K : 'J|I', 2643 J : 'tt|i|b|u|s|strike', 2644 I : 'big|small|font|basefont', 2645 H : 'G|F', 2646 G : 'br|span|bdo', 2647 F : 'object|applet|img|map|iframe', 2648 E : 'A|B|C', 2649 D : 'accesskey|tabindex|onfocus|onblur', 2650 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2651 B : 'lang|xml:lang|dir', 2652 A : 'id|class|style|title' 2653 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2654 'style[B|id|type|media|title|xml:space][]' + 2655 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2656 'param[id|name|value|valuetype|type][]' + 2657 'p[E|align][#|S]' + 2658 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2659 'br[A|clear][]' + 2660 'span[E][#|S]' + 2661 'bdo[A|C|B][#|S]' + 2662 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2663 'h1[E|align][#|S]' + 2664 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2665 'map[B|C|A|name][X|form|Q|area]' + 2666 'h2[E|align][#|S]' + 2667 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height|allowfullscreen][#|Y]' + 2668 'h3[E|align][#|S]' + 2669 'tt[E][#|S]' + 2670 'i[E][#|S]' + 2671 'b[E][#|S]' + 2672 'u[E][#|S]' + 2673 's[E][#|S]' + 2674 'strike[E][#|S]' + 2675 'big[E][#|S]' + 2676 'small[E][#|S]' + 2677 'font[A|B|size|color|face][#|S]' + 2678 'basefont[id|size|color|face][]' + 2679 'em[E][#|S]' + 2680 'strong[E][#|S]' + 2681 'dfn[E][#|S]' + 2682 'code[E][#|S]' + 2683 'q[E|cite][#|S]' + 2684 'samp[E][#|S]' + 2685 'kbd[E][#|S]' + 2686 'var[E][#|S]' + 2687 'cite[E][#|S]' + 2688 'abbr[E][#|S]' + 2689 'acronym[E][#|S]' + 2690 'sub[E][#|S]' + 2691 'sup[E][#|S]' + 2692 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2693 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2694 'optgroup[E|disabled|label][option]' + 2695 'option[E|selected|disabled|label|value][]' + 2696 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2697 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2698 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2699 'h4[E|align][#|S]' + 2700 'ins[E|cite|datetime][#|Y]' + 2701 'h5[E|align][#|S]' + 2702 'del[E|cite|datetime][#|Y]' + 2703 'h6[E|align][#|S]' + 2704 'div[E|align][#|Y]' + 2705 'ul[E|type|compact][li]' + 2706 'li[E|type|value][#|Y]' + 2707 'ol[E|type|compact|start][li]' + 2708 'dl[E|compact][dt|dd]' + 2709 'dt[E][#|S]' + 2710 'dd[E][#|Y]' + 2711 'menu[E|compact][li]' + 2712 'dir[E|compact][li]' + 2713 'pre[E|width|xml:space][#|ZA]' + 2714 'hr[E|align|noshade|size|width][]' + 2715 'blockquote[E|cite][#|Y]' + 2716 'address[E][#|S|p]' + 2717 'center[E][#|Y]' + 2718 'noframes[E][#|Y]' + 2719 'isindex[A|B|prompt][]' + 2720 'fieldset[E][#|legend|Y]' + 2721 'legend[E|accesskey|align][#|S]' + 2722 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2723 'caption[E|align][#|S]' + 2724 'col[ZG][]' + 2725 'colgroup[ZG][col]' + 2726 'thead[ZF][tr]' + 2727 'tr[ZF|bgcolor][th|td]' + 2728 'th[E|ZE][#|Y]' + 2729 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2730 'noscript[E][#|Y]' + 2731 'td[E|ZE][#|Y]' + 2732 'tfoot[ZF][tr]' + 2733 'tbody[ZF][tr]' + 2734 'area[E|D|shape|coords|href|nohref|alt|target][]' +
2735 'base[id|href|target][]' + 2736 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2737 ); 2738 } 2739 2740 return html4; 2741 }; 2742 2743 tinymce.html.Schema = function(settings) { 2744 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2745 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2746 2747 // Creates an lookup table map object for the specified option or the default value 2748 function createLookupTable(option, default_value, extend) { 2749 var value = settings[option]; 2750 2751 if (!value) { 2752 // Get cached default map or make it if needed 2753 value = mapCache[option]; 2754 2755 if (!value) { 2756 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2757 value = tinymce.extend(value, extend); 2758 2759 mapCache[option] = value; 2760 } 2761 } else { 2762 // Create custom map 2763 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2764 } 2765 2766 return value; 2767 }; 2768 2769 settings = settings || {}; 2770 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2771 2772 // 11:41 17.03.2025 szybki fix, zeby TinyMCE nie wycinal znacznikow <time> (no i innych tez) 2773 // Allow all elements and attributes if verify_html is set to false 2774 //if (settings.verify_html === false) 2775 { 2776 settings.valid_elements = '*[*]'; 2777 2778 settings.verify_html = false; 2779 settings.cleanup = false; 2780 settings.invalid_elements = ""; 2781 } 2782 2783 // Build styles list 2784 if (settings.valid_styles) { 2785 validStyles = {}; 2786 2787 // Convert styles into a rule list 2788 each(settings.valid_styles, function(value, key) { 2789 validStyles[key] = tinymce.explode(value); 2790 }); 2791 } 2792 2793 // Setup map objects 2794 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea'); 2795 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2796 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2797 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2798 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script div', shortEndedElementsMap); 2799 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 2800 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 2801 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 2802 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap); 2803 2804 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2805 function patternToRegExp(str) { 2806 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2807 }; 2808 2809 // Parses the specified valid_elements string and adds to the current rules 2810 // This function is a bit hard to read since it's heavily optimized for speed 2811 function addValidElements(valid_elements) { 2812 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2813 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2814 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2815 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2816 hasPatternsRegExp = /[*?+]/; 2817 2818 if (valid_elements) { 2819 // Split valid elements into an array with rules 2820 valid_elements = split(valid_elements); 2821 2822 if (elements['@']) { 2823 globalAttributes = elements['@'].attributes; 2824 globalAttributesOrder = elements['@'].attributesOrder; 2825 } 2826 2827 // Loop all rules 2828 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2829 // Parse element rule 2830 matches = elementRuleRegExp.exec(valid_elements[ei]); 2831 if (matches) { 2832 // Setup local names for matches 2833 prefix = matches[1]; 2834 elementName = matches[2]; 2835 outputName = matches[3]; 2836 attrData = matches[4]; 2837 2838 // Create new attributes and attributesOrder 2839 attributes = {}; 2840 attributesOrder = []; 2841 2842 // Create the new element 2843 element = { 2844 attributes : attributes, 2845 attributesOrder : attributesOrder 2846 }; 2847 2848 // Padd empty elements prefix 2849 if (prefix === '#') 2850 element.paddEmpty = true; 2851 2852 // Remove empty elements prefix 2853 if (prefix === '-') 2854 element.removeEmpty = true; 2855 2856 // Copy attributes from global rule into current rule 2857 if (globalAttributes) { 2858 for (key in globalAttributes) 2859 attributes[key] = globalAttributes[key]; 2860 2861 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2862 } 2863 2864 // Attributes defined 2865 if (attrData) { 2866 attrData = split(attrData, '|'); 2867 for (ai = 0, al = attrData.length; ai < al; ai++) { 2868 matches = attrRuleRegExp.exec(attrData[ai]); 2869 if (matches) { 2870 attr = {}; 2871 attrType = matches[1]; 2872 attrName = matches[2].replace(/::/g, ':'); 2873 prefix = matches[3]; 2874 value = matches[4]; 2875 2876 // Required 2877 if (attrType === '!') { 2878 element.attributesRequired = element.attributesRequired || []; 2879 element.attributesRequired.push(attrName); 2880 attr.required = true; 2881 } 2882
2883 // Denied from global 2884 if (attrType === '-') { 2885 delete attributes[attrName]; 2886 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2887 continue; 2888 } 2889 2890 // Default value 2891 if (prefix) { 2892 // Default value 2893 if (prefix === '=') { 2894 element.attributesDefault = element.attributesDefault || []; 2895 element.attributesDefault.push({name: attrName, value: value}); 2896 attr.defaultValue = value; 2897 } 2898 2899 // Forced value 2900 if (prefix === ':') { 2901 element.attributesForced = element.attributesForced || []; 2902 element.attributesForced.push({name: attrName, value: value}); 2903 attr.forcedValue = value; 2904 } 2905 2906 // Required values 2907 if (prefix === '<') 2908 attr.validValues = makeMap(value, '?'); 2909 } 2910 2911 // Check for attribute patterns 2912 if (hasPatternsRegExp.test(attrName)) { 2913 element.attributePatterns = element.attributePatterns || []; 2914 attr.pattern = patternToRegExp(attrName); 2915 element.attributePatterns.push(attr); 2916 } else { 2917 // Add attribute to order list if it doesn't already exist 2918 if (!attributes[attrName]) 2919 attributesOrder.push(attrName); 2920 2921 attributes[attrName] = attr; 2922 } 2923 } 2924 } 2925 } 2926 2927 // Global rule, store away these for later usage 2928 if (!globalAttributes && elementName == '@') { 2929 globalAttributes = attributes; 2930 globalAttributesOrder = attributesOrder; 2931 } 2932 2933 // Handle substitute elements such as b/strong 2934 if (outputName) { 2935 element.outputName = elementName; 2936 elements[outputName] = element; 2937 } 2938 2939 // Add pattern or exact element 2940 if (hasPatternsRegExp.test(elementName)) { 2941 element.pattern = patternToRegExp(elementName); 2942 patternElements.push(element); 2943 } else 2944 elements[elementName] = element; 2945 } 2946 } 2947 } 2948 }; 2949 2950 function setValidElements(valid_elements) { 2951 elements = {}; 2952 patternElements = []; 2953 2954 addValidElements(valid_elements); 2955 2956 each(schemaItems, function(element, name) { 2957 children[name] = element.children; 2958 }); 2959 }; 2960 2961 // Adds custom non HTML elements to the schema 2962 function addCustomElements(custom_elements) { 2963 var customElementRegExp = /^(~)?(.+)$/; 2964 2965 if (custom_elements) { 2966 each(split(custom_elements), function(rule) { 2967 var matches = customElementRegExp.exec(rule), 2968 inline = matches[1] === '~', 2969 cloneName = inline ? 'span' : 'div', 2970 name = matches[2]; 2971 2972 children[name] = children[cloneName]; 2973 customElementsMap[name] = cloneName; 2974 2975 // If it's not marked as inline then add it to valid block elements 2976 if (!inline) { 2977 blockElementsMap[name.toUpperCase()] = {}; 2978 blockElementsMap[name] = {}; 2979 } 2980 2981 // Add elements clone if needed 2982 if (!elements[name]) { 2983 elements[name] = elements[cloneName]; 2984 } 2985 2986 // Add custom elements at span/div positions 2987 each(children, function(element, child) { 2988 if (element[cloneName]) 2989 element[name] = element[cloneName]; 2990 }); 2991 }); 2992 } 2993 }; 2994 2995 // Adds valid children to the schema object 2996 function addValidChildren(valid_children) { 2997 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2998 2999 if (valid_children) { 3000 each(split(valid_children), function(rule) { 3001 var matches = childRuleRegExp.exec(rule), parent, prefix; 3002 3003 if (matches) { 3004 prefix = matches[1]; 3005 3006 // Add/remove items from default 3007 if (prefix) 3008 parent = children[matches[2]]; 3009 else 3010 parent = children[matches[2]] = {'#comment' : {}}; 3011 3012 parent = children[matches[2]]; 3013 3014 each(split(matches[3], '|'), function(child) { 3015 if (prefix === '-') 3016 delete parent[child]; 3017 else 3018 parent[child] = {}; 3019 }); 3020 } 3021 }); 3022 } 3023 }; 3024 3025 function getElementRule(name) { 3026 var element = elements[name], i; 3027 3028 // Exact match found 3029 if (element) 3030 return element; 3031 3032 // No exact match then try the patterns 3033 i = patternElements.length; 3034 while (i--) { 3035 element = patternElements[i]; 3036 3037 if (element.pattern.test(name)) 3038 return element; 3039 } 3040 }; 3041
3042 if (!settings.valid_elements) { 3043 // No valid elements defined then clone the elements from the schema spec 3044 each(schemaItems, function(element, name) { 3045 elements[name] = { 3046 attributes : element.attributes, 3047 attributesOrder : element.attributesOrder 3048 }; 3049 3050 children[name] = element.children; 3051 }); 3052 3053 // Switch these on HTML4 3054 if (settings.schema != "html5") { 3055 each(split('strong/b,em/i'), function(item) { 3056 item = split(item, '/'); 3057 elements[item[1]].outputName = item[0]; 3058 }); 3059 } 3060 3061 // Add default alt attribute for images 3062 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 3063 3064 // Remove these if they are empty by default 3065 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i,div'), function(name) { 3066 if (elements[name]) { 3067 elements[name].removeEmpty = true; 3068 } 3069 }); 3070 3071 // Padd these by default 3072 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,address,caption'), function(name) { 3073 elements[name].paddEmpty = true; 3074 }); 3075 } else 3076 setValidElements(settings.valid_elements); 3077 3078 addCustomElements(settings.custom_elements); 3079 addValidChildren(settings.valid_children); 3080 addValidElements(settings.extended_valid_elements); 3081 3082 // Todo: Remove this when we fix list handling to be valid 3083 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 3084 3085 // Delete invalid elements 3086 if (settings.invalid_elements) { 3087 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 3088 if (elements[item]) 3089 delete elements[item]; 3090 }); 3091 } 3092 3093 // If the user didn't allow span only allow internal spans 3094 if (!getElementRule('span')) 3095 addValidElements('span[!data-mce-type|*]'); 3096 3097 self.children = children; 3098 3099 self.styles = validStyles; 3100 3101 self.getBoolAttrs = function() { 3102 return boolAttrMap; 3103 }; 3104 3105 self.getBlockElements = function() { 3106 return blockElementsMap; 3107 }; 3108 3109 self.getTextBlockElements = function() { 3110 return textBlockElementsMap; 3111 }; 3112 3113 self.getShortEndedElements = function() { 3114 return shortEndedElementsMap; 3115 }; 3116 3117 self.getSelfClosingElements = function() { 3118 return selfClosingElementsMap; 3119 }; 3120 3121 self.getNonEmptyElements = function() { 3122 return nonEmptyElementsMap; 3123 }; 3124 3125 self.getWhiteSpaceElements = function() { 3126 return whiteSpaceElementsMap; 3127 }; 3128 3129 self.isValidChild = function(name, child) { 3130 var parent = children[name]; 3131 3132 return !!(parent && parent[child]); 3133 }; 3134 3135 self.isValid = function(name, attr) { 3136 var attrPatterns, i, rule = getElementRule(name); 3137 3138 // Check if it's a valid element 3139 if (rule) { 3140 if (attr) { 3141 // Check if attribute name exists 3142 if (rule.attributes[attr]) { 3143 return true; 3144 } 3145 3146 // Check if attribute matches a regexp pattern 3147 attrPatterns = rule.attributePatterns; 3148 if (attrPatterns) { 3149 i = attrPatterns.length; 3150 while (i--) { 3151 if (attrPatterns[i].pattern.test(name)) { 3152 return true; 3153 } 3154 } 3155 } 3156 } else { 3157 return true; 3158 } 3159 } 3160 3161 // No match 3162 return false; 3163 }; 3164
3165 self.getElementRule = getElementRule; 3166 3167 self.getCustomElements = function() { 3168 return customElementsMap; 3169 }; 3170 3171 self.addValidElements = addValidElements; 3172 3173 self.setValidElements = setValidElements; 3174 3175 self.addCustomElements = addCustomElements; 3176 3177 self.addValidChildren = addValidChildren; 3178 3179 self.elements = elements; 3180 }; 3181})(tinymce); 3182 3183(function(tinymce) { 3184 tinymce.html.SaxParser = function(settings, schema) { 3185 var self = this, noop = function() {}; 3186 3187 settings = settings || {}; 3188 self.schema = schema = schema || new tinymce.html.Schema(); 3189 3190 if (settings.fix_self_closing !== false) 3191 settings.fix_self_closing = true; 3192 3193 // Add handler functions from settings and setup default handlers 3194 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 3195 if (name) 3196 self[name] = settings[name] || noop; 3197 }); 3198 3199 self.parse = function(html) { 3200 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 3201 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 3202 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 3203 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 3204 3205 function processEndTag(name) { 3206 var pos, i; 3207 3208 // Find position of parent of the same type 3209 pos = stack.length; 3210 while (pos--) { 3211 if (stack[pos].name === name) 3212 break; 3213 } 3214 3215 // Found parent 3216 if (pos >= 0) { 3217 // Close all the open elements 3218 for (i = stack.length - 1; i >= pos; i--) { 3219 name = stack[i]; 3220 3221 if (name.valid) 3222 self.end(name.name); 3223 } 3224 3225 // Remove the open elements from the stack 3226 stack.length = pos; 3227 } 3228 }; 3229 3230 function parseAttribute(match, name, value, val2, val3) { 3231 var attrRule, i; 3232 3233 name = name.toLowerCase(); 3234 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 3235 3236 // Validate name and value 3237 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 3238 attrRule = validAttributesMap[name]; 3239 3240 // Find rule by pattern matching 3241 if (!attrRule && validAttributePatterns) { 3242 i = validAttributePatterns.length; 3243 while (i--) { 3244 attrRule = validAttributePatterns[i]; 3245 if (attrRule.pattern.test(name)) 3246 break; 3247 } 3248 3249 // No rule matched 3250 if (i === -1) 3251 attrRule = null; 3252 } 3253 3254 // No attribute rule found 3255 if (!attrRule) 3256 return; 3257 3258 // Validate value 3259 if (attrRule.validValues && !(value in attrRule.validValues)) 3260 return; 3261 } 3262 3263 // Add attribute to list and map 3264 attrList.map[name] = value; 3265 attrList.push({ 3266 name: name, 3267 value: value 3268 }); 3269 }; 3270 3271 // Precompile RegExps and map objects 3272 tokenRegExp = new RegExp('<(?:' + 3273 '(?:!--([\\w\\W]*?)-->)|' + // Comment 3274 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 3275 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 3276 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 3277 '(?:\\/([^>]+)>)|' + // End element 3278 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 3279 ')', 'g'); 3280 3281 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 3282 specialElements = { 3283 'script' : /<\/script[^>]*>/gi, 3284 'style' : /<\/style[^>]*>/gi, 3285 'noscript' : /<\/noscript[^>]*>/gi 3286 }; 3287 3288 // Setup lookup tables for empty elements and boolean attributes 3289 shortEndedElements = schema.getShortEndedElements(); 3290 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3291 fillAttrsMap = schema.getBoolAttrs(); 3292 validate = settings.validate; 3293 removeInternalElements = settings.remove_internals; 3294 fixSelfClosing = settings.fix_self_closing; 3295 isIE = tinymce.isIE; 3296 invalidPrefixRegExp = /^:/; 3297 3298 while (matches = tokenRegExp.exec(html)) { 3299 // Text 3300 if (index < matches.index) 3301 self.text(decode(html.substr(index, matches.index - index))); 3302 3303 if (value = matches[6]) { // End element 3304 value = value.toLowerCase(); 3305 3306 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3307 if (isIE && invalidPrefixRegExp.test(value)) 3308 value = value.substr(1); 3309 3310 processEndTag(value); 3311 } else if (value = matches[7]) { // Start element 3312 value = value.toLowerCase(); 3313 3314 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3315 if (isIE && invalidPrefixRegExp.test(value)) 3316 value = value.substr(1); 3317 3318 isShortEnded = value in shortEndedElements; 3319 3320 // Is self closing tag for example an <li> after an open <li> 3321 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3322 processEndTag(value); 3323 3324 // Validate element 3325 if (!validate || (elementRule = schema.getElementRule(value))) { 3326 isValidElement = true; 3327 3328 // Grab attributes map and patters when validation is enabled 3329 if (validate) { 3330 validAttributesMap = elementRule.attributes; 3331 validAttributePatterns = elementRule.attributePatterns; 3332 } 3333 3334 // Parse attributes 3335 if (attribsValue = matches[8]) { 3336 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3337 3338 // If the element has internal attributes then remove it if we are told to do so 3339 if (isInternalElement && removeInternalElements) 3340 isValidElement = false; 3341 3342 attrList = []; 3343 attrList.map = {}; 3344 3345 attribsValue.replace(attrRegExp, parseAttribute); 3346 } else { 3347 attrList = []; 3348 attrList.map = {}; 3349 } 3350 3351 // Process attributes if validation is enabled 3352 if (validate && !isInternalElement) { 3353 attributesRequired = elementRule.attributesRequired; 3354 attributesDefault = elementRule.attributesDefault; 3355 attributesForced = elementRule.attributesForced; 3356 3357 // Handle forced attributes 3358 if (attributesForced) { 3359 i = attributesForced.length; 3360 while (i--) { 3361 attr = attributesForced[i]; 3362 name = attr.name; 3363 attrValue = attr.value; 3364 3365 if (attrValue === '{$uid}') 3366 attrValue = 'mce_' + idCount++; 3367 3368 attrList.map[name] = attrValue; 3369 attrList.push({name: name, value: attrValue}); 3370 } 3371 } 3372 3373 // Handle default attributes 3374 if (attributesDefault) { 3375 i = attributesDefault.length; 3376 while (i--) { 3377 attr = attributesDefault[i]; 3378 name = attr.name; 3379 3380 if (!(name in attrList.map)) { 3381 attrValue = attr.value; 3382 3383 if (attrValue === '{$uid}') 3384 attrValue = 'mce_' + idCount++; 3385 3386 attrList.map[name] = attrValue; 3387 attrList.push({name: name, value: attrValue}); 3388 } 3389 } 3390 } 3391 3392 // Handle required attributes 3393 if (attributesRequired) { 3394 i = attributesRequired.length; 3395 while (i--) { 3396 if (attributesRequired[i] in attrList.map) 3397 break; 3398 } 3399 3400 // None of the required attributes where found 3401 if (i === -1) 3402 isValidElement = false; 3403 } 3404 3405 // Invalidate element if it's marked as bogus 3406 if (attrList.map['data-mce-bogus']) 3407 isValidElement = false; 3408 } 3409 3410 if (isValidElement) 3411 self.start(value, attrList, isShortEnded); 3412 } else 3413 isValidElement = false; 3414 3415 // Treat script, noscript and style a bit different since they may include code that looks like elements 3416 if (endRegExp = specialElements[value]) { 3417 endRegExp.lastIndex = index = matches.index + matches[0].length; 3418 3419 if (matches = endRegExp.exec(html)) { 3420 if (isValidElement) 3421 text = html.substr(index, matches.index - index); 3422 3423 index = matches.index + matches[0].length; 3424 } else { 3425 text = html.substr(index); 3426 index = html.length; 3427 } 3428 3429 if (isValidElement && text.length > 0) 3430 self.text(text, true); 3431 3432 if (isValidElement) 3433 self.end(value); 3434 3435 tokenRegExp.lastIndex = index; 3436 continue; 3437 } 3438 3439 // Push value on to stack 3440 if (!isShortEnded) { 3441 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3442 stack.push({name: value, valid: isValidElement}); 3443 else if (isValidElement) 3444 self.end(value); 3445 } 3446 } else if (value = matches[1]) { // Comment 3447 self.comment(value); 3448 } else if (value = matches[2]) { // CDATA 3449 self.cdata(value); 3450 } else if (value = matches[3]) { // DOCTYPE 3451 self.doctype(value); 3452 } else if (value = matches[4]) { // PI 3453 self.pi(value, matches[5]); 3454 } 3455 3456 index = matches.index + matches[0].length; 3457 } 3458 3459 // Text 3460 if (index < html.length) 3461 self.text(decode(html.substr(index))); 3462 3463 // Close any open elements 3464 for (i = stack.length - 1; i >= 0; i--) { 3465 value = stack[i]; 3466 3467 if (value.valid) 3468 self.end(value.name); 3469 } 3470 }; 3471 } 3472})(tinymce); 3473 3474(function(tinymce) { 3475 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3476 '#text' : 3, 3477 '#comment' : 8, 3478 '#cdata' : 4, 3479 '#pi' : 7, 3480 '#doctype' : 10, 3481 '#document-fragment' : 11 3482 }; 3483 3484 // Walks the tree left/right 3485 function walk(node, root_node, prev) { 3486 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3487 3488 // Walk into nodes if it has a start 3489 if (node[startName]) 3490 return node[startName]; 3491 3492 // Return the sibling if it has one 3493 if (node !== root_node) { 3494 sibling = node[siblingName]; 3495 3496 if (sibling) 3497 return sibling; 3498 3499 // Walk up the parents to look for siblings 3500 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3501 sibling = parent[siblingName]; 3502 3503 if (sibling) 3504 return sibling; 3505 } 3506 } 3507 }; 3508 3509 function Node(name, type) { 3510 this.name = name; 3511 this.type = type; 3512 3513 if (type === 1) { 3514 this.attributes = []; 3515 this.attributes.map = {}; 3516 } 3517 } 3518 3519 tinymce.extend(Node.prototype, { 3520 replace : function(node) { 3521 var self = this; 3522 3523 if (node.parent) 3524 node.remove(); 3525 3526 self.insert(node, self); 3527 self.remove(); 3528 3529 return self; 3530 }, 3531 3532 attr : function(name, value) { 3533 var self = this, attrs, i, undef; 3534 3535 if (typeof name !== "string") { 3536 for (i in name) 3537 self.attr(i, name[i]); 3538 3539 return self; 3540 } 3541
3542 if (attrs = self.attributes) { 3543 if (value !== undef) { 3544 // Remove attribute 3545 if (value === null) { 3546 if (name in attrs.map) { 3547 delete attrs.map[name]; 3548 3549 i = attrs.length; 3550 while (i--) { 3551 if (attrs[i].name === name) { 3552 attrs = attrs.splice(i, 1); 3553 return self; 3554 } 3555 } 3556 } 3557 3558 return self; 3559 } 3560 3561 // Set attribute 3562 if (name in attrs.map) { 3563 // Set attribute 3564 i = attrs.length; 3565 while (i--) { 3566 if (attrs[i].name === name) { 3567 attrs[i].value = value; 3568 break; 3569 } 3570 } 3571 } else 3572 attrs.push({name: name, value: value}); 3573 3574 attrs.map[name] = value; 3575 3576 return self; 3577 } else { 3578 return attrs.map[name]; 3579 } 3580 } 3581 }, 3582 3583 clone : function() { 3584 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3585 3586 // Clone element attributes 3587 if (selfAttrs = self.attributes) { 3588 cloneAttrs = []; 3589 cloneAttrs.map = {}; 3590 3591 for (i = 0, l = selfAttrs.length; i < l; i++) { 3592 selfAttr = selfAttrs[i]; 3593 3594 // Clone everything except id 3595 if (selfAttr.name !== 'id') { 3596 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3597 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3598 } 3599 } 3600 3601 clone.attributes = cloneAttrs; 3602 } 3603 3604 clone.value = self.value; 3605 clone.shortEnded = self.shortEnded; 3606 3607 return clone; 3608 }, 3609 3610 wrap : function(wrapper) { 3611 var self = this; 3612 3613 self.parent.insert(wrapper, self); 3614 wrapper.append(self); 3615 3616 return self; 3617 }, 3618 3619 unwrap : function() { 3620 var self = this, node, next; 3621 3622 for (node = self.firstChild; node; ) { 3623 next = node.next; 3624 self.insert(node, self, true); 3625 node = next; 3626 } 3627 3628 self.remove(); 3629 }, 3630 3631 remove : function() { 3632 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3633 3634 if (parent) { 3635 if (parent.firstChild === self) { 3636 parent.firstChild = next; 3637 3638 if (next) 3639 next.prev = null; 3640 } else { 3641 prev.next = next; 3642 } 3643 3644 if (parent.lastChild === self) { 3645 parent.lastChild = prev; 3646 3647 if (prev) 3648 prev.next = null; 3649 } else { 3650 next.prev = prev; 3651 } 3652 3653 self.parent = self.next = self.prev = null; 3654 } 3655 3656 return self; 3657 }, 3658 3659 append : function(node) { 3660 var self = this, last; 3661 3662 if (node.parent) 3663 node.remove(); 3664 3665 last = self.lastChild; 3666 if (last) { 3667 last.next = node; 3668 node.prev = last; 3669 self.lastChild = node; 3670 } else 3671 self.lastChild = self.firstChild = node; 3672 3673 node.parent = self; 3674 3675 return node; 3676 }, 3677 3678 insert : function(node, ref_node, before) { 3679 var parent; 3680 3681 if (node.parent) 3682 node.remove(); 3683 3684 parent = ref_node.parent || this; 3685 3686 if (before) { 3687 if (ref_node === parent.firstChild) 3688 parent.firstChild = node; 3689 else 3690 ref_node.prev.next = node; 3691 3692 node.prev = ref_node.prev; 3693 node.next = ref_node; 3694 ref_node.prev = node; 3695 } else { 3696 if (ref_node === parent.lastChild) 3697 parent.lastChild = node; 3698 else 3699 ref_node.next.prev = node; 3700 3701 node.next = ref_node.next; 3702 node.prev = ref_node; 3703 ref_node.next = node; 3704 } 3705 3706 node.parent = parent; 3707 3708 return node; 3709 }, 3710 3711 getAll : function(name) { 3712 var self = this, node, collection = []; 3713 3714 for (node = self.firstChild; node; node = walk(node, self)) { 3715 if (node.name === name) 3716 collection.push(node); 3717 } 3718 3719 return collection; 3720 }, 3721 3722 empty : function() { 3723 var self = this, nodes, i, node; 3724 3725 // Remove all children 3726 if (self.firstChild) { 3727 nodes = []; 3728 3729 // Collect the children 3730 for (node = self.firstChild; node; node = walk(node, self)) 3731 nodes.push(node); 3732 3733 // Remove the children 3734 i = nodes.length; 3735 while (i--) { 3736 node = nodes[i]; 3737 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3738 } 3739 } 3740 3741 self.firstChild = self.lastChild = null; 3742 3743 return self; 3744 }, 3745 3746 isEmpty : function(elements) { 3747 var self = this, node = self.firstChild, i, name; 3748 3749 if (node) { 3750 do { 3751 if (node.type === 1) { 3752 // Ignore bogus elements 3753 if (node.attributes.map['data-mce-bogus']) 3754 continue; 3755 3756 // Keep empty elements like <img /> 3757 if (elements[node.name]) 3758 return false;
3759 3760 // Keep elements with data attributes or name attribute like <a name="1"></a> 3761 i = node.attributes.length; 3762 while (i--) { 3763 name = node.attributes[i].name; 3764 if (name === "name" || name.indexOf('data-mce-') === 0) 3765 return false; 3766 } 3767 } 3768 3769 // Keep comments 3770 if (node.type === 8) 3771 return false; 3772 3773 // Keep non whitespace text nodes 3774 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3775 return false; 3776 } while (node = walk(node, self)); 3777 } 3778 3779 return true; 3780 }, 3781 3782 walk : function(prev) { 3783 return walk(this, null, prev); 3784 } 3785 }); 3786 3787 tinymce.extend(Node, { 3788 create : function(name, attrs) { 3789 var node, attrName; 3790 3791 // Create node 3792 node = new Node(name, typeLookup[name] || 1); 3793 3794 // Add attributes if needed 3795 if (attrs) { 3796 for (attrName in attrs) 3797 node.attr(attrName, attrs[attrName]); 3798 } 3799 3800 return node; 3801 } 3802 }); 3803 3804 tinymce.html.Node = Node; 3805})(tinymce); 3806 3807(function(tinymce) { 3808 var Node = tinymce.html.Node; 3809 3810 tinymce.html.DomParser = function(settings, schema) { 3811 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3812 3813 settings = settings || {}; 3814 settings.validate = "validate" in settings ? settings.validate : true; 3815 settings.root_name = settings.root_name || 'body'; 3816 self.schema = schema = schema || new tinymce.html.Schema(); 3817 3818 function fixInvalidChildren(nodes) { 3819 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3820 childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; 3821 3822 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3823 nonEmptyElements = schema.getNonEmptyElements(); 3824 textBlockElements = schema.getTextBlockElements(); 3825 3826 for (ni = 0; ni < nodes.length; ni++) { 3827 node = nodes[ni]; 3828 3829 // Already removed or fixed 3830 if (!node.parent || node.fixed) 3831 continue; 3832 3833 // If the invalid element is a text block and the text block is within a parent LI element 3834 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 3835 if (textBlockElements[node.name] && node.parent.name == 'li') { 3836 // Move sibling text blocks after LI element 3837 sibling = node.next; 3838 while (sibling) { 3839 if (textBlockElements[sibling.name]) { 3840 sibling.name = 'li'; 3841 sibling.fixed = true; 3842 node.parent.insert(sibling, node.parent); 3843 } else { 3844 break; 3845 } 3846 3847 sibling = sibling.next; 3848 } 3849 3850 // Unwrap current text block 3851 node.unwrap(node); 3852 continue; 3853 } 3854 3855 // Get list of all parent nodes until we find a valid parent to stick the child into 3856 parents = [node]; 3857 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3858 parents.push(parent); 3859 3860 // Found a suitable parent 3861 if (parent && parents.length > 1) { 3862 // Reverse the array since it makes looping easier 3863 parents.reverse(); 3864 3865 // Clone the related parent and insert that after the moved node 3866 newParent = currentNode = self.filterNode(parents[0].clone()); 3867 3868 // Start cloning and moving children on the left side of the target node 3869 for (i = 0; i < parents.length - 1; i++) { 3870 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3871 tempNode = self.filterNode(parents[i].clone()); 3872 currentNode.append(tempNode); 3873 } else 3874 tempNode = currentNode; 3875 3876 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3877 nextNode = childNode.next; 3878 tempNode.append(childNode); 3879 childNode = nextNode; 3880 } 3881 3882 currentNode = tempNode; 3883 } 3884 3885 if (!newParent.isEmpty(nonEmptyElements)) { 3886 parent.insert(newParent, parents[0], true); 3887 parent.insert(node, newParent); 3888 } else { 3889 parent.insert(node, parents[0], true); 3890 } 3891 3892 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3893 parent = parents[0]; 3894 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3895 parent.empty().remove(); 3896 } 3897 3898 } else if (node.parent) {
3899 // If it's an LI try to find a UL/OL for it or wrap it 3900 if (node.name === 'li') { 3901 sibling = node.prev; 3902 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3903 sibling.append(node); 3904 continue; 3905 } 3906 3907 sibling = node.next; 3908 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3909 sibling.insert(node, sibling.firstChild, true); 3910 continue; 3911 } 3912 3913 node.wrap(self.filterNode(new Node('ul', 1))); 3914 continue; 3915 } 3916 3917 // Try wrapping the element in a DIV 3918 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3919 node.wrap(self.filterNode(new Node('div', 1))); 3920 } else { 3921 // We failed wrapping it, then remove or unwrap it 3922 if (node.name === 'style' || node.name === 'script') 3923 node.empty().remove(); 3924 else 3925 node.unwrap(); 3926 } 3927 } 3928 } 3929 }; 3930 3931 self.filterNode = function(node) { 3932 var i, name, list; 3933 3934 // Run element filters 3935 if (name in nodeFilters) { 3936 list = matchedNodes[name]; 3937 3938 if (list) 3939 list.push(node); 3940 else 3941 matchedNodes[name] = [node]; 3942 } 3943 3944 // Run attribute filters 3945 i = attributeFilters.length; 3946 while (i--) { 3947 name = attributeFilters[i].name; 3948 3949 if (name in node.attributes.map) { 3950 list = matchedAttributes[name]; 3951 3952 if (list) 3953 list.push(node); 3954 else 3955 matchedAttributes[name] = [node]; 3956 } 3957 } 3958 3959 return node; 3960 }; 3961 3962 self.addNodeFilter = function(name, callback) { 3963 tinymce.each(tinymce.explode(name), function(name) { 3964 var list = nodeFilters[name]; 3965 3966 if (!list) 3967 nodeFilters[name] = list = []; 3968 3969 list.push(callback); 3970 }); 3971 }; 3972 3973 self.addAttributeFilter = function(name, callback) { 3974 tinymce.each(tinymce.explode(name), function(name) { 3975 var i; 3976 3977 for (i = 0; i < attributeFilters.length; i++) { 3978 if (attributeFilters[i].name === name) { 3979 attributeFilters[i].callbacks.push(callback); 3980 return; 3981 } 3982 } 3983 3984 attributeFilters.push({name: name, callbacks: [callback]}); 3985 }); 3986 }; 3987 3988 self.parse = function(html, args) { 3989 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3990 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3991 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3992 3993 args = args || {}; 3994 matchedNodes = {}; 3995 matchedAttributes = {}; 3996 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3997 nonEmptyElements = schema.getNonEmptyElements(); 3998 children = schema.children; 3999 validate = settings.validate; 4000 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 4001 4002 whiteSpaceElements = schema.getWhiteSpaceElements(); 4003 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 4004 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 4005 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 4006 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 4007 4008 function addRootBlocks() { 4009 var node = rootNode.firstChild, next, rootBlockNode; 4010 4011 while (node) { 4012 next = node.next; 4013 4014 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 4015 if (!rootBlockNode) { 4016 // Create a new root block element 4017 rootBlockNode = createNode(rootBlockName, 1); 4018 rootNode.insert(rootBlockNode, node); 4019 rootBlockNode.append(node); 4020 } else 4021 rootBlockNode.append(node); 4022 } else { 4023 rootBlockNode = null; 4024 } 4025 4026 node = next; 4027 }; 4028 }; 4029 4030 function createNode(name, type) { 4031 var node = new Node(name, type), list; 4032 4033 if (name in nodeFilters) { 4034 list = matchedNodes[name]; 4035 4036 if (list) 4037 list.push(node); 4038 else 4039 matchedNodes[name] = [node]; 4040 } 4041 4042 return node; 4043 }; 4044 4045 function removeWhitespaceBefore(node) { 4046 var textNode, textVal, sibling; 4047 4048 for (textNode = node.prev; textNode && textNode.type === 3; ) { 4049 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 4050 4051 if (textVal.length > 0) { 4052 textNode.value = textVal; 4053 textNode = textNode.prev; 4054 } else { 4055 sibling = textNode.prev; 4056 textNode.remove(); 4057 textNode = sibling; 4058 } 4059 } 4060 }; 4061 4062 function cloneAndExcludeBlocks(input) { 4063 var name, output = {}; 4064 4065 for (name in input) { 4066 if (name !== 'li' && name != 'p') { 4067 output[name] = input[name]; 4068 } 4069 } 4070 4071 return output; 4072 }; 4073 4074 parser = new tinymce.html.SaxParser({ 4075 validate : validate, 4076 4077 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 4078 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 4079 4080 cdata: function(text) { 4081 node.append(createNode('#cdata', 4)).value = text; 4082 }, 4083 4084 text: function(text, raw) { 4085 var textNode; 4086 4087 // Trim all redundant whitespace on non white space elements 4088 if (!isInWhiteSpacePreservedElement) { 4089 text = text.replace(allWhiteSpaceRegExp, ' '); 4090 4091 if (node.lastChild && blockElements[node.lastChild.name]) 4092 text = text.replace(startWhiteSpaceRegExp, ''); 4093 } 4094 4095 // Do we need to create the node 4096 if (text.length !== 0) { 4097 textNode = createNode('#text', 3); 4098 textNode.raw = !!raw; 4099 node.append(textNode).value = text; 4100 } 4101 }, 4102 4103 comment: function(text) { 4104 node.append(createNode('#comment', 8)).value = text; 4105 }, 4106 4107 pi: function(name, text) { 4108 node.append(createNode(name, 7)).value = text; 4109 removeWhitespaceBefore(node); 4110 }, 4111 4112 doctype: function(text) { 4113 var newNode; 4114 4115 newNode = node.append(createNode('#doctype', 10)); 4116 newNode.value = text; 4117 removeWhitespaceBefore(node); 4118 }, 4119 4120 start: function(name, attrs, empty) { 4121 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 4122 4123 elementRule = validate ? schema.getElementRule(name) : {}; 4124 if (elementRule) {
4125 newNode = createNode(elementRule.outputName || name, 1); 4126 newNode.attributes = attrs; 4127 newNode.shortEnded = empty; 4128 4129 node.append(newNode); 4130 4131 // Check if node is valid child of the parent node is the child is 4132 // unknown we don't collect it since it's probably a custom element 4133 parent = children[node.name]; 4134 if (parent && children[newNode.name] && !parent[newNode.name]) 4135 invalidChildren.push(newNode); 4136 4137 attrFiltersLen = attributeFilters.length; 4138 while (attrFiltersLen--) { 4139 attrName = attributeFilters[attrFiltersLen].name; 4140 4141 if (attrName in attrs.map) { 4142 list = matchedAttributes[attrName]; 4143 4144 if (list) 4145 list.push(newNode); 4146 else 4147 matchedAttributes[attrName] = [newNode]; 4148 } 4149 } 4150 4151 // Trim whitespace before block 4152 if (blockElements[name]) 4153 removeWhitespaceBefore(newNode); 4154 4155 // Change current node if the element wasn't empty i.e not <br /> or <img /> 4156 if (!empty) 4157 node = newNode; 4158 4159 // Check if we are inside a whitespace preserved element 4160 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4161 isInWhiteSpacePreservedElement = true; 4162 } 4163 } 4164 }, 4165 4166 end: function(name) { 4167 var textNode, elementRule, text, sibling, tempNode; 4168 4169 elementRule = validate ? schema.getElementRule(name) : {}; 4170 if (elementRule) { 4171 if (blockElements[name]) { 4172 if (!isInWhiteSpacePreservedElement) { 4173 // Trim whitespace of the first node in a block 4174 textNode = node.firstChild; 4175 if (textNode && textNode.type === 3) { 4176 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4177 4178 // Any characters left after trim or should we remove it 4179 if (text.length > 0) { 4180 textNode.value = text; 4181 textNode = textNode.next; 4182 } else { 4183 sibling = textNode.next; 4184 textNode.remove(); 4185 textNode = sibling; 4186 } 4187 4188 // Remove any pure whitespace siblings 4189 while (textNode && textNode.type === 3) { 4190 text = textNode.value; 4191 sibling = textNode.next; 4192 4193 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4194 textNode.remove(); 4195 textNode = sibling; 4196 } 4197 4198 textNode = sibling; 4199 } 4200 } 4201 4202 // Trim whitespace of the last node in a block 4203 textNode = node.lastChild; 4204 if (textNode && textNode.type === 3) { 4205 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 4206 4207 // Any characters left after trim or should we remove it 4208 if (text.length > 0) { 4209 textNode.value = text; 4210 textNode = textNode.prev; 4211 } else { 4212 sibling = textNode.prev; 4213 textNode.remove(); 4214 textNode = sibling; 4215 } 4216 4217 // Remove any pure whitespace siblings 4218 while (textNode && textNode.type === 3) { 4219 text = textNode.value; 4220 sibling = textNode.prev; 4221 4222 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 4223 textNode.remove(); 4224 textNode = sibling; 4225 } 4226 4227 textNode = sibling; 4228 } 4229 } 4230 } 4231 4232 // Trim start white space 4233 // Removed due to: #5424 4234 /*textNode = node.prev; 4235 if (textNode && textNode.type === 3) { 4236 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 4237 4238 if (text.length > 0) 4239 textNode.value = text; 4240 else 4241 textNode.remove(); 4242 }*/ 4243 } 4244 4245 // Check if we exited a whitespace preserved element 4246 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 4247 isInWhiteSpacePreservedElement = false; 4248 } 4249 4250 // Handle empty nodes 4251 if (elementRule.removeEmpty || elementRule.paddEmpty) { 4252 if (node.isEmpty(nonEmptyElements)) { 4253 if (elementRule.paddEmpty) 4254 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 4255 else { 4256 // Leave nodes that have a name like <a name="name"> 4257 if (!node.attributes.map.name && !node.attributes.map.id) { 4258 tempNode = node.parent; 4259 node.empty().remove(); 4260 node = tempNode; 4261 return; 4262 } 4263 } 4264 } 4265 } 4266 4267 node = node.parent; 4268 } 4269 } 4270 }, schema); 4271 4272 rootNode = node = new Node(args.context || settings.root_name, 11); 4273 4274 parser.parse(html); 4275 4276 // Fix invalid children or report invalid children in a contextual parsing 4277 if (validate && invalidChildren.length) { 4278 if (!args.context) 4279 fixInvalidChildren(invalidChildren); 4280 else 4281 args.invalid = true; 4282 } 4283 4284 // Wrap nodes in the root into block elements if the root is body 4285 if (rootBlockName && rootNode.name == 'body') 4286 addRootBlocks(); 4287 4288 // Run filters only when the contents is valid 4289 if (!args.invalid) { 4290 // Run node filters 4291 for (name in matchedNodes) { 4292 list = nodeFilters[name]; 4293 nodes = matchedNodes[name]; 4294 4295 // Remove already removed children 4296 fi = nodes.length; 4297 while (fi--) { 4298 if (!nodes[fi].parent) 4299 nodes.splice(fi, 1); 4300 } 4301 4302 for (i = 0, l = list.length; i < l; i++) 4303 list[i](nodes, name, args); 4304 } 4305 4306 // Run attribute filters 4307 for (i = 0, l = attributeFilters.length; i < l; i++) { 4308 list = attributeFilters[i]; 4309 4310 if (list.name in matchedAttributes) { 4311 nodes = matchedAttributes[list.name]; 4312 4313 // Remove already removed children 4314 fi = nodes.length; 4315 while (fi--) { 4316 if (!nodes[fi].parent) 4317 nodes.splice(fi, 1); 4318 } 4319 4320 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4321 list.callbacks[fi](nodes, list.name, args); 4322 } 4323 } 4324 } 4325 4326 return rootNode; 4327 }; 4328 4329 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4330 // make it possible to place the caret inside empty blocks. This logic tries to remove 4331 // these elements and keep br elements that where intended to be there intact 4332 if (settings.remove_trailing_brs) { 4333 self.addNodeFilter('br', function(nodes, name) { 4334 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4335 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4336 4337 // Remove brs from body element as well 4338 blockElements.body = 1; 4339 4340 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4341 for (i = 0; i < l; i++) { 4342 node = nodes[i]; 4343 parent = node.parent; 4344 4345 if (blockElements[node.parent.name] && node === parent.lastChild) { 4346 // Loop all nodes to the left of the current node and check for other BR elements 4347 // excluding bookmarks since they are invisible 4348 prev = node.prev; 4349 while (prev) { 4350 prevName = prev.name; 4351 4352 // Ignore bookmarks 4353 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4354 // Found a non BR element 4355 if (prevName !== "br") 4356 break; 4357 4358 // Found another br it's a <br><br> structure then don't remove anything 4359 if (prevName === 'br') { 4360 node = null; 4361 break; 4362 } 4363 } 4364 4365 prev = prev.prev; 4366 } 4367 4368 if (node) { 4369 node.remove(); 4370 4371 // Is the parent to be considered empty after we removed the BR 4372 if (parent.isEmpty(nonEmptyElements)) { 4373 elementRule = schema.getElementRule(parent.name); 4374 4375 // Remove or padd the element depending on schema rule 4376 if (elementRule) { 4377 if (elementRule.removeEmpty) 4378 parent.remove(); 4379 else if (elementRule.paddEmpty) 4380 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4381 } 4382 } 4383 } 4384 } else {
4385 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4386 lastParent = node; 4387 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4388 lastParent = parent; 4389 4390 if (blockElements[parent.name]) { 4391 break; 4392 } 4393 4394 parent = parent.parent; 4395 } 4396 4397 if (lastParent === parent) { 4398 textNode = new tinymce.html.Node('#text', 3); 4399 textNode.value = '\u00a0'; 4400 node.replace(textNode); 4401 } 4402 } 4403 } 4404 }); 4405 } 4406 4407 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4408 if (!settings.allow_html_in_named_anchor) { 4409 self.addAttributeFilter('id,name', function(nodes, name) { 4410 var i = nodes.length, sibling, prevSibling, parent, node; 4411 4412 while (i--) { 4413 node = nodes[i]; 4414 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4415 parent = node.parent; 4416 4417 // Move children after current node 4418 sibling = node.lastChild; 4419 do { 4420 prevSibling = sibling.prev; 4421 parent.insert(sibling, node); 4422 sibling = prevSibling; 4423 } while (sibling); 4424 } 4425 } 4426 }); 4427 } 4428 } 4429})(tinymce); 4430 4431tinymce.html.Writer = function(settings) { 4432 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4433 4434 settings = settings || {}; 4435 indent = settings.indent; 4436 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4437 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4438 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4439 htmlOutput = settings.element_format == "html"; 4440 4441 return { 4442 start: function(name, attrs, empty) { 4443 var i, l, attr, value; 4444 4445 if (indent && indentBefore[name] && html.length > 0) { 4446 value = html[html.length - 1]; 4447 4448 if (value.length > 0 && value !== '\n') 4449 html.push('\n'); 4450 } 4451 4452 html.push('<', name); 4453 4454 if (attrs) { 4455 for (i = 0, l = attrs.length; i < l; i++) { 4456 attr = attrs[i]; 4457 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4458 } 4459 } 4460 4461 if (!empty || htmlOutput) 4462 html[html.length] = '>'; 4463 else 4464 html[html.length] = ' />'; 4465 4466 if (empty && indent && indentAfter[name] && html.length > 0) { 4467 value = html[html.length - 1]; 4468 4469 if (value.length > 0 && value !== '\n') 4470 html.push('\n'); 4471 } 4472 }, 4473 4474 end: function(name) { 4475 var value; 4476 4477 /*if (indent && indentBefore[name] && html.length > 0) { 4478 value = html[html.length - 1]; 4479 4480 if (value.length > 0 && value !== '\n') 4481 html.push('\n'); 4482 }*/ 4483 4484 html.push('</', name, '>'); 4485 4486 if (indent && indentAfter[name] && html.length > 0) { 4487 value = html[html.length - 1]; 4488 4489 if (value.length > 0 && value !== '\n') 4490 html.push('\n'); 4491 } 4492 }, 4493 4494 text: function(text, raw) { 4495 if (text.length > 0) 4496 html[html.length] = raw ? text : encode(text); 4497 }, 4498 4499 cdata: function(text) { 4500 html.push('<![CDATA[', text, ']]>'); 4501 }, 4502 4503 comment: function(text) { 4504 html.push('<!--', text, '-->'); 4505 }, 4506 4507 pi: function(name, text) { 4508 if (text) 4509 html.push('<?', name, ' ', text, '?>'); 4510 else 4511 html.push('<?', name, '?>'); 4512 4513 if (indent) 4514 html.push('\n'); 4515 }, 4516 4517 doctype: function(text) { 4518 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4519 }, 4520 4521 reset: function() { 4522 html.length = 0; 4523 }, 4524 4525 getContent: function() { 4526 return html.join('').replace(/\n$/, ''); 4527 } 4528 }; 4529}; 4530 4531(function(tinymce) { 4532 tinymce.html.Serializer = function(settings, schema) { 4533 var self = this, writer = new tinymce.html.Writer(settings); 4534 4535 settings = settings || {}; 4536 settings.validate = "validate" in settings ? settings.validate : true; 4537 4538 self.schema = schema = schema || new tinymce.html.Schema(); 4539 self.writer = writer; 4540 4541 self.serialize = function(node) { 4542 var handlers, validate; 4543 4544 validate = settings.validate; 4545 4546 handlers = { 4547 // #text 4548 3: function(node, raw) { 4549 writer.text(node.value, node.raw); 4550 }, 4551 4552 // #comment 4553 8: function(node) { 4554 writer.comment(node.value); 4555 }, 4556 4557 // Processing instruction 4558 7: function(node) { 4559 writer.pi(node.name, node.value); 4560 }, 4561 4562 // Doctype 4563 10: function(node) { 4564 writer.doctype(node.value); 4565 }, 4566 4567 // CDATA 4568 4: function(node) {
4569 writer.cdata(node.value); 4570 }, 4571 4572 // Document fragment 4573 11: function(node) { 4574 if ((node = node.firstChild)) { 4575 do { 4576 walk(node); 4577 } while (node = node.next); 4578 } 4579 } 4580 }; 4581 4582 writer.reset(); 4583 4584 function walk(node) { 4585 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4586 4587 if (!handler) { 4588 name = node.name; 4589 isEmpty = node.shortEnded; 4590 attrs = node.attributes; 4591 4592 // Sort attributes 4593 if (validate && attrs && attrs.length > 1) { 4594 sortedAttrs = []; 4595 sortedAttrs.map = {}; 4596 4597 elementRule = schema.getElementRule(node.name); 4598 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4599 attrName = elementRule.attributesOrder[i]; 4600 4601 if (attrName in attrs.map) { 4602 attrValue = attrs.map[attrName]; 4603 sortedAttrs.map[attrName] = attrValue; 4604 sortedAttrs.push({name: attrName, value: attrValue}); 4605 } 4606 } 4607 4608 for (i = 0, l = attrs.length; i < l; i++) { 4609 attrName = attrs[i].name; 4610 4611 if (!(attrName in sortedAttrs.map)) { 4612 attrValue = attrs.map[attrName]; 4613 sortedAttrs.map[attrName] = attrValue; 4614 sortedAttrs.push({name: attrName, value: attrValue}); 4615 } 4616 } 4617 4618 attrs = sortedAttrs; 4619 } 4620 4621 writer.start(node.name, attrs, isEmpty); 4622 4623 if (!isEmpty) { 4624 if ((node = node.firstChild)) { 4625 do { 4626 walk(node); 4627 } while (node = node.next); 4628 } 4629 4630 writer.end(name); 4631 } 4632 } else 4633 handler(node); 4634 } 4635 4636 // Serialize element and treat all non elements as fragments 4637 if (node.type == 1 && !settings.inner) 4638 walk(node); 4639 else 4640 handlers[11](node); 4641 4642 return writer.getContent(); 4643 }; 4644 } 4645})(tinymce); 4646 4647// JSLint defined globals 4648/*global tinymce:false, window:false */ 4649 4650tinymce.dom = {}; 4651 4652(function(namespace, expando) { 4653 var w3cEventModel = !!document.addEventListener; 4654 4655 function addEvent(target, name, callback, capture) { 4656 if (target.addEventListener) { 4657 target.addEventListener(name, callback, capture || false); 4658 } else if (target.attachEvent) { 4659 target.attachEvent('on' + name, callback); 4660 } 4661 } 4662 4663 function removeEvent(target, name, callback, capture) { 4664 if (target.removeEventListener) { 4665 target.removeEventListener(name, callback, capture || false); 4666 } else if (target.detachEvent) { 4667 target.detachEvent('on' + name, callback); 4668 } 4669 } 4670 4671 function fix(original_event, data) { 4672 var name, event = data || {}; 4673 4674 // Dummy function that gets replaced on the delegation state functions 4675 function returnFalse() { 4676 return false; 4677 } 4678 4679 // Dummy function that gets replaced on the delegation state functions 4680 function returnTrue() { 4681 return true; 4682 } 4683 4684 // Copy all properties from the original event 4685 for (name in original_event) { 4686 // layerX/layerY is deprecated in Chrome and produces a warning 4687 if (name !== "layerX" && name !== "layerY") { 4688 event[name] = original_event[name]; 4689 } 4690 } 4691 4692 // Normalize target IE uses srcElement 4693 if (!event.target) { 4694 event.target = event.srcElement || document; 4695 } 4696 4697 // Add preventDefault method 4698 event.preventDefault = function() { 4699 event.isDefaultPrevented = returnTrue; 4700 4701 // Execute preventDefault on the original event object 4702 if (original_event) { 4703 if (original_event.preventDefault) { 4704 original_event.preventDefault(); 4705 } else { 4706 original_event.returnValue = false; // IE 4707 } 4708 } 4709 }; 4710 4711 // Add stopPropagation 4712 event.stopPropagation = function() { 4713 event.isPropagationStopped = returnTrue; 4714 4715 // Execute stopPropagation on the original event object 4716 if (original_event) { 4717 if (original_event.stopPropagation) { 4718 original_event.stopPropagation(); 4719 } else { 4720 original_event.cancelBubble = true; // IE 4721 } 4722 } 4723 }; 4724 4725 // Add stopImmediatePropagation 4726 event.stopImmediatePropagation = function() { 4727 event.isImmediatePropagationStopped = returnTrue; 4728 event.stopPropagation(); 4729 }; 4730 4731 // Add event delegation states 4732 if (!event.isDefaultPrevented) { 4733 event.isDefaultPrevented = returnFalse; 4734 event.isPropagationStopped = returnFalse; 4735 event.isImmediatePropagationStopped = returnFalse; 4736 } 4737 4738 return event; 4739 } 4740 4741 function bindOnReady(win, callback, event_utils) { 4742 var doc = win.document, event = {type: 'ready'}; 4743 4744 // Gets called when the DOM is ready 4745 function readyHandler() { 4746 if (!event_utils.domLoaded) { 4747 event_utils.domLoaded = true; 4748 callback(event); 4749 } 4750 } 4751 4752 // Page already loaded then fire it directly 4753 if (doc.readyState == "complete") { 4754 readyHandler(); 4755 return; 4756 } 4757 4758 // Use W3C method 4759 if (w3cEventModel) { 4760 addEvent(win, 'DOMContentLoaded', readyHandler); 4761 } else { 4762 // Use IE method 4763 addEvent(doc, "readystatechange", function() { 4764 if (doc.readyState === "complete") { 4765 removeEvent(doc, "readystatechange", arguments.callee); 4766 readyHandler(); 4767 } 4768 }); 4769 4770 // Wait until we can scroll, when we can the DOM is initialized 4771 if (doc.documentElement.doScroll && win === win.top) { 4772 (function() { 4773 try { 4774 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4775 // http://javascript.nwbox.com/IEContentLoaded/ 4776 doc.documentElement.doScroll("left"); 4777 } catch (ex) { 4778 setTimeout(arguments.callee, 0); 4779 return; 4780 } 4781 4782 readyHandler(); 4783 })(); 4784 } 4785 } 4786 4787 // Fallback if any of the above methods should fail for some odd reason 4788 addEvent(win, 'load', readyHandler); 4789 } 4790 4791 function EventUtils(proxy) { 4792 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4793 4794 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4795 hasFocusIn = "onfocusin" in document.documentElement; 4796 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4797 count = 1; 4798 4799 // State if the DOMContentLoaded was executed or not 4800 self.domLoaded = false;
4801 self.events = events; 4802 4803 function executeHandlers(evt, id) { 4804 var callbackList, i, l, callback; 4805 4806 callbackList = events[id][evt.type]; 4807 if (callbackList) { 4808 for (i = 0, l = callbackList.length; i < l; i++) { 4809 callback = callbackList[i]; 4810 4811 // Check if callback exists might be removed if a unbind is called inside the callback 4812 if (callback && callback.func.call(callback.scope, evt) === false) { 4813 evt.preventDefault(); 4814 } 4815 4816 // Should we stop propagation to immediate listeners 4817 if (evt.isImmediatePropagationStopped()) { 4818 return; 4819 } 4820 } 4821 } 4822 } 4823 4824 self.bind = function(target, names, callback, scope) { 4825 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4826 4827 // Native event handler function patches the event and executes the callbacks for the expando 4828 function defaultNativeHandler(evt) { 4829 executeHandlers(fix(evt || win.event), id); 4830 } 4831 4832 // Don't bind to text nodes or comments 4833 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4834 return; 4835 } 4836 4837 // Create or get events id for the target 4838 if (!target[expando]) { 4839 id = count++; 4840 target[expando] = id; 4841 events[id] = {}; 4842 } else { 4843 id = target[expando]; 4844 4845 if (!events[id]) { 4846 events[id] = {}; 4847 } 4848 } 4849 4850 // Setup the specified scope or use the target as a default 4851 scope = scope || target; 4852 4853 // Split names and bind each event, enables you to bind multiple events with one call 4854 names = names.split(' '); 4855 i = names.length; 4856 while (i--) { 4857 name = names[i]; 4858 nativeHandler = defaultNativeHandler; 4859 fakeName = capture = false; 4860 4861 // Use ready instead of DOMContentLoaded 4862 if (name === "DOMContentLoaded") { 4863 name = "ready"; 4864 } 4865 4866 // DOM is already ready 4867 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4868 self.domLoaded = true; 4869 callback.call(scope, fix({type: name})); 4870 continue; 4871 } 4872 4873 // Handle mouseenter/mouseleaver 4874 if (!hasMouseEnterLeave) { 4875 fakeName = mouseEnterLeave[name]; 4876 4877 if (fakeName) { 4878 nativeHandler = function(evt) { 4879 var current, related; 4880 4881 current = evt.currentTarget; 4882 related = evt.relatedTarget; 4883 4884 // 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 4885 if (related && current.contains) { 4886 // Use contains for performance 4887 related = current.contains(related); 4888 } else { 4889 while (related && related !== current) { 4890 related = related.parentNode; 4891 } 4892 } 4893 4894 // Fire fake event 4895 if (!related) { 4896 evt = fix(evt || win.event); 4897 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4898 evt.target = current; 4899 executeHandlers(evt, id); 4900 } 4901 }; 4902 } 4903 } 4904 4905 // Fake bubbeling of focusin/focusout 4906 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4907 capture = true; 4908 fakeName = name === "focusin" ? "focus" : "blur"; 4909 nativeHandler = function(evt) { 4910 evt = fix(evt || win.event); 4911 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4912 executeHandlers(evt, id); 4913 }; 4914 } 4915 4916 // Setup callback list and bind native event 4917 callbackList = events[id][name]; 4918 if (!callbackList) { 4919 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4920 callbackList.fakeName = fakeName; 4921 callbackList.capture = capture; 4922 4923 // Add the nativeHandler to the callback list so that we can later unbind it 4924 callbackList.nativeHandler = nativeHandler; 4925 if (!w3cEventModel) { 4926 callbackList.proxyHandler = proxy(id); 4927 } 4928 4929 // Check if the target has native events support 4930 if (name === "ready") { 4931 bindOnReady(target, nativeHandler, self); 4932 } else { 4933 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4934 } 4935 } else { 4936 // If it already has an native handler then just push the callback 4937 callbackList.push({func: callback, scope: scope}); 4938 } 4939 } 4940 4941 target = callbackList = 0; // Clean memory for IE 4942 4943 return callback; 4944 }; 4945 4946 self.unbind = function(target, names, callback) { 4947 var id, callbackList, i, ci, name, eventMap; 4948 4949 // Don't bind to text nodes or comments 4950 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4951 return self; 4952 } 4953
4954 // Unbind event or events if the target has the expando 4955 id = target[expando]; 4956 if (id) { 4957 eventMap = events[id]; 4958 4959 // Specific callback 4960 if (names) { 4961 names = names.split(' '); 4962 i = names.length; 4963 while (i--) { 4964 name = names[i]; 4965 callbackList = eventMap[name]; 4966 4967 // Unbind the event if it exists in the map 4968 if (callbackList) { 4969 // Remove specified callback 4970 if (callback) { 4971 ci = callbackList.length; 4972 while (ci--) { 4973 if (callbackList[ci].func === callback) { 4974 callbackList.splice(ci, 1); 4975 } 4976 } 4977 } 4978 4979 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4980 if (!callback || callbackList.length === 0) { 4981 delete eventMap[name]; 4982 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4983 } 4984 } 4985 } 4986 } else { 4987 // All events for a specific element 4988 for (name in eventMap) { 4989 callbackList = eventMap[name]; 4990 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4991 } 4992 4993 eventMap = {}; 4994 } 4995 4996 // Check if object is empty, if it isn't then we won't remove the expando map 4997 for (name in eventMap) { 4998 return self; 4999 } 5000 5001 // Delete event object 5002 delete events[id]; 5003 5004 // Remove expando from target 5005 try { 5006 // IE will fail here since it can't delete properties from window 5007 delete target[expando]; 5008 } catch (ex) { 5009 // IE will set it to null 5010 target[expando] = null; 5011 } 5012 } 5013 5014 return self; 5015 }; 5016 5017 self.fire = function(target, name, args) { 5018 var id, event; 5019 5020 // Don't bind to text nodes or comments 5021 if (!target || target.nodeType === 3 || target.nodeType === 8) { 5022 return self; 5023 } 5024 5025 // Build event object by patching the args 5026 event = fix(null, args); 5027 event.type = name; 5028 5029 do { 5030 // Found an expando that means there is listeners to execute 5031 id = target[expando]; 5032 if (id) { 5033 executeHandlers(event, id); 5034 } 5035 5036 // Walk up the DOM 5037 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 5038 } while (target && !event.isPropagationStopped()); 5039 5040 return self; 5041 }; 5042 5043 self.clean = function(target) { 5044 var i, children, unbind = self.unbind; 5045 5046 // Don't bind to text nodes or comments 5047 if (!target || target.nodeType === 3 || target.nodeType === 8) { 5048 return self; 5049 } 5050 5051 // Unbind any element on the specificed target 5052 if (target[expando]) { 5053 unbind(target); 5054 } 5055 5056 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 5057 if (!target.getElementsByTagName) { 5058 target = target.document; 5059 } 5060 5061 // Remove events from each child element 5062 if (target && target.getElementsByTagName) { 5063 unbind(target); 5064 5065 children = target.getElementsByTagName('*'); 5066 i = children.length; 5067 while (i--) { 5068 target = children[i]; 5069 5070 if (target[expando]) { 5071 unbind(target); 5072 } 5073 } 5074 } 5075 5076 return self; 5077 }; 5078 5079 self.callNativeHandler = function(id, evt) { 5080 if (events) { 5081 events[id][evt.type].nativeHandler(evt); 5082 } 5083 }; 5084 5085 self.destory = function() { 5086 events = {}; 5087 }; 5088 5089 // Legacy function calls 5090 5091 self.add = function(target, events, func, scope) { 5092 // Old API supported direct ID assignment 5093 if (typeof(target) === "string") { 5094 target = document.getElementById(target); 5095 } 5096 5097 // Old API supported multiple targets 5098 if (target && target instanceof Array) { 5099 var i = target.length; 5100 5101 while (i--) { 5102 self.add(target[i], events, func, scope); 5103 } 5104 5105 return; 5106 } 5107 5108 // Old API called ready init 5109 if (events === "init") { 5110 events = "ready"; 5111 } 5112 5113 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 5114 }; 5115 5116 self.remove = function(target, events, func, scope) { 5117 if (!target) { 5118 return self; 5119 } 5120 5121 // Old API supported direct ID assignment 5122 if (typeof(target) === "string") { 5123 target = document.getElementById(target); 5124 } 5125 5126 // Old API supported multiple targets 5127 if (target instanceof Array) { 5128 var i = target.length; 5129 5130 while (i--) { 5131 self.remove(target[i], events, func, scope); 5132 } 5133 5134 return self; 5135 } 5136 5137 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 5138 }; 5139 5140 self.clear = function(target) { 5141 // Old API supported direct ID assignment 5142 if (typeof(target) === "string") { 5143 target = document.getElementById(target); 5144 } 5145 5146 return self.clean(target); 5147 }; 5148 5149 self.cancel = function(e) { 5150 if (e) { 5151 self.prevent(e); 5152 self.stop(e); 5153 } 5154 5155 return false;
5156 }; 5157 5158 self.prevent = function(e) { 5159 if (!e.preventDefault) { 5160 e = fix(e); 5161 } 5162 5163 e.preventDefault(); 5164 5165 return false; 5166 }; 5167 5168 self.stop = function(e) { 5169 if (!e.stopPropagation) { 5170 e = fix(e); 5171 } 5172 5173 e.stopPropagation(); 5174 5175 return false; 5176 }; 5177 } 5178 5179 namespace.EventUtils = EventUtils; 5180 5181 namespace.Event = new EventUtils(function(id) { 5182 return function(evt) { 5183 tinymce.dom.Event.callNativeHandler(id, evt); 5184 }; 5185 }); 5186 5187 // Bind ready event when tinymce script is loaded 5188 namespace.Event.bind(window, 'ready', function() {}); 5189 5190 namespace = 0; 5191})(tinymce.dom, 'data-mce-expando'); // Namespace and expando 5192 5193tinymce.dom.TreeWalker = function(start_node, root_node) { 5194 var node = start_node; 5195 5196 function findSibling(node, start_name, sibling_name, shallow) { 5197 var sibling, parent; 5198 5199 if (node) { 5200 // Walk into nodes if it has a start 5201 if (!shallow && node[start_name]) 5202 return node[start_name]; 5203 5204 // Return the sibling if it has one 5205 if (node != root_node) { 5206 sibling = node[sibling_name]; 5207 if (sibling) 5208 return sibling; 5209 5210 // Walk up the parents to look for siblings 5211 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 5212 sibling = parent[sibling_name]; 5213 if (sibling) 5214 return sibling; 5215 } 5216 } 5217 } 5218 }; 5219 5220 this.current = function() { 5221 return node; 5222 }; 5223 5224 this.next = function(shallow) { 5225 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 5226 }; 5227 5228 this.prev = function(shallow) { 5229 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 5230 }; 5231}; 5232 5233(function(tinymce) { 5234 // Shorten names 5235 var each = tinymce.each, 5236 is = tinymce.is, 5237 isWebKit = tinymce.isWebKit, 5238 isIE = tinymce.isIE, 5239 Entities = tinymce.html.Entities, 5240 simpleSelectorRe = /^([a-z0-9],?)+$/i, 5241 whiteSpaceRegExp = /^[ \t\r\n]*$/; 5242 5243 tinymce.create('tinymce.dom.DOMUtils', { 5244 doc : null, 5245 root : null, 5246 files : null, 5247 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 5248 props : { 5249 "for" : "htmlFor", 5250 "class" : "className", 5251 className : "className", 5252 checked : "checked", 5253 disabled : "disabled", 5254 maxlength : "maxLength", 5255 readonly : "readOnly", 5256 selected : "selected", 5257 value : "value", 5258 id : "id", 5259 name : "name", 5260 type : "type" 5261 }, 5262 5263 DOMUtils : function(d, s) { 5264 var t = this, globalStyle, name, blockElementsMap; 5265 5266 t.doc = d; 5267 t.win = window; 5268 t.files = {}; 5269 t.cssFlicker = false; 5270 t.counter = 0; 5271 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 5272 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 5273 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 5274 5275 t.settings = s = tinymce.extend({ 5276 keep_values : false, 5277 hex_colors : 1 5278 }, s); 5279 5280 t.schema = s.schema; 5281 t.styles = new tinymce.html.Styles({ 5282 url_converter : s.url_converter, 5283 url_converter_scope : s.url_converter_scope 5284 }, s.schema); 5285 5286 // Fix IE6SP2 flicker and check it failed for pre SP2 5287 if (tinymce.isIE6) { 5288 try {
5289 d.execCommand('BackgroundImageCache', false, true); 5290 } catch (e) { 5291 t.cssFlicker = true; 5292 } 5293 } 5294 5295 t.fixDoc(d); 5296 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 5297 tinymce.addUnload(t.destroy, t); 5298 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 5299 5300 t.isBlock = function(node) { 5301 // Fix for #5446 5302 if (!node) { 5303 return false; 5304 } 5305 5306 // This function is called in module pattern style since it might be executed with the wrong this scope 5307 var type = node.nodeType; 5308 5309 // If it's a node then check the type and use the nodeName 5310 if (type) 5311 return !!(type === 1 && blockElementsMap[node.nodeName]); 5312 5313 return !!blockElementsMap[node]; 5314 }; 5315 }, 5316 5317 fixDoc: function(doc) { 5318 var settings = this.settings, name; 5319 5320 if (isIE && !tinymce.isIE11 && settings.schema) { 5321 // Add missing HTML 4/5 elements to IE 5322 ('abbr article aside audio canvas ' + 5323 'details figcaption figure footer ' + 5324 'header hgroup mark menu meter nav ' + 5325 'output progress section summary ' + 5326 'time video').replace(/\w+/g, function(name) { 5327 doc.createElement(name); 5328 }); 5329 5330 // Create all custom elements 5331 for (name in settings.schema.getCustomElements()) { 5332 doc.createElement(name); 5333 } 5334 } 5335 }, 5336 5337 clone: function(node, deep) { 5338 var self = this, clone, doc; 5339 5340 // TODO: Add feature detection here in the future 5341 if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) { 5342 return node.cloneNode(deep); 5343 } 5344 5345 doc = self.doc; 5346 5347 // Make a HTML5 safe shallow copy 5348 if (!deep) { 5349 clone = doc.createElement(node.nodeName); 5350 5351 // Copy attribs 5352 each(self.getAttribs(node), function(attr) { 5353 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5354 }); 5355 5356 return clone; 5357 } 5358/* 5359 // Setup HTML5 patched document fragment 5360 if (!self.frag) { 5361 self.frag = doc.createDocumentFragment(); 5362 self.fixDoc(self.frag); 5363 } 5364 5365 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5366 clone = doc.createElement('div'); 5367 self.frag.appendChild(clone); 5368 clone.innerHTML = node.outerHTML; 5369 self.frag.removeChild(clone); 5370*/ 5371 return clone.firstChild; 5372 }, 5373 5374 getRoot : function() { 5375 var t = this, s = t.settings; 5376 5377 return (s && t.get(s.root_element)) || t.doc.body; 5378 }, 5379 5380 getViewPort : function(w) { 5381 var d, b; 5382 5383 w = !w ? this.win : w; 5384 d = w.document; 5385 b = this.boxModel ? d.documentElement : d.body; 5386 5387 // Returns viewport size excluding scrollbars 5388 return { 5389 x : w.pageXOffset || b.scrollLeft, 5390 y : w.pageYOffset || b.scrollTop, 5391 w : w.innerWidth || b.clientWidth, 5392 h : w.innerHeight || b.clientHeight 5393 }; 5394 }, 5395 5396 getRect : function(e) { 5397 var p, t = this, sr; 5398 5399 e = t.get(e); 5400 p = t.getPos(e); 5401 sr = t.getSize(e); 5402 5403 return { 5404 x : p.x, 5405 y : p.y, 5406 w : sr.w, 5407 h : sr.h 5408 }; 5409 }, 5410 5411 getSize : function(e) { 5412 var t = this, w, h; 5413 5414 e = t.get(e); 5415 w = t.getStyle(e, 'width'); 5416 h = t.getStyle(e, 'height'); 5417 5418 // Non pixel value, then force offset/clientWidth 5419 if (w.indexOf('px') === -1) 5420 w = 0; 5421 5422 // Non pixel value, then force offset/clientWidth 5423 if (h.indexOf('px') === -1) 5424 h = 0; 5425 5426 return { 5427 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5428 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5429 }; 5430 }, 5431 5432 getParent : function(n, f, r) { 5433 return this.getParents(n, f, r, false); 5434 }, 5435 5436 getParents : function(n, f, r, c) { 5437 var t = this, na, se = t.settings, o = []; 5438 5439 n = t.get(n); 5440 c = c === undefined; 5441 5442 if (se.strict_root) 5443 r = r || t.getRoot(); 5444 5445 // Wrap node name as func 5446 if (is(f, 'string')) { 5447 na = f; 5448 5449 if (f === '*') { 5450 f = function(n) {return n.nodeType == 1;}; 5451 } else { 5452 f = function(n) { 5453 return t.is(n, na); 5454 }; 5455 } 5456 } 5457 5458 while (n) { 5459 if (n == r || !n.nodeType || n.nodeType === 9) 5460 break; 5461 5462 if (!f || f(n)) { 5463 if (c) 5464 o.push(n); 5465 else 5466 return n; 5467 } 5468 5469 n = n.parentNode; 5470 } 5471 5472 return c ? o : null; 5473 }, 5474 5475 get : function(e) { 5476 var n; 5477 5478 if (e && this.doc && typeof(e) == 'string') { 5479 n = e; 5480 e = this.doc.getElementById(e); 5481 5482 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5483 if (e && e.id !== n) 5484 return this.doc.getElementsByName(n)[1]; 5485 } 5486 5487 return e; 5488 }, 5489 5490 getNext : function(node, selector) { 5491 return this._findSib(node, selector, 'nextSibling'); 5492 }, 5493 5494 getPrev : function(node, selector) { 5495 return this._findSib(node, selector, 'previousSibling'); 5496 }, 5497 5498 5499 select : function(pa, s) { 5500 var t = this; 5501
5502 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5503 }, 5504 5505 is : function(n, selector) { 5506 var i; 5507 5508 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5509 if (n.length === undefined) { 5510 // Simple all selector 5511 if (selector === '*') 5512 return n.nodeType == 1; 5513 5514 // Simple selector just elements 5515 if (simpleSelectorRe.test(selector)) { 5516 selector = selector.toLowerCase().split(/,/); 5517 n = n.nodeName.toLowerCase(); 5518 5519 for (i = selector.length - 1; i >= 0; i--) { 5520 if (selector[i] == n) 5521 return true; 5522 } 5523 5524 return false; 5525 } 5526 } 5527 5528 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5529 }, 5530 5531 5532 add : function(p, n, a, h, c) { 5533 var t = this; 5534 5535 return this.run(p, function(p) { 5536 var e, k; 5537 5538 e = is(n, 'string') ? t.doc.createElement(n) : n; 5539 t.setAttribs(e, a); 5540 5541 if (h) { 5542 if (h.nodeType) 5543 e.appendChild(h); 5544 else 5545 t.setHTML(e, h); 5546 } 5547 5548 return !c ? p.appendChild(e) : e; 5549 }); 5550 }, 5551 5552 create : function(n, a, h) { 5553 return this.add(this.doc.createElement(n), n, a, h, 1); 5554 }, 5555 5556 createHTML : function(n, a, h) { 5557 var o = '', t = this, k; 5558 5559 o += '<' + n; 5560 5561 for (k in a) { 5562 if (a.hasOwnProperty(k)) 5563 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5564 } 5565 5566 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5567 if (typeof(h) != "undefined") 5568 return o + '>' + h + '</' + n + '>'; 5569 5570 return o + ' >'; 5571 }, 5572 5573 remove : function(node, keep_children) { 5574 return this.run(node, function(node) { 5575 var child, parent = node.parentNode; 5576 5577 if (!parent) 5578 return null; 5579 5580 if (keep_children) { 5581 while (child = node.firstChild) { 5582 // IE 8 will crash if you don't remove completely empty text nodes 5583 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5584 parent.insertBefore(child, node); 5585 else 5586 node.removeChild(child); 5587 } 5588 } 5589 5590 return parent.removeChild(node); 5591 }); 5592 }, 5593 5594 setStyle : function(n, na, v) { 5595 var t = this; 5596 5597 return t.run(n, function(e) { 5598 var s, i; 5599 5600 s = e.style; 5601 5602 // Camelcase it, if needed 5603 na = na.replace(/-(\D)/g, function(a, b){ 5604 return b.toUpperCase(); 5605 }); 5606 5607 // Default px suffix on these 5608 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5609 v += 'px'; 5610 5611 switch (na) { 5612 case 'opacity': 5613 // IE specific opacity 5614 if (isIE && ! tinymce.isIE11) { 5615 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5616 5617 if (!n.currentStyle || !n.currentStyle.hasLayout) 5618 s.display = 'inline-block'; 5619 } 5620 5621 // Fix for older browsers 5622 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5623 break; 5624 5625 case 'float': 5626 (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v; 5627 break; 5628 5629 default: 5630 s[na] = v || ''; 5631 } 5632 5633 // Force update of the style data 5634 if (t.settings.update_styles) 5635 t.setAttrib(e, 'data-mce-style'); 5636 }); 5637 }, 5638 5639 getStyle : function(n, na, c) { 5640 n = this.get(n); 5641 5642 if (!n) 5643 return; 5644 5645 // Gecko 5646 if (this.doc.defaultView && c) { 5647 // Remove camelcase 5648 na = na.replace(/[A-Z]/g, function(a){ 5649 return '-' + a; 5650 }); 5651 5652 try { 5653 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5654 } catch (ex) { 5655 // Old safari might fail 5656 return null; 5657 } 5658 } 5659 5660 // Camelcase it, if needed 5661 na = na.replace(/-(\D)/g, function(a, b){ 5662 return b.toUpperCase(); 5663 }); 5664 5665 if (na == 'float') 5666 na = isIE ? 'styleFloat' : 'cssFloat'; 5667 5668 // IE & Opera 5669 if (n.currentStyle && c) 5670 return n.currentStyle[na]; 5671 5672 return n.style ? n.style[na] : undefined; 5673 }, 5674 5675 setStyles : function(e, o) { 5676 var t = this, s = t.settings, ol; 5677 5678 ol = s.update_styles; 5679 s.update_styles = 0; 5680 5681 each(o, function(v, n) { 5682 t.setStyle(e, n, v); 5683 }); 5684 5685 // Update style info 5686 s.update_styles = ol; 5687 if (s.update_styles) 5688 t.setAttrib(e, s.cssText); 5689 }, 5690 5691 removeAllAttribs: function(e) { 5692 return this.run(e, function(e) { 5693 var i, attrs = e.attributes; 5694 for (i = attrs.length - 1; i >= 0; i--) { 5695 e.removeAttributeNode(attrs.item(i)); 5696 } 5697 }); 5698 }, 5699 5700 setAttrib : function(e, n, v) { 5701 var t = this; 5702 5703 // Whats the point 5704 if (!e || !n) 5705 return; 5706 5707 // Strict XML mode 5708 if (t.settings.strict) 5709 n = n.toLowerCase(); 5710 5711 return this.run(e, function(e) { 5712 var s = t.settings; 5713 var originalValue = e.getAttribute(n); 5714 if (v !== null) { 5715 5716 switch (n) { 5717 case "style": 5718 if (!is(v, 'string')) { 5719 each(v, function(v, n) { 5720 t.setStyle(e, n, v); 5721 }); 5722 5723 return; 5724 } 5725 5726 // No mce_style for elements with these since they might get resized by the user 5727 if (s.keep_values) { 5728 if (v && !t._isRes(v)) 5729 e.setAttribute('data-mce-style', v, 2); 5730 else 5731 e.removeAttribute('data-mce-style', 2); 5732 } 5733 5734 e.style.cssText = v; 5735 break; 5736 5737 case "class": 5738 e.className = v || ''; // Fix IE null bug 5739 break; 5740 5741 case "src": 5742 case "href": 5743 if (s.keep_values) {
5744 if (s.url_converter) 5745 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5746 5747 t.setAttrib(e, 'data-mce-' + n, v, 2); 5748 } 5749 5750 break; 5751 5752 case "shape": 5753 e.setAttribute('data-mce-style', v); 5754 break; 5755 } 5756 } 5757 if (is(v) && v !== null && v.length !== 0) 5758 e.setAttribute(n, '' + v, 2); 5759 else 5760 e.removeAttribute(n, 2); 5761 5762 // fire onChangeAttrib event for attributes that have changed 5763 if (tinyMCE.activeEditor && originalValue != v) { 5764 var ed = tinyMCE.activeEditor; 5765 ed.onSetAttrib.dispatch(ed, e, n, v); 5766 } 5767 }); 5768 }, 5769 5770 setAttribs : function(e, o) { 5771 var t = this; 5772 5773 return this.run(e, function(e) { 5774 each(o, function(v, n) { 5775 t.setAttrib(e, n, v); 5776 }); 5777 }); 5778 }, 5779 5780 getAttrib : function(e, n, dv) { 5781 var v, t = this, undef; 5782 5783 e = t.get(e); 5784 5785 if (!e || e.nodeType !== 1) 5786 return dv === undef ? false : dv; 5787 5788 if (!is(dv)) 5789 dv = ''; 5790 5791 // Try the mce variant for these 5792 if (/^(src|href|style|coords|shape)$/.test(n)) { 5793 v = e.getAttribute("data-mce-" + n); 5794 5795 if (v) 5796 return v; 5797 } 5798 5799 if (isIE && t.props[n]) { 5800 v = e[t.props[n]]; 5801 v = v && v.nodeValue ? v.nodeValue : v; 5802 } 5803 5804 if (!v) 5805 v = e.getAttribute(n, 2); 5806 5807 // Check boolean attribs 5808 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5809 if (e[t.props[n]] === true && v === '') 5810 return n; 5811 5812 return v ? n : ''; 5813 } 5814 5815 // Inner input elements will override attributes on form elements 5816 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5817 return e.getAttributeNode(n).nodeValue; 5818 5819 if (n === 'style') { 5820 v = v || e.style.cssText; 5821 5822 if (v) { 5823 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5824 5825 if (t.settings.keep_values && !t._isRes(v)) 5826 e.setAttribute('data-mce-style', v); 5827 } 5828 } 5829 5830 // Remove Apple and WebKit stuff 5831 if (isWebKit && n === "class" && v) 5832 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5833 5834 // Handle IE issues 5835 if (isIE) { 5836 switch (n) { 5837 case 'rowspan': 5838 case 'colspan': 5839 // IE returns 1 as default value 5840 if (v === 1) 5841 v = ''; 5842 5843 break; 5844 5845 case 'size': 5846 // IE returns +0 as default value for size 5847 if (v === '+0' || v === 20 || v === 0) 5848 v = ''; 5849 5850 break; 5851 5852 case 'width': 5853 case 'height': 5854 case 'vspace': 5855 case 'checked': 5856 case 'disabled': 5857 case 'readonly': 5858 if (v === 0) 5859 v = ''; 5860 5861 break; 5862 5863 case 'hspace': 5864 // IE returns -1 as default value 5865 if (v === -1) 5866 v = ''; 5867 5868 break; 5869 5870 case 'maxlength': 5871 case 'tabindex': 5872 // IE returns default value 5873 if (v === 32768 || v === 2147483647 || v === '32768') 5874 v = ''; 5875 5876 break; 5877 5878 case 'multiple': 5879 case 'compact': 5880 case 'noshade': 5881 case 'nowrap': 5882 if (v === 65535) 5883 return n; 5884 5885 return dv; 5886 5887 case 'shape': 5888 v = v.toLowerCase(); 5889 break; 5890 5891 default: 5892 // IE has odd anonymous function for event attributes 5893 if (n.indexOf('on') === 0 && v) 5894 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5895 } 5896 } 5897 5898 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5899 }, 5900 5901 getPos : function(n, ro) { 5902 var t = this, x = 0, y = 0, e, d = t.doc, r; 5903 5904 n = t.get(n); 5905 ro = ro || d.body; 5906 5907 if (n) { 5908 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5909 if (n.getBoundingClientRect) { 5910 n = n.getBoundingClientRect(); 5911 e = t.boxModel ? d.documentElement : d.body; 5912 5913 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5914 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5915 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5916 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5917 5918 return {x : x, y : y}; 5919 } 5920 5921 r = n;
5922 while (r && r != ro && r.nodeType) { 5923 x += r.offsetLeft || 0; 5924 y += r.offsetTop || 0; 5925 r = r.offsetParent; 5926 } 5927 5928 r = n.parentNode; 5929 while (r && r != ro && r.nodeType) { 5930 x -= r.scrollLeft || 0; 5931 y -= r.scrollTop || 0; 5932 r = r.parentNode; 5933 } 5934 } 5935 5936 return {x : x, y : y}; 5937 }, 5938 5939 parseStyle : function(st) { 5940 return this.styles.parse(st); 5941 }, 5942 5943 serializeStyle : function(o, name) { 5944 return this.styles.serialize(o, name); 5945 }, 5946 5947 addStyle: function(cssText) { 5948 var doc = this.doc, head; 5949 5950 // Create style element if needed 5951 styleElm = doc.getElementById('mceDefaultStyles'); 5952 if (!styleElm) { 5953 styleElm = doc.createElement('style'), 5954 styleElm.id = 'mceDefaultStyles'; 5955 styleElm.type = 'text/css'; 5956 5957 head = doc.getElementsByTagName('head')[0]; 5958 if (head.firstChild) { 5959 head.insertBefore(styleElm, head.firstChild); 5960 } else { 5961 head.appendChild(styleElm); 5962 } 5963 } 5964 5965 // Append style data to old or new style element 5966 if (styleElm.styleSheet) { 5967 styleElm.styleSheet.cssText += cssText; 5968 } else { 5969 styleElm.appendChild(doc.createTextNode(cssText)); 5970 } 5971 }, 5972 5973 loadCSS : function(u) { 5974 var t = this, d = t.doc, head; 5975 5976 if (!u) 5977 u = ''; 5978 5979 head = d.getElementsByTagName('head')[0]; 5980 5981 each(u.split(','), function(u) { 5982 var link; 5983 5984 if (t.files[u]) 5985 return; 5986 5987 t.files[u] = true; 5988 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5989 5990 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5991 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5992 // It's ugly but it seems to work fine. 5993 if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) { 5994 link.onload = function() { 5995 if (d.recalc) 5996 d.recalc(); 5997 5998 link.onload = null; 5999 }; 6000 } 6001 6002 head.appendChild(link); 6003 }); 6004 }, 6005 6006 addClass : function(e, c) { 6007 return this.run(e, function(e) { 6008 var o; 6009 6010 if (!c) 6011 return 0; 6012 6013 if (this.hasClass(e, c)) 6014 return e.className; 6015 6016 o = this.removeClass(e, c); 6017 6018 return e.className = (o != '' ? (o + ' ') : '') + c; 6019 }); 6020 }, 6021 6022 removeClass : function(e, c) { 6023 var t = this, re; 6024 6025 return t.run(e, function(e) { 6026 var v; 6027 6028 if (t.hasClass(e, c)) { 6029 if (!re) 6030 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 6031 6032 v = e.className.replace(re, ' '); 6033 v = tinymce.trim(v != ' ' ? v : ''); 6034 6035 e.className = v; 6036 6037 // Empty class attr 6038 if (!v) { 6039 e.removeAttribute('class'); 6040 e.removeAttribute('className'); 6041 } 6042 6043 return v; 6044 } 6045 6046 return e.className; 6047 }); 6048 }, 6049 6050 hasClass : function(n, c) { 6051 n = this.get(n); 6052 6053 if (!n || !c) 6054 return false; 6055 6056 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 6057 }, 6058 6059 show : function(e) { 6060 return this.setStyle(e, 'display', 'block'); 6061 }, 6062 6063 hide : function(e) { 6064 return this.setStyle(e, 'display', 'none'); 6065 }, 6066 6067 isHidden : function(e) { 6068 e = this.get(e); 6069 6070 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 6071 }, 6072 6073 uniqueId : function(p) { 6074 return (!p ? 'mce_' : p) + (this.counter++); 6075 }, 6076 6077 setHTML : function(element, html) { 6078 var self = this; 6079 6080 return self.run(element, function(element) { 6081 if (isIE) { 6082 // Remove all child nodes, IE keeps empty text nodes in DOM 6083 while (element.firstChild) 6084 element.removeChild(element.firstChild); 6085 6086 try { 6087 // IE will remove comments from the beginning 6088 // unless you padd the contents with something 6089 element.innerHTML = '<br />' + html; 6090 element.removeChild(element.firstChild); 6091 } catch (ex) { 6092 // 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 6093 // This seems to fix this problem 6094 6095 // Create new div with HTML contents and a BR infront to keep comments 6096 var newElement = self.create('div'); 6097 newElement.innerHTML = '<br />' + html; 6098 6099 // Add all children from div to target 6100 each (tinymce.grep(newElement.childNodes), function(node, i) { 6101 // Skip br element 6102 if (i && element.canHaveHTML) 6103 element.appendChild(node); 6104 }); 6105 } 6106 } else 6107 element.innerHTML = html; 6108 6109 return html; 6110 }); 6111 }, 6112 6113 getOuterHTML : function(elm) { 6114 var doc, self = this; 6115 6116 elm = self.get(elm); 6117 6118 if (!elm) 6119 return null; 6120 6121 if (elm.nodeType === 1 && self.hasOuterHTML) 6122 return elm.outerHTML; 6123 6124 doc = (elm.ownerDocument || self.doc).createElement("body"); 6125 doc.appendChild(elm.cloneNode(true)); 6126 6127 return doc.innerHTML; 6128 }, 6129 6130 setOuterHTML : function(e, h, d) { 6131 var t = this; 6132 6133 function setHTML(e, h, d) { 6134 var n, tp; 6135 6136 tp = d.createElement("body"); 6137 tp.innerHTML = h; 6138 6139 n = tp.lastChild; 6140 while (n) { 6141 t.insertAfter(n.cloneNode(true), e); 6142 n = n.previousSibling; 6143 } 6144 6145 t.remove(e); 6146 }; 6147 6148 return this.run(e, function(e) { 6149 e = t.get(e); 6150 6151 // Only set HTML on elements 6152 if (e.nodeType == 1) { 6153 d = d || e.ownerDocument || t.doc; 6154 6155 if (isIE) { 6156 try { 6157 // Try outerHTML for IE it sometimes produces an unknown runtime error 6158 if (isIE && e.nodeType == 1) 6159 e.outerHTML = h; 6160 else 6161 setHTML(e, h, d); 6162 } catch (ex) { 6163 // Fix for unknown runtime error 6164 setHTML(e, h, d); 6165 } 6166 } else 6167 setHTML(e, h, d); 6168 } 6169 }); 6170 }, 6171 6172 decode : Entities.decode, 6173 6174 encode : Entities.encodeAllRaw, 6175 6176 insertAfter : function(node, reference_node) { 6177 reference_node = this.get(reference_node); 6178 6179 return this.run(node, function(node) { 6180 var parent, nextSibling; 6181 6182 parent = reference_node.parentNode; 6183 nextSibling = reference_node.nextSibling; 6184 6185 if (nextSibling) 6186 parent.insertBefore(node, nextSibling); 6187 else 6188 parent.appendChild(node); 6189 6190 return node; 6191 }); 6192 }, 6193 6194 replace : function(n, o, k) { 6195 var t = this; 6196 6197 if (is(o, 'array')) 6198 n = n.cloneNode(true); 6199 6200 return t.run(o, function(o) { 6201 if (k) { 6202 each(tinymce.grep(o.childNodes), function(c) { 6203 n.appendChild(c); 6204 }); 6205 } 6206 6207 return o.parentNode.replaceChild(n, o); 6208 }); 6209 }, 6210 6211 rename : function(elm, name) { 6212 var t = this, newElm; 6213 6214 if (elm.nodeName != name.toUpperCase()) { 6215 // Rename block element 6216 newElm = t.create(name); 6217 6218 // Copy attribs to new block 6219 each(t.getAttribs(elm), function(attr_node) { 6220 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 6221 }); 6222 6223 // Replace block 6224 t.replace(newElm, elm, 1); 6225 } 6226 6227 return newElm || elm; 6228 }, 6229 6230 findCommonAncestor : function(a, b) { 6231 var ps = a, pe; 6232 6233 while (ps) { 6234 pe = b; 6235 6236 while (pe && ps != pe) 6237 pe = pe.parentNode; 6238 6239 if (ps == pe) 6240 break; 6241 6242 ps = ps.parentNode; 6243 } 6244 6245 if (!ps && a.ownerDocument) 6246 return a.ownerDocument.documentElement; 6247 6248 return ps; 6249 }, 6250 6251 toHex : function(s) { 6252 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 6253 6254 function hex(s) { 6255 s = parseInt(s, 10).toString(16); 6256 6257 return s.length > 1 ? s : '0' + s; // 0 -> 00 6258 }; 6259 6260 if (c) { 6261 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 6262 6263 return s; 6264 } 6265 6266 return s; 6267 }, 6268 6269 getClasses : function() { 6270 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 6271 6272 if (t.classes) 6273 return t.classes; 6274 6275 function addClasses(s) { 6276 // IE style imports 6277 each(s.imports, function(r) { 6278 addClasses(r); 6279 }); 6280 6281 each(s.cssRules || s.rules, function(r) { 6282 // Real type or fake it on IE 6283 switch (r.type || 1) { 6284 // Rule 6285 case 1: 6286 if (r.selectorText) { 6287 each(r.selectorText.split(','), function(v) { 6288 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 6289 6290 // Is internal or it doesn't contain a class 6291 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 6292 return; 6293 6294 // Remove everything but class name 6295 ov = v; 6296 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 6297 6298 // Filter classes 6299 if (f && !(v = f(v, ov))) 6300 return; 6301 6302 if (!lo[v]) { 6303 cl.push({'class' : v}); 6304 lo[v] = 1; 6305 } 6306 }); 6307 } 6308 break; 6309 6310 // Import 6311 case 3: 6312 try {
6313 addClasses(r.styleSheet); 6314 } catch (ex) { 6315 // Ignore 6316 } 6317 6318 break; 6319 } 6320 }); 6321 }; 6322 6323 try { 6324 each(t.doc.styleSheets, addClasses); 6325 } catch (ex) { 6326 // Ignore 6327 } 6328 6329 if (cl.length > 0) 6330 t.classes = cl; 6331 6332 return cl; 6333 }, 6334 6335 run : function(e, f, s) { 6336 var t = this, o; 6337 6338 if (t.doc && typeof(e) === 'string') 6339 e = t.get(e); 6340 6341 if (!e) 6342 return false; 6343 6344 s = s || this; 6345 if (!e.nodeType && (e.length || e.length === 0)) { 6346 o = []; 6347 6348 each(e, function(e, i) { 6349 if (e) { 6350 if (typeof(e) == 'string') 6351 e = t.doc.getElementById(e); 6352 6353 o.push(f.call(s, e, i)); 6354 } 6355 }); 6356 6357 return o; 6358 } 6359 6360 return f.call(s, e); 6361 }, 6362 6363 getAttribs : function(n) { 6364 var o; 6365 6366 n = this.get(n); 6367 6368 if (!n) 6369 return []; 6370 6371 if (isIE) { 6372 o = []; 6373 6374 // Object will throw exception in IE 6375 if (n.nodeName == 'OBJECT') 6376 return n.attributes; 6377 6378 // IE doesn't keep the selected attribute if you clone option elements 6379 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6380 o.push({specified : 1, nodeName : 'selected'}); 6381 6382 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6383 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6384 o.push({specified : 1, nodeName : a}); 6385 }); 6386 6387 return o; 6388 } 6389 6390 return n.attributes; 6391 }, 6392 6393 isEmpty : function(node, elements) { 6394 var self = this, i, attributes, type, walker, name, brCount = 0; 6395 6396 node = node.firstChild; 6397 if (node) { 6398 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6399 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6400 6401 do { 6402 type = node.nodeType; 6403 6404 if (type === 1) { 6405 // Ignore bogus elements 6406 if (node.getAttribute('data-mce-bogus')) 6407 continue; 6408 6409 // Keep empty elements like <img /> 6410 name = node.nodeName.toLowerCase(); 6411 if (elements && elements[name]) { 6412 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6413 if (name === 'br') { 6414 brCount++; 6415 continue; 6416 } 6417 6418 return false; 6419 } 6420 6421 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6422 attributes = self.getAttribs(node); 6423 i = node.attributes.length; 6424 while (i--) { 6425 name = node.attributes[i].nodeName; 6426 if (name === "name" || name === 'data-mce-bookmark') 6427 return false; 6428 } 6429 } 6430 6431 // Keep comment nodes 6432 if (type == 8) 6433 return false; 6434 6435 // Keep non whitespace text nodes 6436 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6437 return false; 6438 } while (node = walker.next()); 6439 } 6440 6441 return brCount <= 1; 6442 }, 6443 6444 destroy : function(s) { 6445 var t = this; 6446 6447 t.win = t.doc = t.root = t.events = t.frag = null; 6448 6449 // Manual destroy then remove unload handler 6450 if (!s) 6451 tinymce.removeUnload(t.destroy); 6452 }, 6453 6454 createRng : function() { 6455 var d = this.doc; 6456 6457 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6458 }, 6459 6460 nodeIndex : function(node, normalized) { 6461 var idx = 0, lastNodeType, lastNode, nodeType; 6462 6463 if (node) { 6464 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6465 nodeType = node.nodeType; 6466 6467 // Normalize text nodes 6468 if (normalized && nodeType == 3) { 6469 if (nodeType == lastNodeType || !node.nodeValue.length) 6470 continue; 6471 } 6472 idx++; 6473 lastNodeType = nodeType; 6474 } 6475 } 6476 6477 return idx; 6478 }, 6479 6480 split : function(pe, e, re) { 6481 var t = this, r = t.createRng(), bef, aft, pa; 6482 6483 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6484 // but we don't want that in our code since it serves no purpose for the end user 6485 // For example if this is chopped: 6486 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6487 // would produce: 6488 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6489 // this function will then trim of empty edges and produce: 6490 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6491 function trim(node) { 6492 var i, children = node.childNodes, type = node.nodeType; 6493 6494 function surroundedBySpans(node) { 6495 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6496 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6497 return previousIsSpan && nextIsSpan; 6498 } 6499 6500 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6501 return; 6502 6503 for (i = children.length - 1; i >= 0; i--) 6504 trim(children[i]); 6505 6506 if (type != 9) { 6507 // Keep non whitespace text nodes 6508 if (type == 3 && node.nodeValue.length > 0) { 6509 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6510 // Also keep text nodes with only spaces if surrounded by spans. 6511 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6512 var trimmedLength = tinymce.trim(node.nodeValue).length; 6513 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6514 return; 6515 } else if (type == 1) { 6516 // If the only child is a bookmark then move it up 6517 children = node.childNodes; 6518 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6519 node.parentNode.insertBefore(children[0], node); 6520 6521 // Keep non empty elements or img, hr etc 6522 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6523 return; 6524 } 6525 6526 t.remove(node); 6527 } 6528 6529 return node; 6530 }; 6531 6532 if (pe && e) { 6533 // Get before chunk 6534 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6535 r.setEnd(e.parentNode, t.nodeIndex(e)); 6536 bef = r.extractContents(); 6537 6538 // Get after chunk 6539 r = t.createRng(); 6540 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6541 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6542 aft = r.extractContents(); 6543 6544 // Insert before chunk 6545 pa = pe.parentNode; 6546 pa.insertBefore(trim(bef), pe); 6547 6548 // Insert middle chunk 6549 if (re) 6550 pa.replaceChild(re, e); 6551 else 6552 pa.insertBefore(e, pe); 6553 6554 // Insert after chunk 6555 pa.insertBefore(trim(aft), pe); 6556 t.remove(pe); 6557 6558 return re || e; 6559 } 6560 }, 6561 6562 bind : function(target, name, func, scope) { 6563 return this.events.add(target, name, func, scope || this); 6564 }, 6565 6566 unbind : function(target, name, func) { 6567 return this.events.remove(target, name, func); 6568 }, 6569 6570 fire : function(target, name, evt) { 6571 return this.events.fire(target, name, evt); 6572 }, 6573 6574 // Returns the content editable state of a node 6575 getContentEditable: function(node) { 6576 var contentEditable; 6577 6578 // Check type 6579 if (node.nodeType != 1) { 6580 return null; 6581 } 6582 6583 // Check for fake content editable 6584 contentEditable = node.getAttribute("data-mce-contenteditable"); 6585 if (contentEditable && contentEditable !== "inherit") { 6586 return contentEditable; 6587 } 6588 6589 // Check for real content editable 6590 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6591 }, 6592 6593 6594 _findSib : function(node, selector, name) { 6595 var t = this, f = selector; 6596 6597 if (node) { 6598 // If expression make a function of it using is 6599 if (is(f, 'string')) { 6600 f = function(node) { 6601 return t.is(node, selector); 6602 }; 6603 } 6604 6605 // Loop all siblings 6606 for (node = node[name]; node; node = node[name]) { 6607 if (f(node)) 6608 return node; 6609 } 6610 } 6611 6612 return null; 6613 }, 6614 6615 _isRes : function(c) { 6616 // Is live resizble element 6617 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6618 } 6619 6620 /* 6621 walk : function(n, f, s) { 6622 var d = this.doc, w; 6623 6624 if (d.createTreeWalker) { 6625 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6626 6627 while ((n = w.nextNode()) != null) 6628 f.call(s || this, n); 6629 } else 6630 tinymce.walk(n, f, 'childNodes', s); 6631 } 6632 */ 6633 6634 /* 6635 toRGB : function(s) { 6636 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6637 6638 if (c) { 6639 // #FFF -> #FFFFFF 6640 if (!is(c[3])) 6641 c[3] = c[2] = c[1]; 6642 6643 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6644 } 6645 6646 return s; 6647 } 6648 */ 6649 }); 6650 6651 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6652})(tinymce); 6653 6654(function(ns) { 6655 // Range constructor 6656 function Range(dom) { 6657 var t = this, 6658 doc = dom.doc, 6659 EXTRACT = 0, 6660 CLONE = 1, 6661 DELETE = 2, 6662 TRUE = true, 6663 FALSE = false, 6664 START_OFFSET = 'startOffset', 6665 START_CONTAINER = 'startContainer', 6666 END_CONTAINER = 'endContainer', 6667 END_OFFSET = 'endOffset', 6668 extend = tinymce.extend, 6669 nodeIndex = dom.nodeIndex; 6670 6671 extend(t, { 6672 // Inital states 6673 startContainer : doc, 6674 startOffset : 0, 6675 endContainer : doc, 6676 endOffset : 0, 6677 collapsed : TRUE,
6678 commonAncestorContainer : doc, 6679 6680 // Range constants 6681 START_TO_START : 0, 6682 START_TO_END : 1, 6683 END_TO_END : 2, 6684 END_TO_START : 3, 6685 6686 // Public methods 6687 setStart : setStart, 6688 setEnd : setEnd, 6689 setStartBefore : setStartBefore, 6690 setStartAfter : setStartAfter, 6691 setEndBefore : setEndBefore, 6692 setEndAfter : setEndAfter, 6693 collapse : collapse, 6694 selectNode : selectNode, 6695 selectNodeContents : selectNodeContents, 6696 compareBoundaryPoints : compareBoundaryPoints, 6697 deleteContents : deleteContents, 6698 extractContents : extractContents, 6699 cloneContents : cloneContents, 6700 insertNode : insertNode, 6701 surroundContents : surroundContents, 6702 cloneRange : cloneRange, 6703 toStringIE : toStringIE 6704 }); 6705 6706 function createDocumentFragment() { 6707 return doc.createDocumentFragment(); 6708 }; 6709 6710 function setStart(n, o) { 6711 _setEndPoint(TRUE, n, o); 6712 }; 6713 6714 function setEnd(n, o) { 6715 _setEndPoint(FALSE, n, o); 6716 }; 6717 6718 function setStartBefore(n) { 6719 setStart(n.parentNode, nodeIndex(n)); 6720 }; 6721 6722 function setStartAfter(n) { 6723 setStart(n.parentNode, nodeIndex(n) + 1); 6724 }; 6725 6726 function setEndBefore(n) { 6727 setEnd(n.parentNode, nodeIndex(n)); 6728 }; 6729 6730 function setEndAfter(n) { 6731 setEnd(n.parentNode, nodeIndex(n) + 1); 6732 }; 6733 6734 function collapse(ts) { 6735 if (ts) { 6736 t[END_CONTAINER] = t[START_CONTAINER]; 6737 t[END_OFFSET] = t[START_OFFSET]; 6738 } else { 6739 t[START_CONTAINER] = t[END_CONTAINER]; 6740 t[START_OFFSET] = t[END_OFFSET]; 6741 } 6742 6743 t.collapsed = TRUE; 6744 }; 6745 6746 function selectNode(n) { 6747 setStartBefore(n); 6748 setEndAfter(n); 6749 }; 6750 6751 function selectNodeContents(n) { 6752 setStart(n, 0); 6753 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6754 }; 6755 6756 function compareBoundaryPoints(h, r) { 6757 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6758 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6759 6760 // Check START_TO_START 6761 if (h === 0) 6762 return _compareBoundaryPoints(sc, so, rsc, rso); 6763 6764 // Check START_TO_END 6765 if (h === 1) 6766 return _compareBoundaryPoints(ec, eo, rsc, rso); 6767 6768 // Check END_TO_END 6769 if (h === 2) 6770 return _compareBoundaryPoints(ec, eo, rec, reo); 6771 6772 // Check END_TO_START 6773 if (h === 3) 6774 return _compareBoundaryPoints(sc, so, rec, reo); 6775 }; 6776 6777 function deleteContents() { 6778 _traverse(DELETE); 6779 }; 6780 6781 function extractContents() { 6782 return _traverse(EXTRACT); 6783 }; 6784 6785 function cloneContents() { 6786 return _traverse(CLONE); 6787 }; 6788 6789 function insertNode(n) { 6790 var startContainer = this[START_CONTAINER], 6791 startOffset = this[START_OFFSET], nn, o; 6792 6793 // Node is TEXT_NODE or CDATA 6794 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6795 if (!startOffset) { 6796 // At the start of text 6797 startContainer.parentNode.insertBefore(n, startContainer); 6798 } else if (startOffset >= startContainer.nodeValue.length) { 6799 // At the end of text 6800 dom.insertAfter(n, startContainer); 6801 } else { 6802 // Middle, need to split 6803 nn = startContainer.splitText(startOffset); 6804 startContainer.parentNode.insertBefore(n, nn); 6805 } 6806 } else { 6807 // Insert element node 6808 if (startContainer.childNodes.length > 0) 6809 o = startContainer.childNodes[startOffset]; 6810 6811 if (o) 6812 startContainer.insertBefore(n, o); 6813 else 6814 startContainer.appendChild(n); 6815 } 6816 }; 6817 6818 function surroundContents(n) { 6819 var f = t.extractContents(); 6820 6821 t.insertNode(n); 6822 n.appendChild(f); 6823 t.selectNode(n); 6824 }; 6825 6826 function cloneRange() { 6827 return extend(new Range(dom), { 6828 startContainer : t[START_CONTAINER], 6829 startOffset : t[START_OFFSET], 6830 endContainer : t[END_CONTAINER], 6831 endOffset : t[END_OFFSET], 6832 collapsed : t.collapsed, 6833 commonAncestorContainer : t.commonAncestorContainer 6834 }); 6835 }; 6836 6837 // Private methods 6838 6839 function _getSelectedNode(container, offset) { 6840 var child; 6841 6842 if (container.nodeType == 3 /* TEXT_NODE */) 6843 return container; 6844 6845 if (offset < 0) 6846 return container; 6847 6848 child = container.firstChild; 6849 while (child && offset > 0) { 6850 --offset; 6851 child = child.nextSibling; 6852 } 6853 6854 if (child) 6855 return child; 6856 6857 return container; 6858 }; 6859 6860 function _isCollapsed() { 6861 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6862 }; 6863 6864 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6865 var c, offsetC, n, cmnRoot, childA, childB; 6866 6867 // In the first case the boundary-points have the same container. A is before B 6868 // if its offset is less than the offset of B, A is equal to B if its offset is 6869 // equal to the offset of B, and A is after B if its offset is greater than the 6870 // offset of B. 6871 if (containerA == containerB) { 6872 if (offsetA == offsetB) 6873 return 0; // equal 6874 6875 if (offsetA < offsetB) 6876 return -1; // before 6877 6878 return 1; // after 6879 } 6880 6881 // In the second case a child node C of the container of A is an ancestor 6882 // container of B. In this case, A is before B if the offset of A is less than or 6883 // equal to the index of the child node C and A is after B otherwise. 6884 c = containerB; 6885 while (c && c.parentNode != containerA) 6886 c = c.parentNode; 6887 6888 if (c) { 6889 offsetC = 0; 6890 n = containerA.firstChild; 6891 6892 while (n != c && offsetC < offsetA) { 6893 offsetC++; 6894 n = n.nextSibling; 6895 } 6896 6897 if (offsetA <= offsetC) 6898 return -1; // before 6899 6900 return 1; // after 6901 } 6902 6903 // In the third case a child node C of the container of B is an ancestor container 6904 // of A. In this case, A is before B if the index of the child node C is less than 6905 // the offset of B and A is after B otherwise. 6906 c = containerA; 6907 while (c && c.parentNode != containerB) { 6908 c = c.parentNode; 6909 } 6910 6911 if (c) { 6912 offsetC = 0; 6913 n = containerB.firstChild; 6914 6915 while (n != c && offsetC < offsetB) { 6916 offsetC++; 6917 n = n.nextSibling; 6918 } 6919 6920 if (offsetC < offsetB) 6921 return -1; // before 6922 6923 return 1; // after 6924 } 6925 6926 // In the fourth case, none of three other cases hold: the containers of A and B 6927 // are siblings or descendants of sibling nodes. In this case, A is before B if 6928 // the container of A is before the container of B in a pre-order traversal of the 6929 // Ranges' context tree and A is after B otherwise. 6930 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6931 childA = containerA; 6932 6933 while (childA && childA.parentNode != cmnRoot) 6934 childA = childA.parentNode; 6935 6936 if (!childA) 6937 childA = cmnRoot; 6938 6939 childB = containerB; 6940 while (childB && childB.parentNode != cmnRoot) 6941 childB = childB.parentNode; 6942 6943 if (!childB) 6944 childB = cmnRoot; 6945 6946 if (childA == childB) 6947 return 0; // equal 6948 6949 n = cmnRoot.firstChild; 6950 while (n) { 6951 if (n == childA) 6952 return -1; // before 6953 6954 if (n == childB) 6955 return 1; // after 6956 6957 n = n.nextSibling; 6958 } 6959 }; 6960 6961 function _setEndPoint(st, n, o) { 6962 var ec, sc; 6963 6964 if (st) { 6965 t[START_CONTAINER] = n; 6966 t[START_OFFSET] = o; 6967 } else { 6968 t[END_CONTAINER] = n; 6969 t[END_OFFSET] = o; 6970 } 6971 6972 // If one boundary-point of a Range is set to have a root container 6973 // other than the current one for the Range, the Range is collapsed to 6974 // the new position. This enforces the restriction that both boundary- 6975 // points of a Range must have the same root container. 6976 ec = t[END_CONTAINER]; 6977 while (ec.parentNode) 6978 ec = ec.parentNode; 6979 6980 sc = t[START_CONTAINER]; 6981 while (sc.parentNode) 6982 sc = sc.parentNode; 6983 6984 if (sc == ec) { 6985 // The start position of a Range is guaranteed to never be after the 6986 // end position. To enforce this restriction, if the start is set to 6987 // be at a position after the end, the Range is collapsed to that 6988 // position. 6989 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6990 t.collapse(st); 6991 } else 6992 t.collapse(st); 6993 6994 t.collapsed = _isCollapsed(); 6995 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6996 }; 6997 6998 function _traverse(how) { 6999 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 7000 7001 if (t[START_CONTAINER] == t[END_CONTAINER]) 7002 return _traverseSameContainer(how); 7003 7004 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 7005 if (p == t[START_CONTAINER]) 7006 return _traverseCommonStartContainer(c, how); 7007 7008 ++endContainerDepth; 7009 } 7010 7011 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 7012 if (p == t[END_CONTAINER]) 7013 return _traverseCommonEndContainer(c, how); 7014 7015 ++startContainerDepth; 7016 } 7017 7018 depthDiff = startContainerDepth - endContainerDepth; 7019 7020 startNode = t[START_CONTAINER]; 7021 while (depthDiff > 0) { 7022 startNode = startNode.parentNode; 7023 depthDiff--; 7024 } 7025 7026 endNode = t[END_CONTAINER]; 7027 while (depthDiff < 0) { 7028 endNode = endNode.parentNode; 7029 depthDiff++; 7030 } 7031 7032 // ascend the ancestor hierarchy until we have a common parent. 7033 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 7034 startNode = sp; 7035 endNode = ep; 7036 } 7037 7038 return _traverseCommonAncestors(startNode, endNode, how); 7039 }; 7040 7041 function _traverseSameContainer(how) { 7042 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 7043 7044 if (how != DELETE) 7045 frag = createDocumentFragment(); 7046 7047 // If selection is empty, just return the fragment 7048 if (t[START_OFFSET] == t[END_OFFSET]) 7049 return frag; 7050 7051 // Text node needs special case handling 7052 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 7053 // get the substring 7054 s = t[START_CONTAINER].nodeValue; 7055 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 7056 7057 // set the original text node to its new value 7058 if (how != CLONE) { 7059 n = t[START_CONTAINER]; 7060 start = t[START_OFFSET]; 7061 len = t[END_OFFSET] - t[START_OFFSET]; 7062 7063 if (start === 0 && len >= n.nodeValue.length - 1) { 7064 n.parentNode.removeChild(n); 7065 } else { 7066 n.deleteData(start, len); 7067 } 7068 7069 // Nothing is partially selected, so collapse to start point 7070 t.collapse(TRUE); 7071 } 7072 7073 if (how == DELETE) 7074 return; 7075 7076 if (sub.length > 0) { 7077 frag.appendChild(doc.createTextNode(sub)); 7078 } 7079 7080 return frag; 7081 } 7082 7083 // Copy nodes between the start/end offsets. 7084 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 7085 cnt = t[END_OFFSET] - t[START_OFFSET]; 7086 7087 while (n && cnt > 0) { 7088 sibling = n.nextSibling; 7089 xferNode = _traverseFullySelected(n, how); 7090 7091 if (frag) 7092 frag.appendChild( xferNode ); 7093 7094 --cnt; 7095 n = sibling; 7096 } 7097 7098 // Nothing is partially selected, so collapse to start point 7099 if (how != CLONE) 7100 t.collapse(TRUE); 7101 7102 return frag; 7103 }; 7104 7105 function _traverseCommonStartContainer(endAncestor, how) { 7106 var frag, n, endIdx, cnt, sibling, xferNode; 7107 7108 if (how != DELETE) 7109 frag = createDocumentFragment(); 7110 7111 n = _traverseRightBoundary(endAncestor, how); 7112 7113 if (frag) 7114 frag.appendChild(n); 7115 7116 endIdx = nodeIndex(endAncestor); 7117 cnt = endIdx - t[START_OFFSET]; 7118 7119 if (cnt <= 0) { 7120 // Collapse to just before the endAncestor, which 7121 // is partially selected. 7122 if (how != CLONE) { 7123 t.setEndBefore(endAncestor); 7124 t.collapse(FALSE); 7125 } 7126 7127 return frag; 7128 } 7129 7130 n = endAncestor.previousSibling; 7131 while (cnt > 0) { 7132 sibling = n.previousSibling; 7133 xferNode = _traverseFullySelected(n, how); 7134 7135 if (frag) 7136 frag.insertBefore(xferNode, frag.firstChild); 7137 7138 --cnt; 7139 n = sibling; 7140 } 7141 7142 // Collapse to just before the endAncestor, which 7143 // is partially selected. 7144 if (how != CLONE) { 7145 t.setEndBefore(endAncestor); 7146 t.collapse(FALSE); 7147 } 7148 7149 return frag; 7150 }; 7151 7152 function _traverseCommonEndContainer(startAncestor, how) { 7153 var frag, startIdx, n, cnt, sibling, xferNode; 7154 7155 if (how != DELETE) 7156 frag = createDocumentFragment(); 7157 7158 n = _traverseLeftBoundary(startAncestor, how); 7159 if (frag) 7160 frag.appendChild(n); 7161 7162 startIdx = nodeIndex(startAncestor); 7163 ++startIdx; // Because we already traversed it 7164 7165 cnt = t[END_OFFSET] - startIdx; 7166 n = startAncestor.nextSibling; 7167 while (n && cnt > 0) { 7168 sibling = n.nextSibling; 7169 xferNode = _traverseFullySelected(n, how); 7170 7171 if (frag) 7172 frag.appendChild(xferNode); 7173 7174 --cnt; 7175 n = sibling; 7176 } 7177 7178 if (how != CLONE) { 7179 t.setStartAfter(startAncestor); 7180 t.collapse(TRUE); 7181 } 7182 7183 return frag; 7184 }; 7185 7186 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 7187 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 7188 7189 if (how != DELETE) 7190 frag = createDocumentFragment(); 7191 7192 n = _traverseLeftBoundary(startAncestor, how); 7193 if (frag) 7194 frag.appendChild(n); 7195 7196 commonParent = startAncestor.parentNode; 7197 startOffset = nodeIndex(startAncestor); 7198 endOffset = nodeIndex(endAncestor); 7199 ++startOffset; 7200 7201 cnt = endOffset - startOffset; 7202 sibling = startAncestor.nextSibling; 7203 7204 while (cnt > 0) { 7205 nextSibling = sibling.nextSibling; 7206 n = _traverseFullySelected(sibling, how); 7207 7208 if (frag) 7209 frag.appendChild(n); 7210 7211 sibling = nextSibling; 7212 --cnt; 7213 } 7214 7215 n = _traverseRightBoundary(endAncestor, how); 7216 7217 if (frag) 7218 frag.appendChild(n); 7219 7220 if (how != CLONE) { 7221 t.setStartAfter(startAncestor); 7222 t.collapse(TRUE); 7223 } 7224 7225 return frag; 7226 }; 7227 7228 function _traverseRightBoundary(root, how) {
7229 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 7230 7231 if (next == root) 7232 return _traverseNode(next, isFullySelected, FALSE, how); 7233 7234 parent = next.parentNode; 7235 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 7236 7237 while (parent) { 7238 while (next) { 7239 prevSibling = next.previousSibling; 7240 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 7241 7242 if (how != DELETE) 7243 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 7244 7245 isFullySelected = TRUE; 7246 next = prevSibling; 7247 } 7248 7249 if (parent == root) 7250 return clonedParent; 7251 7252 next = parent.previousSibling; 7253 parent = parent.parentNode; 7254 7255 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 7256 7257 if (how != DELETE) 7258 clonedGrandParent.appendChild(clonedParent); 7259 7260 clonedParent = clonedGrandParent; 7261 } 7262 }; 7263 7264 function _traverseLeftBoundary(root, how) { 7265 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 7266 7267 if (next == root) 7268 return _traverseNode(next, isFullySelected, TRUE, how); 7269 7270 parent = next.parentNode; 7271 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 7272 7273 while (parent) { 7274 while (next) { 7275 nextSibling = next.nextSibling; 7276 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 7277 7278 if (how != DELETE) 7279 clonedParent.appendChild(clonedChild); 7280 7281 isFullySelected = TRUE; 7282 next = nextSibling; 7283 } 7284 7285 if (parent == root) 7286 return clonedParent; 7287 7288 next = parent.nextSibling; 7289 parent = parent.parentNode; 7290 7291 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 7292 7293 if (how != DELETE) 7294 clonedGrandParent.appendChild(clonedParent); 7295 7296 clonedParent = clonedGrandParent; 7297 } 7298 }; 7299 7300 function _traverseNode(n, isFullySelected, isLeft, how) { 7301 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 7302 7303 if (isFullySelected) 7304 return _traverseFullySelected(n, how); 7305 7306 if (n.nodeType == 3 /* TEXT_NODE */) { 7307 txtValue = n.nodeValue; 7308 7309 if (isLeft) { 7310 offset = t[START_OFFSET]; 7311 newNodeValue = txtValue.substring(offset); 7312 oldNodeValue = txtValue.substring(0, offset); 7313 } else { 7314 offset = t[END_OFFSET]; 7315 newNodeValue = txtValue.substring(0, offset); 7316 oldNodeValue = txtValue.substring(offset); 7317 } 7318 7319 if (how != CLONE) 7320 n.nodeValue = oldNodeValue; 7321 7322 if (how == DELETE) 7323 return; 7324 7325 newNode = dom.clone(n, FALSE); 7326 newNode.nodeValue = newNodeValue; 7327 7328 return newNode; 7329 } 7330 7331 if (how == DELETE) 7332 return; 7333 7334 return dom.clone(n, FALSE); 7335 }; 7336 7337 function _traverseFullySelected(n, how) { 7338 if (how != DELETE) 7339 return how == CLONE ? dom.clone(n, TRUE) : n; 7340 7341 n.parentNode.removeChild(n); 7342 }; 7343 7344 function toStringIE() { 7345 return dom.create('body', null, cloneContents()).outerText; 7346 } 7347 7348 return t; 7349 }; 7350 7351 ns.Range = Range; 7352 7353 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7354 Range.prototype.toString = function() { 7355 return this.toStringIE(); 7356 }; 7357})(tinymce.dom); 7358 7359(function() { 7360 function Selection(selection) { 7361 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7362 7363 function getPosition(rng, start) { 7364 var checkRng, startIndex = 0, endIndex, inside, 7365 children, child, offset, index, position = -1, parent; 7366 7367 // Setup test range, collapse it and get the parent 7368 checkRng = rng.duplicate(); 7369 checkRng.collapse(start); 7370 parent = checkRng.parentElement(); 7371 7372 // Check if the selection is within the right document 7373 if (parent.ownerDocument !== selection.dom.doc) 7374 return; 7375 7376 // IE will report non editable elements as it's parent so look for an editable one 7377 while (parent.contentEditable === "false") { 7378 parent = parent.parentNode; 7379 } 7380 7381 // If parent doesn't have any children then return that we are inside the element 7382 if (!parent.hasChildNodes()) { 7383 return {node : parent, inside : 1}; 7384 } 7385 7386 // Setup node list and endIndex 7387 children = parent.children; 7388 endIndex = children.length - 1; 7389 7390 // Perform a binary search for the position 7391 while (startIndex <= endIndex) { 7392 index = Math.floor((startIndex + endIndex) / 2); 7393 7394 // Move selection to node and compare the ranges 7395 child = children[index];
7396 checkRng.moveToElementText(child); 7397 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7398 7399 // Before/after or an exact match 7400 if (position > 0) { 7401 endIndex = index - 1; 7402 } else if (position < 0) { 7403 startIndex = index + 1; 7404 } else { 7405 return {node : child}; 7406 } 7407 } 7408 7409 // Check if child position is before or we didn't find a position 7410 if (position < 0) { 7411 // No element child was found use the parent element and the offset inside that 7412 if (!child) { 7413 checkRng.moveToElementText(parent); 7414 checkRng.collapse(true); 7415 child = parent; 7416 inside = true; 7417 } else 7418 checkRng.collapse(false); 7419 7420 // 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 7421 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7422 offset = 0; 7423 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7424 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7425 break; 7426 } 7427 7428 offset++; 7429 } 7430 } else { 7431 // Child position is after the selection endpoint 7432 checkRng.collapse(true); 7433 7434 // 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 7435 offset = 0; 7436 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7437 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7438 break; 7439 } 7440 7441 offset++; 7442 } 7443 } 7444 7445 return {node : child, position : position, offset : offset, inside : inside}; 7446 }; 7447 7448 // Returns a W3C DOM compatible range object by using the IE Range API 7449 function getRange() { 7450 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7451 7452 // If selection is outside the current document just return an empty range 7453 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7454 if (element.ownerDocument != dom.doc) 7455 return domRange; 7456 7457 collapsed = selection.isCollapsed(); 7458 7459 // Handle control selection 7460 if (ieRange.item) { 7461 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7462 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7463 7464 return domRange; 7465 } 7466 7467 function findEndPoint(start) { 7468 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7469 7470 container = endPoint.node; 7471 offset = endPoint.offset; 7472 7473 if (endPoint.inside && !container.hasChildNodes()) { 7474 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7475 return; 7476 } 7477 7478 if (offset === undef) { 7479 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7480 return; 7481 } 7482 7483 if (endPoint.position < 0) { 7484 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7485 7486 if (!sibling) { 7487 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7488 return; 7489 } 7490 7491 if (!offset) { 7492 if (sibling.nodeType == 3) 7493 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7494 else 7495 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7496 7497 return; 7498 } 7499 7500 // Find the text node and offset 7501 while (sibling) { 7502 nodeValue = sibling.nodeValue; 7503 textNodeOffset += nodeValue.length; 7504 7505 // We are at or passed the position we where looking for 7506 if (textNodeOffset >= offset) { 7507 container = sibling; 7508 textNodeOffset -= offset; 7509 textNodeOffset = nodeValue.length - textNodeOffset; 7510 break; 7511 } 7512 7513 sibling = sibling.nextSibling; 7514 } 7515 } else { 7516 // Find the text node and offset 7517 sibling = container.previousSibling; 7518 7519 if (!sibling) 7520 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7521 7522 // If there isn't any text to loop then use the first position 7523 if (!offset) { 7524 if (container.nodeType == 3) 7525 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7526 else 7527 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7528 7529 return; 7530 } 7531 7532 while (sibling) { 7533 textNodeOffset += sibling.nodeValue.length; 7534 7535 // We are at or passed the position we where looking for 7536 if (textNodeOffset >= offset) { 7537 container = sibling; 7538 textNodeOffset -= offset; 7539 break; 7540 } 7541 7542 sibling = sibling.previousSibling; 7543 } 7544 } 7545 7546 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7547 }; 7548 7549 try { 7550 // Find start point 7551 findEndPoint(true); 7552 7553 // Find end point if needed 7554 if (!collapsed) 7555 findEndPoint(); 7556 } catch (ex) {
7557 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7558 // access the nodeValue or other properties of text nodes. This seems to happend when 7559 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7560 if (ex.number == -2147024809) { 7561 // Get the current selection 7562 bookmark = self.getBookmark(2); 7563 7564 // Get start element 7565 tmpRange = ieRange.duplicate(); 7566 tmpRange.collapse(true); 7567 element = tmpRange.parentElement(); 7568 7569 // Get end element 7570 if (!collapsed) { 7571 tmpRange = ieRange.duplicate(); 7572 tmpRange.collapse(false); 7573 element2 = tmpRange.parentElement(); 7574 element2.innerHTML = element2.innerHTML; 7575 } 7576 7577 // Remove the broken elements 7578 element.innerHTML = element.innerHTML; 7579 7580 // Restore the selection 7581 self.moveToBookmark(bookmark); 7582 7583 // Since the range has moved we need to re-get it 7584 ieRange = selection.getRng(); 7585 7586 // Find start point 7587 findEndPoint(true); 7588 7589 // Find end point if needed 7590 if (!collapsed) 7591 findEndPoint(); 7592 } else 7593 throw ex; // Throw other errors 7594 } 7595 7596 return domRange; 7597 }; 7598 7599 this.getBookmark = function(type) { 7600 var rng = selection.getRng(), start, end, bookmark = {}; 7601 7602 function getIndexes(node) { 7603 var parent, root, children, i, indexes = []; 7604 7605 parent = node.parentNode; 7606 root = dom.getRoot().parentNode; 7607 7608 while (parent != root && parent.nodeType !== 9) { 7609 children = parent.children; 7610 7611 i = children.length; 7612 while (i--) { 7613 if (node === children[i]) { 7614 indexes.push(i); 7615 break; 7616 } 7617 } 7618 7619 node = parent; 7620 parent = parent.parentNode; 7621 } 7622 7623 return indexes; 7624 }; 7625 7626 function getBookmarkEndPoint(start) { 7627 var position; 7628 7629 position = getPosition(rng, start); 7630 if (position) { 7631 return { 7632 position : position.position, 7633 offset : position.offset, 7634 indexes : getIndexes(position.node), 7635 inside : position.inside 7636 }; 7637 } 7638 }; 7639 7640 // Non ubstructive bookmark 7641 if (type === 2) { 7642 // Handle text selection 7643 if (!rng.item) { 7644 bookmark.start = getBookmarkEndPoint(true); 7645 7646 if (!selection.isCollapsed()) 7647 bookmark.end = getBookmarkEndPoint(); 7648 } else 7649 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7650 } 7651 7652 return bookmark; 7653 }; 7654 7655 this.moveToBookmark = function(bookmark) { 7656 var rng, body = dom.doc.body; 7657 7658 function resolveIndexes(indexes) { 7659 var node, i, idx, children; 7660 7661 node = dom.getRoot(); 7662 for (i = indexes.length - 1; i >= 0; i--) { 7663 children = node.children; 7664 idx = indexes[i]; 7665 7666 if (idx <= children.length - 1) { 7667 node = children[idx]; 7668 } 7669 } 7670 7671 return node; 7672 }; 7673 7674 function setBookmarkEndPoint(start) { 7675 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7676 7677 if (endPoint) { 7678 moveLeft = endPoint.position > 0; 7679 7680 moveRng = body.createTextRange(); 7681 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7682 7683 offset = endPoint.offset; 7684 if (offset !== undef) { 7685 moveRng.collapse(endPoint.inside || moveLeft); 7686 moveRng.moveStart('character', moveLeft ? -offset : offset); 7687 } else 7688 moveRng.collapse(start); 7689 7690 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7691 7692 if (start) 7693 rng.collapse(true); 7694 } 7695 }; 7696 7697 if (bookmark.start) { 7698 if (bookmark.start.ctrl) { 7699 rng = body.createControlRange(); 7700 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7701 rng.select(); 7702 } else { 7703 rng = body.createTextRange(); 7704 setBookmarkEndPoint(true); 7705 setBookmarkEndPoint(); 7706 rng.select(); 7707 } 7708 } 7709 }; 7710 7711 this.addRange = function(rng) { 7712 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 7713 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 7714 7715 function setEndPoint(start) { 7716 var container, offset, marker, tmpRng, nodes; 7717 7718 marker = dom.create('a'); 7719 container = start ? startContainer : endContainer; 7720 offset = start ? startOffset : endOffset; 7721 tmpRng = ieRng.duplicate(); 7722 7723 if (container == doc || container == doc.documentElement) { 7724 container = body; 7725 offset = 0; 7726 } 7727 7728 if (container.nodeType == 3) { 7729 container.parentNode.insertBefore(marker, container); 7730 tmpRng.moveToElementText(marker); 7731 tmpRng.moveStart('character', offset); 7732 dom.remove(marker); 7733 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7734 } else { 7735 nodes = container.childNodes; 7736 7737 if (nodes.length) { 7738 if (offset >= nodes.length) { 7739 dom.insertAfter(marker, nodes[nodes.length - 1]); 7740 } else { 7741 container.insertBefore(marker, nodes[offset]); 7742 } 7743 7744 tmpRng.moveToElementText(marker); 7745 } else if (container.canHaveHTML) { 7746 // Empty node selection for example <div>|</div> 7747 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7748 container.innerHTML = '<span>\uFEFF</span>'; 7749 marker = container.firstChild; 7750 tmpRng.moveToElementText(marker); 7751 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7752 } 7753 7754 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7755 dom.remove(marker); 7756 } 7757 } 7758 7759 // Setup some shorter versions 7760 startContainer = rng.startContainer; 7761 startOffset = rng.startOffset; 7762 endContainer = rng.endContainer; 7763 endOffset = rng.endOffset; 7764 ieRng = body.createTextRange(); 7765 7766 // If single element selection then try making a control selection out of it 7767 if (startContainer == endContainer && startContainer.nodeType == 1) { 7768 // Trick to place the caret inside an empty block element like <p></p> 7769 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7770 if (startContainer.canHaveHTML) {
7771 // Check if previous sibling is an empty block if it is then we need to render it 7772 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7773 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7774 sibling = startContainer.previousSibling; 7775 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7776 sibling.innerHTML = '\uFEFF'; 7777 } else { 7778 sibling = null; 7779 } 7780 7781 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7782 ieRng.moveToElementText(startContainer.lastChild); 7783 ieRng.select(); 7784 dom.doc.selection.clear(); 7785 startContainer.innerHTML = ''; 7786 7787 if (sibling) { 7788 sibling.innerHTML = ''; 7789 } 7790 return; 7791 } else { 7792 startOffset = dom.nodeIndex(startContainer); 7793 startContainer = startContainer.parentNode; 7794 } 7795 } 7796 7797 if (startOffset == endOffset - 1) { 7798 try { 7799 ctrlElm = startContainer.childNodes[startOffset]; 7800 ctrlRng = body.createControlRange(); 7801 ctrlRng.addElement(ctrlElm); 7802 ctrlRng.select(); 7803 7804 // Check if the range produced is on the correct element and is a control range 7805 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 7806 nativeRng = selection.getRng(); 7807 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 7808 return; 7809 } 7810 } catch (ex) { 7811 // Ignore 7812 } 7813 } 7814 } 7815 7816 // Set start/end point of selection 7817 setEndPoint(true); 7818 setEndPoint(); 7819 7820 // Select the new range and scroll it into view 7821 ieRng.select(); 7822 }; 7823 7824 // Expose range method 7825 this.getRangeAt = getRange; 7826 }; 7827 7828 // Expose the selection object 7829 tinymce.dom.TridentSelection = Selection; 7830})(); 7831 7832 7833/* 7834 * Sizzle CSS Selector Engine 7835 * Copyright, The Dojo Foundation 7836 * Released under the MIT, BSD, and GPL Licenses. 7837 * More information: http://sizzlejs.com/ 7838 */ 7839(function(){ 7840 7841var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7842 expando = "sizcache", 7843 done = 0, 7844 toString = Object.prototype.toString, 7845 hasDuplicate = false, 7846 baseHasDuplicate = true, 7847 rBackslash = /\\/g, 7848 rReturn = /\r\n/g, 7849 rNonWord = /\W/; 7850 7851// Here we check if the JavaScript engine is using some sort of 7852// optimization where it does not always call our comparision 7853// function. If that is the case, discard the hasDuplicate value. 7854// Thus far that includes Google Chrome. 7855[0, 0].sort(function() { 7856 baseHasDuplicate = false; 7857 return 0; 7858}); 7859 7860var Sizzle = function( selector, context, results, seed ) { 7861 results = results || []; 7862 context = context || document; 7863 7864 var origContext = context; 7865 7866 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7867 return []; 7868 } 7869 7870 if ( !selector || typeof selector !== "string" ) { 7871 return results; 7872 } 7873 7874 var m, set, checkSet, extra, ret, cur, pop, i, 7875 prune = true, 7876 contextXML = Sizzle.isXML( context ), 7877 parts = [], 7878 soFar = selector; 7879 7880 // Reset the position of the chunker regexp (start from head) 7881 do { 7882 chunker.exec( "" ); 7883 m = chunker.exec( soFar ); 7884 7885 if ( m ) { 7886 soFar = m[3]; 7887 7888 parts.push( m[1] ); 7889 7890 if ( m[2] ) { 7891 extra = m[3]; 7892 break; 7893 } 7894 } 7895 } while ( m ); 7896 7897 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7898 7899 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7900 set = posProcess( parts[0] + parts[1], context, seed ); 7901 7902 } else { 7903 set = Expr.relative[ parts[0] ] ? 7904 [ context ] : 7905 Sizzle( parts.shift(), context ); 7906 7907 while ( parts.length ) { 7908 selector = parts.shift(); 7909 7910 if ( Expr.relative[ selector ] ) { 7911 selector += parts.shift(); 7912 } 7913 7914 set = posProcess( selector, set, seed ); 7915 } 7916 } 7917 7918 } else { 7919 // Take a shortcut and set the context if the root selector is an ID 7920 // (but not if it'll be faster if the inner selector is an ID) 7921 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7922 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7923 7924 ret = Sizzle.find( parts.shift(), context, contextXML ); 7925 context = ret.expr ? 7926 Sizzle.filter( ret.expr, ret.set )[0] : 7927 ret.set[0]; 7928 } 7929 7930 if ( context ) { 7931 ret = seed ? 7932 { expr: parts.pop(), set: makeArray(seed) } : 7933 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7934 7935 set = ret.expr ? 7936 Sizzle.filter( ret.expr, ret.set ) : 7937 ret.set; 7938 7939 if ( parts.length > 0 ) { 7940 checkSet = makeArray( set ); 7941 7942 } else { 7943 prune = false;
7944 } 7945 7946 while ( parts.length ) { 7947 cur = parts.pop(); 7948 pop = cur; 7949 7950 if ( !Expr.relative[ cur ] ) { 7951 cur = ""; 7952 } else { 7953 pop = parts.pop(); 7954 } 7955 7956 if ( pop == null ) { 7957 pop = context; 7958 } 7959 7960 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7961 } 7962 7963 } else { 7964 checkSet = parts = []; 7965 } 7966 } 7967 7968 if ( !checkSet ) { 7969 checkSet = set; 7970 } 7971 7972 if ( !checkSet ) { 7973 Sizzle.error( cur || selector ); 7974 } 7975 7976 if ( toString.call(checkSet) === "[object Array]" ) { 7977 if ( !prune ) { 7978 results.push.apply( results, checkSet ); 7979 7980 } else if ( context && context.nodeType === 1 ) { 7981 for ( i = 0; checkSet[i] != null; i++ ) { 7982 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7983 results.push( set[i] ); 7984 } 7985 } 7986 7987 } else { 7988 for ( i = 0; checkSet[i] != null; i++ ) { 7989 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7990 results.push( set[i] ); 7991 } 7992 } 7993 } 7994 7995 } else { 7996 makeArray( checkSet, results ); 7997 } 7998 7999 if ( extra ) { 8000 Sizzle( extra, origContext, results, seed ); 8001 Sizzle.uniqueSort( results ); 8002 } 8003 8004 return results; 8005}; 8006 8007Sizzle.uniqueSort = function( results ) { 8008 if ( sortOrder ) { 8009 hasDuplicate = baseHasDuplicate; 8010 results.sort( sortOrder ); 8011 8012 if ( hasDuplicate ) { 8013 for ( var i = 1; i < results.length; i++ ) { 8014 if ( results[i] === results[ i - 1 ] ) { 8015 results.splice( i--, 1 ); 8016 } 8017 } 8018 } 8019 } 8020 8021 return results; 8022}; 8023 8024Sizzle.matches = function( expr, set ) { 8025 return Sizzle( expr, null, null, set ); 8026}; 8027 8028Sizzle.matchesSelector = function( node, expr ) { 8029 return Sizzle( expr, null, null, [node] ).length > 0; 8030}; 8031 8032Sizzle.find = function( expr, context, isXML ) { 8033 var set, i, len, match, type, left; 8034 8035 if ( !expr ) { 8036 return []; 8037 } 8038 8039 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 8040 type = Expr.order[i]; 8041 8042 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 8043 left = match[1]; 8044 match.splice( 1, 1 ); 8045 8046 if ( left.substr( left.length - 1 ) !== "\\" ) { 8047 match[1] = (match[1] || "").replace( rBackslash, "" ); 8048 set = Expr.find[ type ]( match, context, isXML ); 8049 8050 if ( set != null ) { 8051 expr = expr.replace( Expr.match[ type ], "" ); 8052 break; 8053 } 8054 } 8055 } 8056 } 8057 8058 if ( !set ) { 8059 set = typeof context.getElementsByTagName !== "undefined" ? 8060 context.getElementsByTagName( "*" ) : 8061 []; 8062 } 8063 8064 return { set: set, expr: expr }; 8065}; 8066 8067Sizzle.filter = function( expr, set, inplace, not ) { 8068 var match, anyFound, 8069 type, found, item, filter, left, 8070 i, pass, 8071 old = expr, 8072 result = [], 8073 curLoop = set, 8074 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 8075 8076 while ( expr && set.length ) { 8077 for ( type in Expr.filter ) { 8078 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 8079 filter = Expr.filter[ type ]; 8080 left = match[1]; 8081 8082 anyFound = false; 8083 8084 match.splice(1,1); 8085 8086 if ( left.substr( left.length - 1 ) === "\\" ) { 8087 continue; 8088 } 8089 8090 if ( curLoop === result ) { 8091 result = []; 8092 } 8093 8094 if ( Expr.preFilter[ type ] ) { 8095 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 8096 8097 if ( !match ) { 8098 anyFound = found = true; 8099 8100 } else if ( match === true ) { 8101 continue; 8102 } 8103 } 8104 8105 if ( match ) { 8106 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 8107 if ( item ) { 8108 found = filter( item, match, i, curLoop ); 8109 pass = not ^ found; 8110 8111 if ( inplace && found != null ) { 8112 if ( pass ) { 8113 anyFound = true; 8114 8115 } else { 8116 curLoop[i] = false; 8117 } 8118 8119 } else if ( pass ) { 8120 result.push( item ); 8121 anyFound = true; 8122 } 8123 } 8124 } 8125 } 8126 8127 if ( found !== undefined ) { 8128 if ( !inplace ) { 8129 curLoop = result; 8130 } 8131 8132 expr = expr.replace( Expr.match[ type ], "" ); 8133 8134 if ( !anyFound ) { 8135 return []; 8136 } 8137 8138 break; 8139 } 8140 } 8141 } 8142 8143 // Improper expression 8144 if ( expr === old ) { 8145 if ( anyFound == null ) { 8146 Sizzle.error( expr ); 8147 8148 } else { 8149 break; 8150 } 8151 } 8152 8153 old = expr; 8154 } 8155 8156 return curLoop; 8157}; 8158 8159Sizzle.error = function( msg ) { 8160 throw new Error( "Syntax error, unrecognized expression: " + msg ); 8161}; 8162 8163var getText = Sizzle.getText = function( elem ) { 8164 var i, node, 8165 nodeType = elem.nodeType, 8166 ret = ""; 8167 8168 if ( nodeType ) { 8169 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 8170 // Use textContent || innerText for elements 8171 if ( typeof elem.textContent === 'string' ) { 8172 return elem.textContent; 8173 } else if ( typeof elem.innerText === 'string' ) { 8174 // Replace IE's carriage returns 8175 return elem.innerText.replace( rReturn, '' ); 8176 } else { 8177 // Traverse it's children 8178 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 8179 ret += getText( elem ); 8180 } 8181 } 8182 } else if ( nodeType === 3 || nodeType === 4 ) { 8183 return elem.nodeValue; 8184 } 8185 } else { 8186 8187 // If no nodeType, this is expected to be an array 8188 for ( i = 0; (node = elem[i]); i++ ) {
8189 // Do not traverse comment nodes 8190 if ( node.nodeType !== 8 ) { 8191 ret += getText( node ); 8192 } 8193 } 8194 } 8195 return ret; 8196}; 8197 8198var Expr = Sizzle.selectors = { 8199 order: [ "ID", "NAME", "TAG" ], 8200 8201 match: { 8202 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8203 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 8204 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 8205 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 8206 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 8207 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 8208 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 8209 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 8210 }, 8211 8212 leftMatch: {}, 8213 8214 attrMap: { 8215 "class": "className", 8216 "for": "htmlFor" 8217 }, 8218 8219 attrHandle: { 8220 href: function( elem ) { 8221 return elem.getAttribute( "href" ); 8222 }, 8223 type: function( elem ) { 8224 return elem.getAttribute( "type" ); 8225 } 8226 }, 8227 8228 relative: { 8229 "+": function(checkSet, part){ 8230 var isPartStr = typeof part === "string", 8231 isTag = isPartStr && !rNonWord.test( part ), 8232 isPartStrNotTag = isPartStr && !isTag; 8233 8234 if ( isTag ) { 8235 part = part.toLowerCase(); 8236 } 8237 8238 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 8239 if ( (elem = checkSet[i]) ) { 8240 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 8241 8242 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 8243 elem || false : 8244 elem === part; 8245 } 8246 } 8247 8248 if ( isPartStrNotTag ) { 8249 Sizzle.filter( part, checkSet, true ); 8250 } 8251 }, 8252 8253 ">": function( checkSet, part ) { 8254 var elem, 8255 isPartStr = typeof part === "string", 8256 i = 0, 8257 l = checkSet.length; 8258 8259 if ( isPartStr && !rNonWord.test( part ) ) { 8260 part = part.toLowerCase(); 8261 8262 for ( ; i < l; i++ ) { 8263 elem = checkSet[i]; 8264 8265 if ( elem ) { 8266 var parent = elem.parentNode; 8267 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 8268 } 8269 } 8270 8271 } else { 8272 for ( ; i < l; i++ ) { 8273 elem = checkSet[i]; 8274 8275 if ( elem ) { 8276 checkSet[i] = isPartStr ? 8277 elem.parentNode : 8278 elem.parentNode === part; 8279 } 8280 } 8281 8282 if ( isPartStr ) { 8283 Sizzle.filter( part, checkSet, true ); 8284 } 8285 } 8286 }, 8287 8288 "": function(checkSet, part, isXML){ 8289 var nodeCheck, 8290 doneName = done++, 8291 checkFn = dirCheck; 8292 8293 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8294 part = part.toLowerCase(); 8295 nodeCheck = part; 8296 checkFn = dirNodeCheck; 8297 } 8298 8299 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 8300 }, 8301 8302 "~": function( checkSet, part, isXML ) { 8303 var nodeCheck, 8304 doneName = done++, 8305 checkFn = dirCheck; 8306 8307 if ( typeof part === "string" && !rNonWord.test( part ) ) { 8308 part = part.toLowerCase(); 8309 nodeCheck = part; 8310 checkFn = dirNodeCheck; 8311 } 8312 8313 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 8314 } 8315 }, 8316 8317 find: { 8318 ID: function( match, context, isXML ) { 8319 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8320 var m = context.getElementById(match[1]); 8321 // Check parentNode to catch when Blackberry 4.6 returns 8322 // nodes that are no longer in the document #6963 8323 return m && m.parentNode ? [m] : []; 8324 } 8325 }, 8326 8327 NAME: function( match, context ) { 8328 if ( typeof context.getElementsByName !== "undefined" ) { 8329 var ret = [], 8330 results = context.getElementsByName( match[1] ); 8331 8332 for ( var i = 0, l = results.length; i < l; i++ ) { 8333 if ( results[i].getAttribute("name") === match[1] ) { 8334 ret.push( results[i] ); 8335 } 8336 } 8337 8338 return ret.length === 0 ? null : ret; 8339 } 8340 }, 8341 8342 TAG: function( match, context ) { 8343 if ( typeof context.getElementsByTagName !== "undefined" ) { 8344 return context.getElementsByTagName( match[1] ); 8345 } 8346 } 8347 }, 8348 preFilter: { 8349 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8350 match = " " + match[1].replace( rBackslash, "" ) + " "; 8351 8352 if ( isXML ) { 8353 return match; 8354 } 8355 8356 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8357 if ( elem ) { 8358 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8359 if ( !inplace ) { 8360 result.push( elem ); 8361 } 8362 8363 } else if ( inplace ) { 8364 curLoop[i] = false;
8365 } 8366 } 8367 } 8368 8369 return false; 8370 }, 8371 8372 ID: function( match ) { 8373 return match[1].replace( rBackslash, "" ); 8374 }, 8375 8376 TAG: function( match, curLoop ) { 8377 return match[1].replace( rBackslash, "" ).toLowerCase(); 8378 }, 8379 8380 CHILD: function( match ) { 8381 if ( match[1] === "nth" ) { 8382 if ( !match[2] ) { 8383 Sizzle.error( match[0] ); 8384 } 8385 8386 match[2] = match[2].replace(/^\+|\s*/g, ''); 8387 8388 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8389 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8390 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8391 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8392 8393 // calculate the numbers (first)n+(last) including if they are negative 8394 match[2] = (test[1] + (test[2] || 1)) - 0; 8395 match[3] = test[3] - 0; 8396 } 8397 else if ( match[2] ) { 8398 Sizzle.error( match[0] ); 8399 } 8400 8401 // TODO: Move to normal caching system 8402 match[0] = done++; 8403 8404 return match; 8405 }, 8406 8407 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8408 var name = match[1] = match[1].replace( rBackslash, "" ); 8409 8410 if ( !isXML && Expr.attrMap[name] ) { 8411 match[1] = Expr.attrMap[name]; 8412 } 8413 8414 // Handle if an un-quoted value was used 8415 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8416 8417 if ( match[2] === "~=" ) { 8418 match[4] = " " + match[4] + " "; 8419 } 8420 8421 return match; 8422 }, 8423 8424 PSEUDO: function( match, curLoop, inplace, result, not ) { 8425 if ( match[1] === "not" ) { 8426 // If we're dealing with a complex expression, or a simple one 8427 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8428 match[3] = Sizzle(match[3], null, null, curLoop); 8429 8430 } else { 8431 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8432 8433 if ( !inplace ) { 8434 result.push.apply( result, ret ); 8435 } 8436 8437 return false; 8438 } 8439 8440 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8441 return true; 8442 } 8443 8444 return match; 8445 }, 8446 8447 POS: function( match ) { 8448 match.unshift( true ); 8449 8450 return match; 8451 } 8452 }, 8453 8454 filters: { 8455 enabled: function( elem ) { 8456 return elem.disabled === false && elem.type !== "hidden"; 8457 }, 8458 8459 disabled: function( elem ) { 8460 return elem.disabled === true; 8461 }, 8462 8463 checked: function( elem ) { 8464 return elem.checked === true; 8465 }, 8466 8467 selected: function( elem ) { 8468 // Accessing this property makes selected-by-default 8469 // options in Safari work properly 8470 if ( elem.parentNode ) { 8471 elem.parentNode.selectedIndex; 8472 } 8473 8474 return elem.selected === true; 8475 }, 8476 8477 parent: function( elem ) { 8478 return !!elem.firstChild; 8479 }, 8480 8481 empty: function( elem ) { 8482 return !elem.firstChild; 8483 }, 8484 8485 has: function( elem, i, match ) { 8486 return !!Sizzle( match[3], elem ).length; 8487 }, 8488 8489 header: function( elem ) { 8490 return (/h\d/i).test( elem.nodeName ); 8491 }, 8492 8493 text: function( elem ) { 8494 var attr = elem.getAttribute( "type" ), type = elem.type; 8495 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8496 // use getAttribute instead to test this case 8497 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8498 }, 8499 8500 radio: function( elem ) { 8501 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8502 }, 8503 8504 checkbox: function( elem ) { 8505 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8506 }, 8507 8508 file: function( elem ) { 8509 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8510 }, 8511 8512 password: function( elem ) { 8513 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8514 }, 8515 8516 submit: function( elem ) { 8517 var name = elem.nodeName.toLowerCase(); 8518 return (name === "input" || name === "button") && "submit" === elem.type; 8519 }, 8520 8521 image: function( elem ) { 8522 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8523 }, 8524 8525 reset: function( elem ) { 8526 var name = elem.nodeName.toLowerCase(); 8527 return (name === "input" || name === "button") && "reset" === elem.type; 8528 }, 8529 8530 button: function( elem ) { 8531 var name = elem.nodeName.toLowerCase(); 8532 return name === "input" && "button" === elem.type || name === "button"; 8533 }, 8534 8535 input: function( elem ) { 8536 return (/input|select|textarea|button/i).test( elem.nodeName ); 8537 }, 8538 8539 focus: function( elem ) { 8540 return elem === elem.ownerDocument.activeElement; 8541 } 8542 }, 8543 setFilters: { 8544 first: function( elem, i ) { 8545 return i === 0; 8546 }, 8547 8548 last: function( elem, i, match, array ) { 8549 return i === array.length - 1; 8550 }, 8551 8552 even: function( elem, i ) { 8553 return i % 2 === 0; 8554 }, 8555 8556 odd: function( elem, i ) { 8557 return i % 2 === 1; 8558 }, 8559 8560 lt: function( elem, i, match ) { 8561 return i < match[3] - 0; 8562 }, 8563 8564 gt: function( elem, i, match ) { 8565 return i > match[3] - 0; 8566 }, 8567 8568 nth: function( elem, i, match ) { 8569 return match[3] - 0 === i; 8570 }, 8571 8572 eq: function( elem, i, match ) { 8573 return match[3] - 0 === i; 8574 } 8575 }, 8576 filter: { 8577 PSEUDO: function( elem, match, i, array ) { 8578 var name = match[1], 8579 filter = Expr.filters[ name ]; 8580 8581 if ( filter ) { 8582 return filter( elem, i, match, array ); 8583 8584 } else if ( name === "contains" ) { 8585 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8586 8587 } else if ( name === "not" ) { 8588 var not = match[3]; 8589 8590 for ( var j = 0, l = not.length; j < l; j++ ) { 8591 if ( not[j] === elem ) { 8592 return false;
8593 } 8594 } 8595 8596 return true; 8597 8598 } else { 8599 Sizzle.error( name ); 8600 } 8601 }, 8602 8603 CHILD: function( elem, match ) { 8604 var first, last, 8605 doneName, parent, cache, 8606 count, diff, 8607 type = match[1], 8608 node = elem; 8609 8610 switch ( type ) { 8611 case "only": 8612 case "first": 8613 while ( (node = node.previousSibling) ) { 8614 if ( node.nodeType === 1 ) { 8615 return false; 8616 } 8617 } 8618 8619 if ( type === "first" ) { 8620 return true; 8621 } 8622 8623 node = elem; 8624 8625 /* falls through */ 8626 case "last": 8627 while ( (node = node.nextSibling) ) { 8628 if ( node.nodeType === 1 ) { 8629 return false; 8630 } 8631 } 8632 8633 return true; 8634 8635 case "nth": 8636 first = match[2]; 8637 last = match[3]; 8638 8639 if ( first === 1 && last === 0 ) { 8640 return true; 8641 } 8642 8643 doneName = match[0]; 8644 parent = elem.parentNode; 8645 8646 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8647 count = 0; 8648 8649 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8650 if ( node.nodeType === 1 ) { 8651 node.nodeIndex = ++count; 8652 } 8653 } 8654 8655 parent[ expando ] = doneName; 8656 } 8657 8658 diff = elem.nodeIndex - last; 8659 8660 if ( first === 0 ) { 8661 return diff === 0; 8662 8663 } else { 8664 return ( diff % first === 0 && diff / first >= 0 ); 8665 } 8666 } 8667 }, 8668 8669 ID: function( elem, match ) { 8670 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8671 }, 8672 8673 TAG: function( elem, match ) { 8674 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8675 }, 8676 8677 CLASS: function( elem, match ) { 8678 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8679 .indexOf( match ) > -1; 8680 }, 8681 8682 ATTR: function( elem, match ) { 8683 var name = match[1], 8684 result = Sizzle.attr ? 8685 Sizzle.attr( elem, name ) : 8686 Expr.attrHandle[ name ] ? 8687 Expr.attrHandle[ name ]( elem ) : 8688 elem[ name ] != null ? 8689 elem[ name ] : 8690 elem.getAttribute( name ), 8691 value = result + "", 8692 type = match[2], 8693 check = match[4]; 8694 8695 return result == null ? 8696 type === "!=" : 8697 !type && Sizzle.attr ? 8698 result != null : 8699 type === "=" ? 8700 value === check : 8701 type === "*=" ? 8702 value.indexOf(check) >= 0 : 8703 type === "~=" ? 8704 (" " + value + " ").indexOf(check) >= 0 : 8705 !check ? 8706 value && result !== false : 8707 type === "!=" ? 8708 value !== check : 8709 type === "^=" ? 8710 value.indexOf(check) === 0 : 8711 type === "$=" ? 8712 value.substr(value.length - check.length) === check : 8713 type === "|=" ? 8714 value === check || value.substr(0, check.length + 1) === check + "-" : 8715 false; 8716 }, 8717 8718 POS: function( elem, match, i, array ) { 8719 var name = match[2], 8720 filter = Expr.setFilters[ name ]; 8721 8722 if ( filter ) { 8723 return filter( elem, i, match, array ); 8724 } 8725 } 8726 } 8727}; 8728 8729var origPOS = Expr.match.POS, 8730 fescape = function(all, num){ 8731 return "\\" + (num - 0 + 1); 8732 }; 8733 8734for ( var type in Expr.match ) { 8735 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8736 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8737} 8738// Expose origPOS 8739// "global" as in regardless of relation to brackets/parens 8740Expr.match.globalPOS = origPOS; 8741 8742var makeArray = function( array, results ) { 8743 array = Array.prototype.slice.call( array, 0 ); 8744 8745 if ( results ) { 8746 results.push.apply( results, array ); 8747 return results; 8748 } 8749 8750 return array; 8751}; 8752 8753// Perform a simple check to determine if the browser is capable of 8754// converting a NodeList to an array using builtin methods. 8755// Also verifies that the returned array holds DOM nodes 8756// (which is not the case in the Blackberry browser) 8757try { 8758 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8759 8760// Provide a fallback method if it does not work 8761} catch( e ) { 8762 makeArray = function( array, results ) { 8763 var i = 0, 8764 ret = results || []; 8765 8766 if ( toString.call(array) === "[object Array]" ) { 8767 Array.prototype.push.apply( ret, array ); 8768 8769 } else { 8770 if ( typeof array.length === "number" ) { 8771 for ( var l = array.length; i < l; i++ ) {
8772 ret.push( array[i] ); 8773 } 8774 8775 } else { 8776 for ( ; array[i]; i++ ) { 8777 ret.push( array[i] ); 8778 } 8779 } 8780 } 8781 8782 return ret; 8783 }; 8784} 8785 8786var sortOrder, siblingCheck; 8787 8788if ( document.documentElement.compareDocumentPosition ) { 8789 sortOrder = function( a, b ) { 8790 if ( a === b ) { 8791 hasDuplicate = true; 8792 return 0; 8793 } 8794 8795 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8796 return a.compareDocumentPosition ? -1 : 1; 8797 } 8798 8799 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8800 }; 8801 8802} else { 8803 sortOrder = function( a, b ) { 8804 // The nodes are identical, we can exit early 8805 if ( a === b ) { 8806 hasDuplicate = true; 8807 return 0; 8808 8809 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8810 } else if ( a.sourceIndex && b.sourceIndex ) { 8811 return a.sourceIndex - b.sourceIndex; 8812 } 8813 8814 var al, bl, 8815 ap = [], 8816 bp = [], 8817 aup = a.parentNode, 8818 bup = b.parentNode, 8819 cur = aup; 8820 8821 // If the nodes are siblings (or identical) we can do a quick check 8822 if ( aup === bup ) { 8823 return siblingCheck( a, b ); 8824 8825 // If no parents were found then the nodes are disconnected 8826 } else if ( !aup ) { 8827 return -1; 8828 8829 } else if ( !bup ) { 8830 return 1; 8831 } 8832 8833 // Otherwise they're somewhere else in the tree so we need 8834 // to build up a full list of the parentNodes for comparison 8835 while ( cur ) { 8836 ap.unshift( cur ); 8837 cur = cur.parentNode; 8838 } 8839 8840 cur = bup; 8841 8842 while ( cur ) { 8843 bp.unshift( cur ); 8844 cur = cur.parentNode; 8845 } 8846 8847 al = ap.length; 8848 bl = bp.length; 8849 8850 // Start walking down the tree looking for a discrepancy 8851 for ( var i = 0; i < al && i < bl; i++ ) { 8852 if ( ap[i] !== bp[i] ) { 8853 return siblingCheck( ap[i], bp[i] ); 8854 } 8855 } 8856 8857 // We ended someplace up the tree so do a sibling check 8858 return i === al ? 8859 siblingCheck( a, bp[i], -1 ) : 8860 siblingCheck( ap[i], b, 1 ); 8861 }; 8862 8863 siblingCheck = function( a, b, ret ) { 8864 if ( a === b ) { 8865 return ret; 8866 } 8867 8868 var cur = a.nextSibling; 8869 8870 while ( cur ) { 8871 if ( cur === b ) { 8872 return -1; 8873 } 8874 8875 cur = cur.nextSibling; 8876 } 8877 8878 return 1; 8879 }; 8880} 8881 8882// Check to see if the browser returns elements by name when 8883// querying by getElementById (and provide a workaround) 8884(function(){ 8885 // We're going to inject a fake input element with a specified name 8886 var form = document.createElement("div"), 8887 id = "script" + (new Date()).getTime(), 8888 root = document.documentElement; 8889 8890 form.innerHTML = "<a name='" + id + "'/>"; 8891 8892 // Inject it into the root element, check its status, and remove it quickly 8893 root.insertBefore( form, root.firstChild ); 8894 8895 // The workaround has to do additional checks after a getElementById 8896 // Which slows things down for other browsers (hence the branching) 8897 if ( document.getElementById( id ) ) { 8898 Expr.find.ID = function( match, context, isXML ) { 8899 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8900 var m = context.getElementById(match[1]); 8901 8902 return m ? 8903 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8904 [m] : 8905 undefined : 8906 []; 8907 } 8908 }; 8909 8910 Expr.filter.ID = function( elem, match ) { 8911 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8912 8913 return elem.nodeType === 1 && node && node.nodeValue === match; 8914 }; 8915 } 8916 8917 root.removeChild( form ); 8918 8919 // release memory in IE 8920 root = form = null; 8921})(); 8922 8923(function(){ 8924 // Check to see if the browser returns only elements 8925 // when doing getElementsByTagName("*") 8926 8927 // Create a fake element 8928 var div = document.createElement("div"); 8929 div.appendChild( document.createComment("") ); 8930 8931 // Make sure no comments are found 8932 if ( div.getElementsByTagName("*").length > 0 ) { 8933 Expr.find.TAG = function( match, context ) { 8934 var results = context.getElementsByTagName( match[1] ); 8935 8936 // Filter out possible comments 8937 if ( match[1] === "*" ) { 8938 var tmp = []; 8939 8940 for ( var i = 0; results[i]; i++ ) { 8941 if ( results[i].nodeType === 1 ) { 8942 tmp.push( results[i] ); 8943 } 8944 } 8945 8946 results = tmp; 8947 } 8948 8949 return results; 8950 }; 8951 } 8952 8953 // Check to see if an attribute returns normalized href attributes 8954 div.innerHTML = "<a href='#'></a>"; 8955 8956 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8957 div.firstChild.getAttribute("href") !== "#" ) { 8958 8959 Expr.attrHandle.href = function( elem ) { 8960 return elem.getAttribute( "href", 2 ); 8961 }; 8962 } 8963 8964 // release memory in IE 8965 div = null; 8966})(); 8967 8968if ( document.querySelectorAll ) { 8969 (function(){
8970 var oldSizzle = Sizzle, 8971 div = document.createElement("div"), 8972 id = "__sizzle__"; 8973 8974 div.innerHTML = "<p class='TEST'></p>"; 8975 8976 // Safari can't handle uppercase or unicode characters when 8977 // in quirks mode. 8978 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8979 return; 8980 } 8981 8982 Sizzle = function( query, context, extra, seed ) { 8983 context = context || document; 8984 8985 // Only use querySelectorAll on non-XML documents 8986 // (ID selectors don't work in non-HTML documents) 8987 if ( !seed && !Sizzle.isXML(context) ) { 8988 // See if we find a selector to speed up 8989 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8990 8991 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8992 // Speed-up: Sizzle("TAG") 8993 if ( match[1] ) { 8994 return makeArray( context.getElementsByTagName( query ), extra ); 8995 8996 // Speed-up: Sizzle(".CLASS") 8997 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8998 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8999 } 9000 } 9001 9002 if ( context.nodeType === 9 ) { 9003 // Speed-up: Sizzle("body") 9004 // The body element only exists once, optimize finding it 9005 if ( query === "body" && context.body ) { 9006 return makeArray( [ context.body ], extra ); 9007 9008 // Speed-up: Sizzle("#ID") 9009 } else if ( match && match[3] ) { 9010 var elem = context.getElementById( match[3] ); 9011 9012 // Check parentNode to catch when Blackberry 4.6 returns 9013 // nodes that are no longer in the document #6963 9014 if ( elem && elem.parentNode ) { 9015 // Handle the case where IE and Opera return items 9016 // by name instead of ID 9017 if ( elem.id === match[3] ) { 9018 return makeArray( [ elem ], extra ); 9019 } 9020 9021 } else { 9022 return makeArray( [], extra ); 9023 } 9024 } 9025 9026 try { 9027 return makeArray( context.querySelectorAll(query), extra ); 9028 } catch(qsaError) {} 9029 9030 // qSA works strangely on Element-rooted queries 9031 // We can work around this by specifying an extra ID on the root 9032 // and working up from there (Thanks to Andrew Dupont for the technique) 9033 // IE 8 doesn't work on object elements 9034 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 9035 var oldContext = context, 9036 old = context.getAttribute( "id" ), 9037 nid = old || id, 9038 hasParent = context.parentNode, 9039 relativeHierarchySelector = /^\s*[+~]/.test( query ); 9040 9041 if ( !old ) { 9042 context.setAttribute( "id", nid ); 9043 } else { 9044 nid = nid.replace( /'/g, "\\$&" ); 9045 } 9046 if ( relativeHierarchySelector && hasParent ) { 9047 context = context.parentNode; 9048 } 9049 9050 try { 9051 if ( !relativeHierarchySelector || hasParent ) { 9052 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 9053 } 9054 9055 } catch(pseudoError) { 9056 } finally { 9057 if ( !old ) { 9058 oldContext.removeAttribute( "id" ); 9059 } 9060 } 9061 } 9062 } 9063 9064 return oldSizzle(query, context, extra, seed); 9065 }; 9066 9067 for ( var prop in oldSizzle ) { 9068 Sizzle[ prop ] = oldSizzle[ prop ]; 9069 } 9070 9071 // release memory in IE 9072 div = null; 9073 })(); 9074} 9075 9076(function(){ 9077 var html = document.documentElement, 9078 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 9079 9080 if ( matches ) { 9081 // Check to see if it's possible to do matchesSelector 9082 // on a disconnected node (IE 9 fails this) 9083 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 9084 pseudoWorks = false; 9085 9086 try { 9087 // This should fail with an exception 9088 // Gecko does not error, returns false instead
9089 matches.call( document.documentElement, "[test!='']:sizzle" ); 9090 9091 } catch( pseudoError ) { 9092 pseudoWorks = true; 9093 } 9094 9095 Sizzle.matchesSelector = function( node, expr ) { 9096 // Make sure that attribute selectors are quoted 9097 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 9098 9099 if ( !Sizzle.isXML( node ) ) { 9100 try { 9101 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 9102 var ret = matches.call( node, expr ); 9103 9104 // IE 9's matchesSelector returns false on disconnected nodes 9105 if ( ret || !disconnectedMatch || 9106 // As well, disconnected nodes are said to be in a document 9107 // fragment in IE 9, so check for that 9108 node.document && node.document.nodeType !== 11 ) { 9109 return ret; 9110 } 9111 } 9112 } catch(e) {} 9113 } 9114 9115 return Sizzle(expr, null, null, [node]).length > 0; 9116 }; 9117 } 9118})(); 9119 9120(function(){ 9121 var div = document.createElement("div"); 9122 9123 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 9124 9125 // Opera can't find a second classname (in 9.6) 9126 // Also, make sure that getElementsByClassName actually exists 9127 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 9128 return; 9129 } 9130 9131 // Safari caches class attributes, doesn't catch changes (in 3.2) 9132 div.lastChild.className = "e"; 9133 9134 if ( div.getElementsByClassName("e").length === 1 ) { 9135 return; 9136 } 9137 9138 Expr.order.splice(1, 0, "CLASS"); 9139 Expr.find.CLASS = function( match, context, isXML ) { 9140 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 9141 return context.getElementsByClassName(match[1]); 9142 } 9143 }; 9144 9145 // release memory in IE 9146 div = null; 9147})(); 9148 9149function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9150 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9151 var elem = checkSet[i]; 9152 9153 if ( elem ) { 9154 var match = false; 9155 9156 elem = elem[dir]; 9157 9158 while ( elem ) { 9159 if ( elem[ expando ] === doneName ) { 9160 match = checkSet[elem.sizset]; 9161 break; 9162 } 9163 9164 if ( elem.nodeType === 1 && !isXML ){ 9165 elem[ expando ] = doneName; 9166 elem.sizset = i; 9167 } 9168 9169 if ( elem.nodeName.toLowerCase() === cur ) { 9170 match = elem; 9171 break; 9172 } 9173 9174 elem = elem[dir]; 9175 } 9176 9177 checkSet[i] = match; 9178 } 9179 } 9180} 9181 9182function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 9183 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 9184 var elem = checkSet[i]; 9185 9186 if ( elem ) { 9187 var match = false; 9188 9189 elem = elem[dir]; 9190 9191 while ( elem ) { 9192 if ( elem[ expando ] === doneName ) { 9193 match = checkSet[elem.sizset]; 9194 break; 9195 } 9196 9197 if ( elem.nodeType === 1 ) { 9198 if ( !isXML ) { 9199 elem[ expando ] = doneName; 9200 elem.sizset = i; 9201 } 9202 9203 if ( typeof cur !== "string" ) { 9204 if ( elem === cur ) { 9205 match = true; 9206 break; 9207 } 9208 9209 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 9210 match = elem; 9211 break; 9212 } 9213 } 9214 9215 elem = elem[dir]; 9216 } 9217 9218 checkSet[i] = match; 9219 } 9220 } 9221} 9222 9223if ( document.documentElement.contains ) { 9224 Sizzle.contains = function( a, b ) { 9225 return a !== b && (a.contains ? a.contains(b) : true); 9226 }; 9227 9228} else if ( document.documentElement.compareDocumentPosition ) { 9229 Sizzle.contains = function( a, b ) { 9230 return !!(a.compareDocumentPosition(b) & 16); 9231 }; 9232 9233} else { 9234 Sizzle.contains = function() { 9235 return false; 9236 }; 9237} 9238 9239Sizzle.isXML = function( elem ) { 9240 // documentElement is verified for cases where it doesn't yet exist 9241 // (such as loading iframes in IE - #4833) 9242 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 9243 9244 return documentElement ? documentElement.nodeName !== "HTML" : false; 9245}; 9246 9247var posProcess = function( selector, context, seed ) { 9248 var match, 9249 tmpSet = [], 9250 later = "", 9251 root = context.nodeType ? [context] : context; 9252 9253 // Position selectors must be done after the filter 9254 // And so must :not(positional) so we move all PSEUDOs to the end 9255 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 9256 later += match[0]; 9257 selector = selector.replace( Expr.match.PSEUDO, "" ); 9258 } 9259 9260 selector = Expr.relative[selector] ? selector + "*" : selector; 9261 9262 for ( var i = 0, l = root.length; i < l; i++ ) { 9263 Sizzle( selector, root[i], tmpSet, seed ); 9264 } 9265 9266 return Sizzle.filter( later, tmpSet ); 9267}; 9268 9269// EXPOSE 9270
9271window.tinymce.dom.Sizzle = Sizzle; 9272 9273})(); 9274 9275 9276(function(tinymce) { 9277 tinymce.dom.Element = function(id, settings) { 9278 var t = this, dom, el; 9279 9280 t.settings = settings = settings || {}; 9281 t.id = id; 9282 t.dom = dom = settings.dom || tinymce.DOM; 9283 9284 // Only IE leaks DOM references, this is a lot faster 9285 if (!tinymce.isIE) 9286 el = dom.get(t.id); 9287 9288 tinymce.each( 9289 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 9290 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 9291 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 9292 'isHidden,setHTML,get').split(/,/), function(k) { 9293 t[k] = function() { 9294 var a = [id], i; 9295 9296 for (i = 0; i < arguments.length; i++) 9297 a.push(arguments[i]); 9298 9299 a = dom[k].apply(dom, a); 9300 t.update(k); 9301 9302 return a; 9303 }; 9304 } 9305 ); 9306 9307 tinymce.extend(t, { 9308 on : function(n, f, s) { 9309 return tinymce.dom.Event.add(t.id, n, f, s); 9310 }, 9311 9312 getXY : function() { 9313 return { 9314 x : parseInt(t.getStyle('left')), 9315 y : parseInt(t.getStyle('top')) 9316 }; 9317 }, 9318 9319 getSize : function() { 9320 var n = dom.get(t.id); 9321 9322 return { 9323 w : parseInt(t.getStyle('width') || n.clientWidth), 9324 h : parseInt(t.getStyle('height') || n.clientHeight) 9325 }; 9326 }, 9327 9328 moveTo : function(x, y) { 9329 t.setStyles({left : x, top : y}); 9330 }, 9331 9332 moveBy : function(x, y) { 9333 var p = t.getXY(); 9334 9335 t.moveTo(p.x + x, p.y + y); 9336 }, 9337 9338 resizeTo : function(w, h) { 9339 t.setStyles({width : w, height : h}); 9340 }, 9341 9342 resizeBy : function(w, h) { 9343 var s = t.getSize(); 9344 9345 t.resizeTo(s.w + w, s.h + h); 9346 }, 9347 9348 update : function(k) { 9349 var b; 9350 9351 if (tinymce.isIE6 && settings.blocker) { 9352 k = k || ''; 9353 9354 // Ignore getters 9355 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9356 return; 9357 9358 // Remove blocker on remove 9359 if (k == 'remove') { 9360 dom.remove(t.blocker); 9361 return; 9362 } 9363 9364 if (!t.blocker) { 9365 t.blocker = dom.uniqueId(); 9366 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9367 dom.setStyle(b, 'opacity', 0); 9368 } else 9369 b = dom.get(t.blocker); 9370 9371 dom.setStyles(b, { 9372 left : t.getStyle('left', 1), 9373 top : t.getStyle('top', 1), 9374 width : t.getStyle('width', 1), 9375 height : t.getStyle('height', 1), 9376 display : t.getStyle('display', 1), 9377 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9378 }); 9379 } 9380 } 9381 }); 9382 }; 9383})(tinymce); 9384 9385(function(tinymce) { 9386 function trimNl(s) { 9387 return s.replace(/[\n\r]+/g, ''); 9388 }; 9389 9390 // Shorten names 9391 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9392 9393 tinymce.create('tinymce.dom.Selection', { 9394 Selection : function(dom, win, serializer, editor) { 9395 var t = this; 9396 9397 t.dom = dom; 9398 t.win = win; 9399 t.serializer = serializer; 9400 t.editor = editor; 9401 9402 // Add events 9403 each([ 9404 'onBeforeSetContent', 9405 9406 'onBeforeGetContent', 9407 9408 'onSetContent', 9409 9410 'onGetContent' 9411 ], function(e) { 9412 t[e] = new tinymce.util.Dispatcher(t); 9413 }); 9414 9415 // No W3C Range support 9416 if (!t.win.getSelection) 9417 t.tridentSel = new tinymce.dom.TridentSelection(t); 9418 9419 if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel) 9420 this._fixIESelection(); 9421 9422 // Prevent leaks 9423 tinymce.addUnload(t.destroy, t); 9424 }, 9425 9426 setCursorLocation: function(node, offset) { 9427 var t = this; var r = t.dom.createRng(); 9428 r.setStart(node, offset); 9429 r.setEnd(node, offset); 9430 t.setRng(r); 9431 t.collapse(false); 9432 }, 9433 getContent : function(s) { 9434 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9435 9436 s = s || {}; 9437 wb = wa = ''; 9438 s.get = true; 9439 s.format = s.format || 'html'; 9440 s.forced_root_block = ''; 9441 t.onBeforeGetContent.dispatch(t, s); 9442 9443 if (s.format == 'text') 9444 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9445 9446 if (r.cloneContents) { 9447 n = r.cloneContents(); 9448 9449 if (n) 9450 e.appendChild(n); 9451 } else if (is(r.item) || is(r.htmlText)) { 9452 // IE will produce invalid markup if elements are present that 9453 // it doesn't understand like custom elements or HTML5 elements. 9454 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9455 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9456 e.removeChild(e.firstChild); 9457 } else 9458 e.innerHTML = r.toString(); 9459 9460 // Keep whitespace before and after 9461 if (/^\s/.test(e.innerHTML)) 9462 wb = ' '; 9463 9464 if (/\s+$/.test(e.innerHTML)) 9465 wa = ' '; 9466 9467 s.getInner = true; 9468 9469 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9470 t.onGetContent.dispatch(t, s); 9471 9472 return s.content; 9473 }, 9474 9475 setContent : function(content, args) {
9476 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9477 9478 args = args || {format : 'html'}; 9479 args.set = true; 9480 content = args.content = content; 9481 9482 // Dispatch before set content event 9483 if (!args.no_events) 9484 self.onBeforeSetContent.dispatch(self, args); 9485 9486 content = args.content; 9487 9488 if (rng.insertNode) { 9489 // Make caret marker since insertNode places the caret in the beginning of text after insert 9490 content += '<span id="__caret">_</span>'; 9491 9492 // Delete and insert new node 9493 if (rng.startContainer == doc && rng.endContainer == doc) { 9494 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9495 doc.body.innerHTML = content; 9496 } else { 9497 rng.deleteContents(); 9498 9499 if (doc.body.childNodes.length === 0) { 9500 doc.body.innerHTML = content; 9501 } else { 9502 // createContextualFragment doesn't exists in IE 9 DOMRanges 9503 if (rng.createContextualFragment) { 9504 rng.insertNode(rng.createContextualFragment(content)); 9505 } else { 9506 // Fake createContextualFragment call in IE 9 9507 frag = doc.createDocumentFragment(); 9508 temp = doc.createElement('div'); 9509 9510 frag.appendChild(temp); 9511 temp.outerHTML = content; 9512 9513 rng.insertNode(frag); 9514 } 9515 } 9516 } 9517 9518 // Move to caret marker
9519 caretNode = self.dom.get('__caret'); 9520 9521 // Make sure we wrap it compleatly, Opera fails with a simple select call 9522 rng = doc.createRange(); 9523 rng.setStartBefore(caretNode); 9524 rng.setEndBefore(caretNode); 9525 self.setRng(rng); 9526 9527 // Remove the caret position 9528 self.dom.remove('__caret'); 9529 9530 try { 9531 self.setRng(rng); 9532 } catch (ex) { 9533 // Might fail on Opera for some odd reason 9534 } 9535 } else { 9536 if (rng.item) { 9537 // Delete content and get caret text selection 9538 doc.execCommand('Delete', false, null); 9539 rng = self.getRng(); 9540 } 9541 9542 // Explorer removes spaces from the beginning of pasted contents 9543 if (/^\s+/.test(content)) { 9544 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9545 self.dom.remove('__mce_tmp'); 9546 } else 9547 rng.pasteHTML(content); 9548 } 9549 9550 // Dispatch set content event 9551 if (!args.no_events) 9552 self.onSetContent.dispatch(self, args); 9553 }, 9554 9555 getStart : function() { 9556 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9557 9558 if (rng.duplicate || rng.item) { 9559 // Control selection, return first item 9560 if (rng.item) 9561 return rng.item(0); 9562 9563 // Get start element 9564 checkRng = rng.duplicate(); 9565 checkRng.collapse(1); 9566 startElement = checkRng.parentElement(); 9567 if (startElement.ownerDocument !== self.dom.doc) { 9568 startElement = self.dom.getRoot(); 9569 } 9570 9571 // Check if range parent is inside the start element, then return the inner parent element 9572 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9573 parentElement = node = rng.parentElement(); 9574 while (node = node.parentNode) { 9575 if (node == startElement) { 9576 startElement = parentElement; 9577 break; 9578 } 9579 } 9580 9581 return startElement; 9582 } else { 9583 startElement = rng.startContainer; 9584 9585 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9586 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9587 9588 if (startElement && startElement.nodeType == 3) 9589 return startElement.parentNode; 9590 9591 return startElement; 9592 } 9593 }, 9594 9595 getEnd : function() { 9596 var self = this, rng = self.getRng(), endElement, endOffset; 9597 9598 if (rng.duplicate || rng.item) { 9599 if (rng.item) 9600 return rng.item(0); 9601 9602 rng = rng.duplicate(); 9603 rng.collapse(0); 9604 endElement = rng.parentElement(); 9605 if (endElement.ownerDocument !== self.dom.doc) { 9606 endElement = self.dom.getRoot(); 9607 } 9608 9609 if (endElement && endElement.nodeName == 'BODY') 9610 return endElement.lastChild || endElement; 9611 9612 return endElement; 9613 } else { 9614 endElement = rng.endContainer; 9615 endOffset = rng.endOffset; 9616 9617 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9618 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9619 9620 if (endElement && endElement.nodeType == 3) 9621 return endElement.parentNode; 9622 9623 return endElement; 9624 } 9625 }, 9626 9627 getBookmark : function(type, normalized) { 9628 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9629 9630 function findIndex(name, element) { 9631 var index = 0; 9632 9633 each(dom.select(name), function(node, i) { 9634 if (node == element) 9635 index = i; 9636 }); 9637 9638 return index; 9639 }; 9640 9641 function normalizeTableCellSelection(rng) { 9642 function moveEndPoint(start) { 9643 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9644 9645 container = rng[prefix + 'Container']; 9646 offset = rng[prefix + 'Offset']; 9647 9648 if (container.nodeType == 1 && container.nodeName == "TR") { 9649 childNodes = container.childNodes; 9650 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9651 if (container) { 9652 offset = start ? 0 : container.childNodes.length; 9653 rng['set' + (start ? 'Start' : 'End')](container, offset); 9654 } 9655 } 9656 }; 9657 9658 moveEndPoint(true); 9659 moveEndPoint(); 9660 9661 return rng; 9662 }; 9663 9664 function getLocation() { 9665 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9666 9667 function getPoint(rng, start) { 9668 var container = rng[start ? 'startContainer' : 'endContainer'], 9669 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9670 9671 if (container.nodeType == 3) { 9672 if (normalized) { 9673 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9674 offset += node.nodeValue.length; 9675 } 9676 9677 point.push(offset); 9678 } else { 9679 childNodes = container.childNodes; 9680 9681 if (offset >= childNodes.length && childNodes.length) { 9682 after = 1; 9683 offset = Math.max(0, childNodes.length - 1); 9684 } 9685 9686 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9687 } 9688 9689 for (; container && container != root; container = container.parentNode) 9690 point.push(t.dom.nodeIndex(container, normalized)); 9691 9692 return point; 9693 }; 9694 9695 bookmark.start = getPoint(rng, true); 9696 9697 if (!t.isCollapsed()) 9698 bookmark.end = getPoint(rng); 9699 9700 return bookmark; 9701 }; 9702 9703 if (type == 2) { 9704 if (t.tridentSel) 9705 return t.tridentSel.getBookmark(type); 9706 9707 return getLocation(); 9708 } 9709 9710 // Handle simple range 9711 if (type) { 9712 rng = t.getRng(); 9713 9714 if (rng.setStart) { 9715 rng = {
9716 startContainer: rng.startContainer, 9717 startOffset: rng.startOffset, 9718 endContainer: rng.endContainer, 9719 endOffset: rng.endOffset 9720 }; 9721 } 9722 9723 return {rng : rng}; 9724 } 9725 9726 rng = t.getRng(); 9727 id = dom.uniqueId(); 9728 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9729 styles = 'overflow:hidden;line-height:0px'; 9730 9731 // Explorer method 9732 if (rng.duplicate || rng.item) { 9733 // Text selection 9734 if (!rng.item) { 9735 rng2 = rng.duplicate(); 9736 9737 try { 9738 // Insert start marker 9739 rng.collapse(); 9740 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9741 9742 // Insert end marker 9743 if (!collapsed) { 9744 rng2.collapse(false); 9745 9746 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9747 rng.moveToElementText(rng2.parentElement()); 9748 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9749 rng2.move('character', -1); 9750 9751 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9752 } 9753 } catch (ex) { 9754 // IE might throw unspecified error so lets ignore it 9755 return null; 9756 } 9757 } else { 9758 // Control selection 9759 element = rng.item(0); 9760 name = element.nodeName; 9761 9762 return {name : name, index : findIndex(name, element)}; 9763 } 9764 } else { 9765 element = t.getNode(); 9766 name = element.nodeName; 9767 if (name == 'IMG') 9768 return {name : name, index : findIndex(name, element)}; 9769 9770 // W3C method 9771 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9772 9773 // Insert end marker 9774 if (!collapsed) { 9775 rng2.collapse(false); 9776 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9777 } 9778 9779 rng = normalizeTableCellSelection(rng); 9780 rng.collapse(true); 9781 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9782 } 9783 9784 t.moveToBookmark({id : id, keep : 1}); 9785 9786 return {id : id}; 9787 }, 9788 9789 moveToBookmark : function(bookmark) { 9790 var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset; 9791 9792 function setEndPoint(start) { 9793 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9794 9795 if (point) { 9796 offset = point[0]; 9797 9798 // Find container node 9799 for (node = root, i = point.length - 1; i >= 1; i--) { 9800 children = node.childNodes; 9801 9802 if (point[i] > children.length - 1) 9803 return; 9804 9805 node = children[point[i]]; 9806 } 9807 9808 // Move text offset to best suitable location 9809 if (node.nodeType === 3) 9810 offset = Math.min(point[0], node.nodeValue.length); 9811 9812 // Move element offset to best suitable location 9813 if (node.nodeType === 1) 9814 offset = Math.min(point[0], node.childNodes.length); 9815 9816 // Set offset within container node 9817 if (start) 9818 rng.setStart(node, offset); 9819 else 9820 rng.setEnd(node, offset); 9821 } 9822 9823 return true; 9824 }; 9825 9826 function restoreEndPoint(suffix) { 9827 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9828 9829 if (marker) { 9830 node = marker.parentNode; 9831 9832 if (suffix == 'start') { 9833 if (!keep) { 9834 idx = dom.nodeIndex(marker); 9835 } else { 9836 node = marker.firstChild; 9837 idx = 1; 9838 } 9839 9840 startContainer = endContainer = node; 9841 startOffset = endOffset = idx; 9842 } else { 9843 if (!keep) { 9844 idx = dom.nodeIndex(marker); 9845 } else { 9846 node = marker.firstChild; 9847 idx = 1; 9848 } 9849 9850 endContainer = node; 9851 endOffset = idx; 9852 } 9853 9854 if (!keep) { 9855 prev = marker.previousSibling; 9856 next = marker.nextSibling; 9857 9858 // Remove all marker text nodes 9859 each(tinymce.grep(marker.childNodes), function(node) { 9860 if (node.nodeType == 3) 9861 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9862 }); 9863 9864 // Remove marker but keep children if for example contents where inserted into the marker 9865 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9866 while (marker = dom.get(bookmark.id + '_' + suffix)) 9867 dom.remove(marker, 1); 9868 9869 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9870 // 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 9871 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9872 idx = prev.nodeValue.length; 9873 prev.appendData(next.nodeValue); 9874 dom.remove(next); 9875 9876 if (suffix == 'start') { 9877 startContainer = endContainer = prev; 9878 startOffset = endOffset = idx; 9879 } else { 9880 endContainer = prev; 9881 endOffset = idx; 9882 } 9883 } 9884 } 9885 } 9886 }; 9887 9888 function addBogus(node) { 9889 // Adds a bogus BR element for empty block elements 9890 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9891 node.innerHTML = '<br data-mce-bogus="1" />'; 9892 9893 return node; 9894 }; 9895 9896 if (bookmark) { 9897 if (bookmark.start) { 9898 rng = dom.createRng(); 9899 root = dom.getRoot(); 9900 9901 if (t.tridentSel) 9902 return t.tridentSel.moveToBookmark(bookmark); 9903 9904 if (setEndPoint(true) && setEndPoint()) { 9905 t.setRng(rng); 9906 } 9907 } else if (bookmark.id) { 9908 // Restore start/end points 9909 restoreEndPoint('start'); 9910 restoreEndPoint('end'); 9911 9912 if (startContainer) { 9913 rng = dom.createRng(); 9914 rng.setStart(addBogus(startContainer), startOffset); 9915 rng.setEnd(addBogus(endContainer), endOffset); 9916 t.setRng(rng); 9917 } 9918 } else if (bookmark.name) { 9919 t.select(dom.select(bookmark.name)[bookmark.index]); 9920 } else if (bookmark.rng) { 9921 rng = bookmark.rng; 9922 9923 if (rng.startContainer) { 9924 rng2 = t.dom.createRng(); 9925 9926 try { 9927 rng2.setStart(rng.startContainer, rng.startOffset); 9928 rng2.setEnd(rng.endContainer, rng.endOffset); 9929 } catch (e) { 9930 // Might fail with index error 9931 } 9932 9933 rng = rng2; 9934 } 9935 9936 t.setRng(rng); 9937 } 9938 } 9939 }, 9940 9941 select : function(node, content) { 9942 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9943 9944 function setPoint(node, start) { 9945 var walker = new TreeWalker(node, node); 9946 9947 do { 9948 // Text node 9949 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9950 if (start)
9951 rng.setStart(node, 0); 9952 else 9953 rng.setEnd(node, node.nodeValue.length); 9954 9955 return; 9956 } 9957 9958 // BR element 9959 if (node.nodeName == 'BR') { 9960 if (start) 9961 rng.setStartBefore(node); 9962 else 9963 rng.setEndBefore(node); 9964 9965 return; 9966 } 9967 } while (node = (start ? walker.next() : walker.prev())); 9968 }; 9969 9970 if (node) { 9971 idx = dom.nodeIndex(node); 9972 rng.setStart(node.parentNode, idx); 9973 rng.setEnd(node.parentNode, idx + 1); 9974 9975 // Find first/last text node or BR element 9976 if (content) { 9977 setPoint(node, 1); 9978 setPoint(node); 9979 } 9980 9981 t.setRng(rng); 9982 } 9983 9984 return node; 9985 }, 9986 9987 isCollapsed : function() { 9988 var t = this, r = t.getRng(), s = t.getSel(); 9989 9990 if (!r || r.item) 9991 return false; 9992 9993 if (r.compareEndPoints) 9994 return r.compareEndPoints('StartToEnd', r) === 0; 9995 9996 return !s || r.collapsed; 9997 }, 9998 9999 collapse : function(to_start) { 10000 var self = this, rng = self.getRng(), node; 10001 10002 // Control range on IE 10003 if (rng.item) { 10004 node = rng.item(0); 10005 rng = self.win.document.body.createTextRange(); 10006 rng.moveToElementText(node); 10007 } 10008 10009 rng.collapse(!!to_start); 10010 self.setRng(rng); 10011 }, 10012 10013 getSel : function() { 10014 var t = this, w = this.win; 10015 10016 return w.getSelection ? w.getSelection() : w.document.selection; 10017 }, 10018 10019 getRng : function(w3c) { 10020 var self = this, selection, rng, elm, doc = self.win.document; 10021 10022 // Workaround for IE 11 not being able to select images properly see #6613 see quirk fix 10023 if (self.fakeRng) { 10024 return self.fakeRng; 10025 } 10026 10027 // Found tridentSel object then we need to use that one 10028 if (w3c && self.tridentSel) { 10029 return self.tridentSel.getRangeAt(0); 10030 } 10031 10032 try { 10033 if (selection = self.getSel()) { 10034 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 10035 } 10036 } catch (ex) { 10037 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 10038 } 10039 10040 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 10041 if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) { 10042 elm = doc.selection.createRange().item(0); 10043 rng = doc.createRange(); 10044 rng.setStartBefore(elm); 10045 rng.setEndAfter(elm); 10046 } 10047 10048 // No range found then create an empty one 10049 // This can occur when the editor is placed in a hidden container element on Gecko 10050 // Or on IE when there was an exception 10051 if (!rng) { 10052 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 10053 } 10054 10055 // If range is at start of document then move it to start of body 10056 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 10057 elm = self.dom.getRoot(); 10058 rng.setStart(elm, 0); 10059 rng.setEnd(elm, 0); 10060 } 10061 10062 if (self.selectedRange && self.explicitRange) { 10063 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 10064 // Safari, Opera and Chrome only ever select text which causes the range to change. 10065 // This lets us use the originally set range if the selection hasn't been changed by the user. 10066 rng = self.explicitRange; 10067 } else { 10068 self.selectedRange = null; 10069 self.explicitRange = null; 10070 } 10071 } 10072 10073 return rng; 10074 }, 10075 10076 setRng : function(r, forward) { 10077 var s, t = this; 10078 10079 if (!t.tridentSel) { 10080 s = t.getSel(); 10081 10082 if (s) { 10083 t.explicitRange = r; 10084 10085 try { 10086 s.removeAllRanges(); 10087 } catch (ex) { 10088 // IE9 might throw errors here don't know why 10089 } 10090 10091 s.addRange(r); 10092 10093 // Forward is set to false and we have an extend function 10094 if (forward === false && s.extend) { 10095 s.collapse(r.endContainer, r.endOffset); 10096 s.extend(r.startContainer, r.startOffset); 10097 } 10098 10099 // adding range isn't always successful so we need to check range count otherwise an exception can occur 10100 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 10101 } 10102 } else { 10103 // Is W3C Range 10104 if (r.cloneRange) { 10105 try { 10106 t.tridentSel.addRange(r); 10107 return; 10108 } catch (ex) { 10109 //IE9 throws an error here if called before selection is placed in the editor
10110 } 10111 } 10112 10113 // Is IE specific range 10114 try { 10115 r.select(); 10116 } catch (ex) { 10117 // Needed for some odd IE bug #1843306 10118 } 10119 } 10120 }, 10121 10122 setNode : function(n) { 10123 var t = this; 10124 10125 t.setContent(t.dom.getOuterHTML(n)); 10126 10127 return n; 10128 }, 10129 10130 getNode : function() { 10131 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 10132 10133 function skipEmptyTextNodes(n, forwards) { 10134 var orig = n; 10135 while (n && n.nodeType === 3 && n.length === 0) { 10136 n = forwards ? n.nextSibling : n.previousSibling; 10137 } 10138 return n || orig; 10139 }; 10140 10141 // Range maybe lost after the editor is made visible again 10142 if (!rng) 10143 return t.dom.getRoot(); 10144 10145 if (rng.setStart) { 10146 elm = rng.commonAncestorContainer; 10147 10148 // Handle selection a image or other control like element such as anchors 10149 if (!rng.collapsed) { 10150 if (rng.startContainer == rng.endContainer) { 10151 if (rng.endOffset - rng.startOffset < 2) { 10152 if (rng.startContainer.hasChildNodes()) 10153 elm = rng.startContainer.childNodes[rng.startOffset]; 10154 } 10155 } 10156 10157 // If the anchor node is a element instead of a text node then return this element 10158 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 10159 // return sel.anchorNode.childNodes[sel.anchorOffset]; 10160 10161 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 10162 // This happens when you double click an underlined word in FireFox. 10163 if (start.nodeType === 3 && end.nodeType === 3) { 10164 if (start.length === rng.startOffset) { 10165 start = skipEmptyTextNodes(start.nextSibling, true); 10166 } else { 10167 start = start.parentNode; 10168 } 10169 if (rng.endOffset === 0) { 10170 end = skipEmptyTextNodes(end.previousSibling, false); 10171 } else { 10172 end = end.parentNode; 10173 } 10174 10175 if (start && start === end) 10176 return start; 10177 } 10178 } 10179 10180 if (elm && elm.nodeType == 3) 10181 return elm.parentNode; 10182 10183 return elm; 10184 } 10185 10186 return rng.item ? rng.item(0) : rng.parentElement(); 10187 }, 10188 10189 getSelectedBlocks : function(st, en) { 10190 var t = this, dom = t.dom, sb, eb, n, bl = []; 10191 10192 sb = dom.getParent(st || t.getStart(), dom.isBlock); 10193 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 10194 10195 if (sb) 10196 bl.push(sb); 10197 10198 if (sb && eb && sb != eb) { 10199 n = sb; 10200 10201 var walker = new TreeWalker(sb, dom.getRoot()); 10202 while ((n = walker.next()) && n != eb) { 10203 if (dom.isBlock(n)) 10204 bl.push(n); 10205 } 10206 } 10207 10208 if (eb && sb != eb) 10209 bl.push(eb); 10210 10211 return bl; 10212 }, 10213 10214 isForward: function(){ 10215 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 10216 10217 // No support for selection direction then always return true 10218 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 10219 return true; 10220 } 10221 10222 anchorRange = dom.createRng(); 10223 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 10224 anchorRange.collapse(true); 10225 10226 focusRange = dom.createRng(); 10227 focusRange.setStart(sel.focusNode, sel.focusOffset); 10228 focusRange.collapse(true); 10229 10230 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 10231 }, 10232 10233 normalize : function() { 10234 var self = this, rng, normalized, collapsed, node, sibling; 10235 10236 function normalizeEndPoint(start) { 10237 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 10238 10239 function hasBrBeforeAfter(node, left) { 10240 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 10241 10242 while (node = walker[left ? 'prev' : 'next']()) { 10243 if (node.nodeName === "BR") { 10244 return true; 10245 } 10246 } 10247 }; 10248 10249 // Walks the dom left/right to find a suitable text node to move the endpoint into 10250 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 10251 function findTextNodeRelative(left, startNode) { 10252 var walker, lastInlineElement; 10253 10254 startNode = startNode || container; 10255 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 10256 10257 // Walk left until we hit a text node we can move to or a block/br/img 10258 while (node = walker[left ? 'prev' : 'next']()) { 10259 // Found text node that has a length 10260 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10261 container = node; 10262 offset = left ? node.nodeValue.length : 0; 10263 normalized = true; 10264 return; 10265 } 10266 10267 // Break if we find a block or a BR/IMG/INPUT etc 10268 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10269 return; 10270 } 10271 10272 lastInlineElement = node; 10273 } 10274 10275 // Only fetch the last inline element when in caret mode for now 10276 if (collapsed && lastInlineElement) { 10277 container = lastInlineElement; 10278 normalized = true; 10279 offset = 0; 10280 } 10281 }; 10282 10283 container = rng[(start ? 'start' : 'end') + 'Container']; 10284 offset = rng[(start ? 'start' : 'end') + 'Offset']; 10285 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 10286 10287 // If the container is a document move it to the body element 10288 if (container.nodeType === 9) { 10289 container = dom.getRoot(); 10290 offset = 0; 10291 } 10292 10293 // If the container is body try move it into the closest text node or position 10294 if (container === body) { 10295 // If start is before/after a image, table etc 10296 if (start) { 10297 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 10298 if (node) { 10299 nodeName = node.nodeName.toLowerCase(); 10300 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 10301 return; 10302 } 10303 } 10304 } 10305 10306 // Resolve the index 10307 if (container.hasChildNodes()) { 10308 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 10309 offset = 0; 10310 10311 // Don't walk into elements that doesn't have any child nodes like a IMG 10312 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 10313 // Walk the DOM to find a text node to place the caret at or a BR 10314 node = container; 10315 walker = new TreeWalker(container, body); 10316 10317 do { 10318 // Found a text node use that position 10319 if (node.nodeType === 3 && node.nodeValue.length > 0) { 10320 offset = start ? 0 : node.nodeValue.length; 10321 container = node; 10322 normalized = true; 10323 break; 10324 } 10325 10326 // Found a BR/IMG element that we can place the caret before 10327 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 10328 offset = dom.nodeIndex(node); 10329 container = node.parentNode; 10330 10331 // Put caret after image when moving the end point 10332 if (node.nodeName == "IMG" && !start) { 10333 offset++; 10334 } 10335 10336 normalized = true; 10337 break; 10338 } 10339 } while (node = (start ? walker.next() : walker.prev())); 10340 } 10341 } 10342 } 10343 10344 // Lean the caret to the left if possible 10345 if (collapsed) { 10346 // So this: <b>x</b><i>|x</i> 10347 // Becomes: <b>x|</b><i>x</i> 10348 // Seems that only gecko has issues with this 10349 if (container.nodeType === 3 && offset === 0) { 10350 findTextNodeRelative(true); 10351 } 10352 10353 // Lean left into empty inline elements when the caret is before a BR 10354 // So this: <i><b></b><i>|<br></i> 10355 // Becomes: <i><b>|</b><i><br></i> 10356 // Seems that only gecko has issues with this 10357 if (container.nodeType === 1) { 10358 node = container.childNodes[offset]; 10359 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 10360 findTextNodeRelative(true, container.childNodes[offset]); 10361 } 10362 } 10363 } 10364 10365 // Lean the start of the selection right if possible 10366 // So this: x[<b>x]</b> 10367 // Becomes: x<b>[x]</b> 10368 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 10369 findTextNodeRelative(false); 10370 } 10371 10372 // Set endpoint if it was normalized 10373 if (normalized) 10374 rng['set' + (start ? 'Start' : 'End')](container, offset); 10375 }; 10376 10377 // Normalize only on non IE browsers for now 10378 if (tinymce.isIE) 10379 return; 10380 10381 rng = self.getRng(); 10382 collapsed = rng.collapsed; 10383 10384 // Normalize the end points 10385 normalizeEndPoint(true); 10386 10387 if (!collapsed) 10388 normalizeEndPoint(); 10389 10390 // Set the selection if it was normalized 10391 if (normalized) { 10392 // If it was collapsed then make sure it still is 10393 if (collapsed) { 10394 rng.collapse(true); 10395 } 10396 10397 //console.log(self.dom.dumpRng(rng)); 10398 self.setRng(rng, self.isForward()); 10399 } 10400 }, 10401 10402 selectorChanged: function(selector, callback) { 10403 var self = this, currentSelectors; 10404 10405 if (!self.selectorChangedData) { 10406 self.selectorChangedData = {}; 10407 currentSelectors = {}; 10408 10409 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10410 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10411 10412 // Check for new matching selectors 10413 each(self.selectorChangedData, function(callbacks, selector) { 10414 each(parents, function(node) { 10415 if (dom.is(node, selector)) { 10416 if (!currentSelectors[selector]) { 10417 // Execute callbacks 10418 each(callbacks, function(callback) { 10419 callback(true, {node: node, selector: selector, parents: parents}); 10420 }); 10421 10422 currentSelectors[selector] = callbacks; 10423 } 10424 10425 matchedSelectors[selector] = callbacks; 10426 return false;
10427 } 10428 }); 10429 }); 10430 10431 // Check if current selectors still match 10432 each(currentSelectors, function(callbacks, selector) { 10433 if (!matchedSelectors[selector]) { 10434 delete currentSelectors[selector]; 10435 10436 each(callbacks, function(callback) { 10437 callback(false, {node: node, selector: selector, parents: parents}); 10438 }); 10439 } 10440 }); 10441 }); 10442 } 10443 10444 // Add selector listeners 10445 if (!self.selectorChangedData[selector]) { 10446 self.selectorChangedData[selector] = []; 10447 } 10448 10449 self.selectorChangedData[selector].push(callback); 10450 10451 return self; 10452 }, 10453 10454 scrollIntoView: function(elm) { 10455 var y, viewPort, self = this, dom = self.dom; 10456 10457 viewPort = dom.getViewPort(self.editor.getWin()); 10458 y = dom.getPos(elm).y; 10459 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 10460 self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); 10461 } 10462 }, 10463 10464 destroy : function(manual) { 10465 var self = this; 10466 10467 self.win = null; 10468 10469 // Manual destroy then remove unload handler 10470 if (!manual) 10471 tinymce.removeUnload(self.destroy); 10472 }, 10473 10474 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10475 _fixIESelection : function() { 10476 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10477 10478 // Return range from point or null if it failed 10479 function rngFromPoint(x, y) { 10480 var rng = body.createTextRange(); 10481 10482 try { 10483 rng.moveToPoint(x, y); 10484 } catch (ex) { 10485 // IE sometimes throws and exception, so lets just ignore it 10486 rng = null; 10487 } 10488 10489 return rng; 10490 }; 10491 10492 // Fires while the selection is changing 10493 function selectionChange(e) { 10494 var pointRng; 10495 10496 // Check if the button is down or not 10497 if (e.button) { 10498 // Create range from mouse position 10499 pointRng = rngFromPoint(e.x, e.y); 10500 10501 if (pointRng) { 10502 // Check if pointRange is before/after selection then change the endPoint 10503 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10504 pointRng.setEndPoint('StartToStart', startRng); 10505 else 10506 pointRng.setEndPoint('EndToEnd', startRng); 10507 10508 pointRng.select(); 10509 } 10510 } else 10511 endSelection(); 10512 } 10513 10514 // Removes listeners 10515 function endSelection() { 10516 var rng = doc.selection.createRange(); 10517 10518 // If the range is collapsed then use the last start range 10519 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10520 startRng.select(); 10521 10522 dom.unbind(doc, 'mouseup', endSelection); 10523 dom.unbind(doc, 'mousemove', selectionChange); 10524 startRng = started = 0; 10525 }; 10526 10527 // Make HTML element unselectable since we are going to handle selection by hand 10528 doc.documentElement.unselectable = true; 10529 10530 // Detect when user selects outside BODY 10531 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10532 if (e.target.nodeName === 'HTML') { 10533 if (started) 10534 endSelection(); 10535 10536 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10537 htmlElm = doc.documentElement; 10538 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10539 return; 10540 10541 started = 1; 10542 // Setup start position 10543 startRng = rngFromPoint(e.x, e.y); 10544 if (startRng) { 10545 // Listen for selection change events 10546 dom.bind(doc, 'mouseup', endSelection); 10547 dom.bind(doc, 'mousemove', selectionChange); 10548 10549 dom.win.focus(); 10550 startRng.select(); 10551 } 10552 } 10553 }); 10554 } 10555 }); 10556})(tinymce); 10557 10558(function(tinymce) { 10559 tinymce.dom.Serializer = function(settings, dom, schema) { 10560 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10561 10562 // Support the old apply_source_formatting option 10563 if (!settings.apply_source_formatting) 10564 settings.indent = false; 10565 10566 // Default DOM and Schema if they are undefined 10567 dom = dom || tinymce.DOM; 10568 schema = schema || new tinymce.html.Schema(settings); 10569 settings.entity_encoding = settings.entity_encoding || 'named'; 10570 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10571 10572 onPreProcess = new tinymce.util.Dispatcher(self); 10573 10574 onPostProcess = new tinymce.util.Dispatcher(self); 10575 10576 htmlParser = new tinymce.html.DomParser(settings, schema); 10577 10578 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10579 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10580 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10581 10582 while (i--) { 10583 node = nodes[i]; 10584 10585 value = node.attributes.map[internalName]; 10586 if (value !== undef) { 10587 // Set external name to internal value and remove internal 10588 node.attr(name, value.length > 0 ? value : null); 10589 node.attr(internalName, null); 10590 } else { 10591 // No internal attribute found then convert the value we have in the DOM 10592 value = node.attributes.map[name]; 10593 10594 if (name === "style") 10595 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10596 else if (urlConverter) 10597 value = urlConverter.call(urlConverterScope, value, name, node.name); 10598 10599 node.attr(name, value.length > 0 ? value : null); 10600 } 10601 } 10602 }); 10603
10604 // Remove internal classes mceItem<..> or mceSelected 10605 htmlParser.addAttributeFilter('class', function(nodes, name) { 10606 var i = nodes.length, node, value; 10607 10608 while (i--) { 10609 node = nodes[i]; 10610 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10611 node.attr('class', value.length > 0 ? value : null); 10612 } 10613 }); 10614 10615 // Remove bookmark elements 10616 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10617 var i = nodes.length, node; 10618 10619 while (i--) { 10620 node = nodes[i]; 10621 10622 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10623 node.remove(); 10624 } 10625 }); 10626 10627 // Remove expando attributes 10628 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10629 var i = nodes.length; 10630 10631 while (i--) { 10632 nodes[i].attr(name, null); 10633 } 10634 }); 10635 10636 htmlParser.addNodeFilter('noscript', function(nodes) { 10637 var i = nodes.length, node; 10638 10639 while (i--) { 10640 node = nodes[i].firstChild; 10641 10642 if (node) { 10643 node.value = tinymce.html.Entities.decode(node.value); 10644 } 10645 } 10646 }); 10647 10648 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10649 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10650 var i = nodes.length, node, value; 10651 10652 function trim(value) { 10653 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10654 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10655 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10656 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10657 }; 10658 10659 while (i--) { 10660 node = nodes[i]; 10661 value = node.firstChild ? node.firstChild.value : ''; 10662 10663 if (name === "script") { 10664 // Remove mce- prefix from script elements 10665 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10666 10667 if (value.length > 0) 10668 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10669 } else { 10670 if (value.length > 0) 10671 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10672 } 10673 } 10674 }); 10675 10676 // Convert comments to cdata and handle protected comments 10677 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10678 var i = nodes.length, node; 10679 10680 while (i--) { 10681 node = nodes[i]; 10682 10683 if (node.value.indexOf('[CDATA[') === 0) { 10684 node.name = '#cdata'; 10685 node.type = 4; 10686 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10687 } else if (node.value.indexOf('mce:protected ') === 0) { 10688 node.name = "#text"; 10689 node.type = 3; 10690 node.raw = true; 10691 node.value = unescape(node.value).substr(14); 10692 } 10693 } 10694 }); 10695 10696 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10697 var i = nodes.length, node; 10698 10699 while (i--) { 10700 node = nodes[i]; 10701 if (node.type === 7) 10702 node.remove(); 10703 else if (node.type === 1) { 10704 if (name === "input" && !("type" in node.attributes.map)) 10705 node.attr('type', 'text'); 10706 } 10707 } 10708 }); 10709 10710 // Fix list elements, TODO: Replace this later 10711 if (settings.fix_list_elements) { 10712 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10713 var i = nodes.length, node, parentNode; 10714 10715 while (i--) { 10716 node = nodes[i]; 10717 parentNode = node.parent; 10718 10719 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10720 if (node.prev && node.prev.name === 'li') { 10721 node.prev.append(node); 10722 } 10723 } 10724 } 10725 }); 10726 } 10727 10728 // Remove internal data attributes 10729 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10730 var i = nodes.length; 10731 10732 while (i--) { 10733 nodes[i].attr(name, null); 10734 } 10735 }); 10736 10737 // Return public methods 10738 return { 10739 schema : schema, 10740 10741 addNodeFilter : htmlParser.addNodeFilter, 10742 10743 addAttributeFilter : htmlParser.addAttributeFilter, 10744 10745 onPreProcess : onPreProcess, 10746 10747 onPostProcess : onPostProcess, 10748 10749 serialize : function(node, args) { 10750 var impl, doc, oldDoc, htmlSerializer, content; 10751 10752 // Explorer won't clone contents of script and style and the 10753 // selected index of select elements are cleared on a clone operation. 10754 if (isIE && dom.select('script,style,select,map').length > 0) { 10755 content = node.innerHTML; 10756 node = node.cloneNode(false); 10757 dom.setHTML(node, content); 10758 } else 10759 node = node.cloneNode(true); 10760 10761 // Nodes needs to be attached to something in WebKit/Opera 10762 // Older builds of Opera crashes if you attach the node to an document created dynamically 10763 // and since we can't feature detect a crash we need to sniff the acutal build number 10764 // This fix will make DOM ranges and make Sizzle happy! 10765 try { 10766 impl = node.ownerDocument.implementation; 10767 if (impl.createHTMLDocument) { 10768 // Create an empty HTML document 10769 doc = impl.createHTMLDocument(""); 10770 10771 // Add the element or it's children if it's a body element to the new document 10772 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10773 doc.body.appendChild(doc.importNode(node, true)); 10774 }); 10775 10776 // Grab first child or body element for serialization 10777 if (node.nodeName != 'BODY') 10778 node = doc.body.firstChild; 10779 else 10780 node = doc.body; 10781 10782 // set the new document in DOMUtils so createElement etc works 10783 oldDoc = dom.doc; 10784 dom.doc = doc; 10785 } 10786 } catch(err) { 10787 console.log('serialize: ' + err); 10788 } 10789 args = args || {}; 10790 args.format = args.format || 'html'; 10791 10792 // Pre process 10793 if (!args.no_events) { 10794 args.node = node; 10795 onPreProcess.dispatch(self, args); 10796 } 10797 10798 // Setup serializer 10799 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10800 10801 // Parse and serialize HTML 10802 args.content = htmlSerializer.serialize( 10803 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10804 ); 10805 10806 // Replace all BOM characters for now until we can find a better solution 10807 if (!args.cleanup){ 10808 args.content = args.content.replace(/\uFEFF/g, ''); 10809 // usuwam slasha z taga 10810 args.content = args.content.replace('/>', '>'); 10811 } 10812 10813 // Post process 10814 if (!args.no_events) 10815 onPostProcess.dispatch(self, args); 10816 10817 // Restore the old document if it was changed 10818 if (oldDoc) 10819 dom.doc = oldDoc; 10820 10821 args.node = null; 10822 10823 return args.content; 10824 }, 10825 10826 addRules : function(rules) { 10827 schema.addValidElements(rules); 10828 }, 10829 10830 setRules : function(rules) { 10831 schema.setValidElements(rules); 10832 } 10833 }; 10834 }; 10835})(tinymce); 10836(function(tinymce) { 10837 tinymce.dom.ScriptLoader = function(settings) { 10838 var QUEUED = 0, 10839 LOADING = 1, 10840 LOADED = 2, 10841 states = {}, 10842 queue = [], 10843 scriptLoadedCallbacks = {}, 10844 queueLoadedCallbacks = [], 10845 loading = 0, 10846 undef; 10847 10848 function loadScript(url, callback) { 10849 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10850 10851 // Execute callback when script is loaded 10852 function done() { 10853 dom.remove(id); 10854 10855 if (elm) 10856 elm.onreadystatechange = elm.onload = elm = null; 10857 10858 callback(); 10859 }; 10860 10861 function error() { 10862 // Report the error so it's easier for people to spot loading errors 10863 if (typeof(console) !== "undefined" && console.log) 10864 console.log("Failed to load: " + url); 10865 10866 // We can't mark it as done if there is a load error since 10867 // A) We don't want to produce 404 errors on the server and 10868 // B) the onerror event won't fire on all browsers. 10869 // done(); 10870 }; 10871 10872 id = dom.uniqueId(); 10873 10874 if (tinymce.isIE6) { 10875 uri = new tinymce.util.URI(url); 10876 loc = location; 10877 10878 // If script is from same domain and we 10879 // use IE 6 then use XHR since it's more reliable 10880 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10881 tinymce.util.XHR.send({ 10882 url : tinymce._addVer(uri.getURI()), 10883 success : function(content) { 10884 // Create new temp script element 10885 var script = dom.create('script', { 10886 type : 'text/javascript' 10887 }); 10888 10889 // Evaluate script in global scope 10890 script.text = content; 10891 document.getElementsByTagName('head')[0].appendChild(script); 10892 dom.remove(script); 10893 10894 done(); 10895 }, 10896 10897 error : error 10898 }); 10899 10900 return; 10901 } 10902 } 10903 10904 // Create new script element 10905 elm = document.createElement('script'); 10906 elm.id = id; 10907 elm.type = 'text/javascript'; 10908 elm.src = tinymce._addVer(url); 10909 10910 // Add onload listener for non IE browsers since IE9 10911 // fires onload event before the script is parsed and executed 10912 if (!tinymce.isIE || tinymce.isIE11) 10913 elm.onload = done; 10914 10915 // Add onerror event will get fired on some browsers but not all of them 10916 elm.onerror = error; 10917 10918 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10919 if (!tinymce.isOpera) { 10920 elm.onreadystatechange = function() { 10921 var state = elm.readyState; 10922 10923 // Loaded state is passed on IE 6 however there 10924 // are known issues with this method but we can't use 10925 // XHR in a cross domain loading 10926 if (state == 'complete' || state == 'loaded') 10927 done(); 10928 }; 10929 } 10930 10931 // Most browsers support this feature so we report errors 10932 // for those at least to help users track their missing plugins etc 10933 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10934 /*elm.onerror = function() { 10935 alert('Failed to load: ' + url); 10936 };*/ 10937 10938 // Add script to document 10939 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10940 }; 10941 10942 this.isDone = function(url) { 10943 return states[url] == LOADED; 10944 }; 10945 10946 this.markDone = function(url) { 10947 states[url] = LOADED; 10948 }; 10949 10950 this.add = this.load = function(url, callback, scope) { 10951 var item, state = states[url]; 10952 10953 // Add url to load queue 10954 if (state == undef) { 10955 queue.push(url); 10956 states[url] = QUEUED; 10957 } 10958 10959 if (callback) { 10960 // Store away callback for later execution 10961 if (!scriptLoadedCallbacks[url]) 10962 scriptLoadedCallbacks[url] = []; 10963 10964 scriptLoadedCallbacks[url].push({ 10965 func : callback, 10966 scope : scope || this 10967 }); 10968 } 10969 }; 10970 10971 this.loadQueue = function(callback, scope) { 10972 this.loadScripts(queue, callback, scope); 10973 }; 10974 10975 this.loadScripts = function(scripts, callback, scope) { 10976 var loadScripts; 10977 10978 function execScriptLoadedCallbacks(url) { 10979 // Execute URL callback functions 10980 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10981 callback.func.call(callback.scope); 10982 }); 10983 10984 scriptLoadedCallbacks[url] = undef; 10985 }; 10986 10987 queueLoadedCallbacks.push({ 10988 func : callback, 10989 scope : scope || this 10990 }); 10991 10992 loadScripts = function() { 10993 var loadingScripts = tinymce.grep(scripts); 10994 10995 // Current scripts has been handled 10996 scripts.length = 0; 10997 10998 // Load scripts that needs to be loaded 10999 tinymce.each(loadingScripts, function(url) { 11000 // Script is already loaded then execute script callbacks directly 11001 if (states[url] == LOADED) { 11002 execScriptLoadedCallbacks(url); 11003 return; 11004 } 11005 11006 // Is script not loading then start loading it 11007 if (states[url] != LOADING) { 11008 states[url] = LOADING; 11009 loading++; 11010 11011 loadScript(url, function() { 11012 states[url] = LOADED; 11013 loading--; 11014 11015 execScriptLoadedCallbacks(url); 11016 11017 // Load more scripts if they where added by the recently loaded script 11018 loadScripts(); 11019 }); 11020 } 11021 }); 11022 11023 // No scripts are currently loading then execute all pending queue loaded callbacks 11024 if (!loading) { 11025 tinymce.each(queueLoadedCallbacks, function(callback) { 11026 callback.func.call(callback.scope); 11027 }); 11028 11029 queueLoadedCallbacks.length = 0; 11030 } 11031 }; 11032 11033 loadScripts(); 11034 }; 11035 }; 11036 11037 // Global script loader 11038 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 11039})(tinymce); 11040 11041(function(tinymce) { 11042 tinymce.dom.RangeUtils = function(dom) { 11043 var INVISIBLE_CHAR = '\uFEFF'; 11044 11045 this.walk = function(rng, callback) { 11046 var startContainer = rng.startContainer, 11047 startOffset = rng.startOffset, 11048 endContainer = rng.endContainer, 11049 endOffset = rng.endOffset, 11050 ancestor, startPoint, 11051 endPoint, node, parent, siblings, nodes; 11052 11053 // Handle table cell selection the table plugin enables 11054 // you to fake select table cells and perform formatting actions on them 11055 nodes = dom.select('td.mceSelected,th.mceSelected'); 11056 if (nodes.length > 0) { 11057 tinymce.each(nodes, function(node) { 11058 callback([node]); 11059 }); 11060 11061 return; 11062 } 11063 11064 function exclude(nodes) { 11065 var node; 11066 11067 // First node is excluded 11068 node = nodes[0]; 11069 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 11070 nodes.splice(0, 1); 11071 } 11072 11073 // Last node is excluded 11074 node = nodes[nodes.length - 1]; 11075 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 11076 nodes.splice(nodes.length - 1, 1); 11077 } 11078 11079 return nodes; 11080 }; 11081 11082 function collectSiblings(node, name, end_node) { 11083 var siblings = []; 11084 11085 for (; node && node != end_node; node = node[name]) 11086 siblings.push(node); 11087 11088 return siblings; 11089 }; 11090 11091 function findEndPoint(node, root) { 11092 do { 11093 if (node.parentNode == root) 11094 return node; 11095 11096 node = node.parentNode; 11097 } while(node); 11098 }; 11099
11100 function walkBoundary(start_node, end_node, next) { 11101 var siblingName = next ? 'nextSibling' : 'previousSibling'; 11102 11103 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 11104 parent = node.parentNode; 11105 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 11106 11107 if (siblings.length) { 11108 if (!next) 11109 siblings.reverse(); 11110 11111 callback(exclude(siblings)); 11112 } 11113 } 11114 }; 11115 11116 // If index based start position then resolve it 11117 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 11118 startContainer = startContainer.childNodes[startOffset]; 11119 11120 // If index based end position then resolve it 11121 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 11122 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 11123 11124 // Same container 11125 if (startContainer == endContainer) 11126 return callback(exclude([startContainer])); 11127 11128 // Find common ancestor and end points 11129 ancestor = dom.findCommonAncestor(startContainer, endContainer); 11130 11131 // Process left side 11132 for (node = startContainer; node; node = node.parentNode) { 11133 if (node === endContainer) 11134 return walkBoundary(startContainer, ancestor, true); 11135 11136 if (node === ancestor) 11137 break; 11138 } 11139 11140 // Process right side 11141 for (node = endContainer; node; node = node.parentNode) { 11142 if (node === startContainer) 11143 return walkBoundary(endContainer, ancestor); 11144 11145 if (node === ancestor) 11146 break; 11147 } 11148 11149 // Find start/end point 11150 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 11151 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 11152 11153 // Walk left leaf 11154 walkBoundary(startContainer, startPoint, true); 11155 11156 // Walk the middle from start to end point 11157 siblings = collectSiblings( 11158 startPoint == startContainer ? startPoint : startPoint.nextSibling, 11159 'nextSibling', 11160 endPoint == endContainer ? endPoint.nextSibling : endPoint 11161 ); 11162 11163 if (siblings.length) 11164 callback(exclude(siblings)); 11165 11166 // Walk right leaf 11167 walkBoundary(endContainer, endPoint); 11168 }; 11169 11170 this.split = function(rng) { 11171 var startContainer = rng.startContainer, 11172 startOffset = rng.startOffset, 11173 endContainer = rng.endContainer, 11174 endOffset = rng.endOffset; 11175 11176 function splitText(node, offset) { 11177 return node.splitText(offset); 11178 }; 11179 11180 // Handle single text node 11181 if (startContainer == endContainer && startContainer.nodeType == 3) { 11182 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11183 endContainer = splitText(startContainer, startOffset); 11184 startContainer = endContainer.previousSibling; 11185 11186 if (endOffset > startOffset) { 11187 endOffset = endOffset - startOffset; 11188 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 11189 endOffset = endContainer.nodeValue.length; 11190 startOffset = 0; 11191 } else { 11192 endOffset = 0; 11193 } 11194 } 11195 } else { 11196 // Split startContainer text node if needed 11197 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 11198 startContainer = splitText(startContainer, startOffset); 11199 startOffset = 0; 11200 } 11201 11202 // Split endContainer text node if needed 11203 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 11204 endContainer = splitText(endContainer, endOffset).previousSibling; 11205 endOffset = endContainer.nodeValue.length; 11206 } 11207 } 11208 11209 return { 11210 startContainer : startContainer, 11211 startOffset : startOffset, 11212 endContainer : endContainer, 11213 endOffset : endOffset 11214 }; 11215 }; 11216 11217 }; 11218 11219 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 11220 if (rng1 && rng2) { 11221 // Compare native IE ranges 11222 if (rng1.item || rng1.duplicate) { 11223 // Both are control ranges and the selected element matches 11224 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 11225 return true; 11226 11227 // Both are text ranges and the range matches 11228 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 11229 return true; 11230 } else { 11231 // Compare w3c ranges 11232 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 11233 } 11234 } 11235 11236 return false;
11237 }; 11238})(tinymce); 11239 11240(function(tinymce) { 11241 var Event = tinymce.dom.Event, each = tinymce.each; 11242 11243 tinymce.create('tinymce.ui.KeyboardNavigation', { 11244 KeyboardNavigation: function(settings, dom) { 11245 var t = this, root = settings.root, items = settings.items, 11246 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 11247 excludeFromTabOrder = settings.excludeFromTabOrder, 11248 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 11249 11250 dom = dom || tinymce.DOM; 11251 11252 itemFocussed = function(evt) { 11253 focussedId = evt.target.id; 11254 }; 11255 11256 itemBlurred = function(evt) { 11257 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 11258 }; 11259 11260 rootFocussed = function(evt) { 11261 var item = dom.get(focussedId); 11262 dom.setAttrib(item, 'tabindex', '0'); 11263 item.focus(); 11264 }; 11265 11266 t.focus = function() { 11267 dom.get(focussedId).focus(); 11268 }; 11269 11270 t.destroy = function() { 11271 each(items, function(item) { 11272 var elm = dom.get(item.id); 11273 11274 dom.unbind(elm, 'focus', itemFocussed); 11275 dom.unbind(elm, 'blur', itemBlurred); 11276 }); 11277 11278 var rootElm = dom.get(root); 11279 dom.unbind(rootElm, 'focus', rootFocussed); 11280 dom.unbind(rootElm, 'keydown', rootKeydown); 11281 11282 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 11283 t.destroy = function() {}; 11284 }; 11285 11286 t.moveFocus = function(dir, evt) { 11287 var idx = -1, controls = t.controls, newFocus; 11288 11289 if (!focussedId) 11290 return; 11291 11292 each(items, function(item, index) { 11293 if (item.id === focussedId) { 11294 idx = index; 11295 return false; 11296 } 11297 }); 11298 11299 idx += dir; 11300 if (idx < 0) { 11301 idx = items.length - 1; 11302 } else if (idx >= items.length) { 11303 idx = 0; 11304 } 11305 11306 newFocus = items[idx]; 11307 dom.setAttrib(focussedId, 'tabindex', '-1'); 11308 dom.setAttrib(newFocus.id, 'tabindex', '0'); 11309 dom.get(newFocus.id).focus(); 11310 11311 if (settings.actOnFocus) { 11312 settings.onAction(newFocus.id); 11313 } 11314 11315 if (evt) 11316 Event.cancel(evt); 11317 }; 11318 11319 rootKeydown = function(evt) { 11320 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; 11321 11322 switch (evt.keyCode) { 11323 case DOM_VK_LEFT: 11324 if (enableLeftRight) t.moveFocus(-1); 11325 Event.cancel(evt); 11326 break; 11327 11328 case DOM_VK_RIGHT: 11329 if (enableLeftRight) t.moveFocus(1); 11330 Event.cancel(evt); 11331 break; 11332 11333 case DOM_VK_UP: 11334 if (enableUpDown) t.moveFocus(-1); 11335 Event.cancel(evt); 11336 break; 11337 11338 case DOM_VK_DOWN: 11339 if (enableUpDown) t.moveFocus(1); 11340 Event.cancel(evt); 11341 break; 11342 11343 case DOM_VK_ESCAPE: 11344 if (settings.onCancel) { 11345 settings.onCancel(); 11346 Event.cancel(evt); 11347 } 11348 break; 11349 11350 case DOM_VK_ENTER: 11351 case DOM_VK_RETURN: 11352 case DOM_VK_SPACE: 11353 if (settings.onAction) { 11354 settings.onAction(focussedId); 11355 Event.cancel(evt); 11356 } 11357 break; 11358 } 11359 }; 11360 11361 // Set up state and listeners for each item. 11362 each(items, function(item, idx) { 11363 var tabindex, elm; 11364 11365 if (!item.id) { 11366 item.id = dom.uniqueId('_mce_item_'); 11367 } 11368 11369 elm = dom.get(item.id); 11370 11371 if (excludeFromTabOrder) { 11372 dom.bind(elm, 'blur', itemBlurred); 11373 tabindex = '-1'; 11374 } else { 11375 tabindex = (idx === 0 ? '0' : '-1'); 11376 } 11377 11378 elm.setAttribute('tabindex', tabindex); 11379 dom.bind(elm, 'focus', itemFocussed); 11380 }); 11381 11382 // Setup initial state for root element. 11383 if (items[0]){ 11384 focussedId = items[0].id; 11385 } 11386 11387 dom.setAttrib(root, 'tabindex', '-1'); 11388 11389 // Setup listeners for root element. 11390 var rootElm = dom.get(root); 11391 dom.bind(rootElm, 'focus', rootFocussed); 11392 dom.bind(rootElm, 'keydown', rootKeydown); 11393 } 11394 }); 11395})(tinymce); 11396 11397(function(tinymce) { 11398 // Shorten class names 11399 var DOM = tinymce.DOM, is = tinymce.is; 11400 11401 tinymce.create('tinymce.ui.Control', { 11402 Control : function(id, s, editor) { 11403 this.id = id; 11404 this.settings = s = s || {}; 11405 this.rendered = false; 11406 this.onRender = new tinymce.util.Dispatcher(this); 11407 this.classPrefix = ''; 11408 this.scope = s.scope || this; 11409 this.disabled = 0; 11410 this.active = 0; 11411 this.editor = editor; 11412 }, 11413 11414 setAriaProperty : function(property, value) {
11415 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11416 if (element) { 11417 DOM.setAttrib(element, 'aria-' + property, !!value); 11418 } 11419 }, 11420 11421 focus : function() { 11422 DOM.get(this.id).focus(); 11423 }, 11424 11425 setDisabled : function(s) { 11426 if (s != this.disabled) { 11427 this.setAriaProperty('disabled', s); 11428 11429 this.setState('Disabled', s); 11430 this.setState('Enabled', !s); 11431 this.disabled = s; 11432 } 11433 }, 11434 11435 isDisabled : function() { 11436 return this.disabled; 11437 }, 11438 11439 setActive : function(s) { 11440 if (s != this.active) { 11441 this.setState('Active', s); 11442 this.active = s; 11443 this.setAriaProperty('pressed', s); 11444 } 11445 }, 11446 11447 isActive : function() { 11448 return this.active; 11449 }, 11450 11451 setState : function(c, s) { 11452 var n = DOM.get(this.id); 11453 11454 c = this.classPrefix + c; 11455 11456 if (s) 11457 DOM.addClass(n, c); 11458 else 11459 DOM.removeClass(n, c); 11460 }, 11461 11462 isRendered : function() { 11463 return this.rendered; 11464 }, 11465 11466 renderHTML : function() { 11467 }, 11468 11469 renderTo : function(n) { 11470 DOM.setHTML(n, this.renderHTML()); 11471 }, 11472 11473 postRender : function() { 11474 var t = this, b; 11475 11476 // Set pending states 11477 if (is(t.disabled)) { 11478 b = t.disabled; 11479 t.disabled = -1; 11480 t.setDisabled(b); 11481 } 11482 11483 if (is(t.active)) { 11484 b = t.active; 11485 t.active = -1; 11486 t.setActive(b); 11487 } 11488 }, 11489 11490 remove : function() { 11491 DOM.remove(this.id); 11492 this.destroy(); 11493 }, 11494 11495 destroy : function() { 11496 tinymce.dom.Event.clear(this.id); 11497 } 11498 }); 11499})(tinymce); 11500tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11501 Container : function(id, s, editor) { 11502 this.parent(id, s, editor); 11503 11504 this.controls = []; 11505 11506 this.lookup = {}; 11507 }, 11508 11509 add : function(c) { 11510 this.lookup[c.id] = c; 11511 this.controls.push(c); 11512 11513 return c; 11514 }, 11515 11516 get : function(n) { 11517 return this.lookup[n]; 11518 } 11519}); 11520 11521 11522tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11523 Separator : function(id, s) { 11524 this.parent(id, s); 11525 this.classPrefix = 'mceSeparator'; 11526 this.setDisabled(true); 11527 }, 11528 11529 renderHTML : function() { 11530 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11531 } 11532}); 11533 11534(function(tinymce) { 11535 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11536 11537 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11538 MenuItem : function(id, s) { 11539 this.parent(id, s); 11540 this.classPrefix = 'mceMenuItem'; 11541 }, 11542 11543 setSelected : function(s) { 11544 this.setState('Selected', s); 11545 this.setAriaProperty('checked', !!s); 11546 this.selected = s; 11547 }, 11548 11549 isSelected : function() { 11550 return this.selected; 11551 }, 11552 11553 postRender : function() { 11554 var t = this; 11555 11556 t.parent(); 11557 11558 // Set pending state 11559 if (is(t.selected)) 11560 t.setSelected(t.selected); 11561 } 11562 }); 11563})(tinymce); 11564 11565(function(tinymce) { 11566 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11567 11568 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11569 Menu : function(id, s) { 11570 var t = this; 11571 11572 t.parent(id, s); 11573 t.items = {}; 11574 t.collapsed = false; 11575 t.menuCount = 0; 11576 t.onAddItem = new tinymce.util.Dispatcher(this); 11577 }, 11578 11579 expand : function(d) { 11580 var t = this; 11581 11582 if (d) { 11583 walk(t, function(o) { 11584 if (o.expand) 11585 o.expand(); 11586 }, 'items', t); 11587 } 11588 11589 t.collapsed = false; 11590 }, 11591 11592 collapse : function(d) { 11593 var t = this; 11594 11595 if (d) { 11596 walk(t, function(o) { 11597 if (o.collapse) 11598 o.collapse(); 11599 }, 'items', t); 11600 } 11601 11602 t.collapsed = true; 11603 }, 11604 11605 isCollapsed : function() { 11606 return this.collapsed; 11607 }, 11608 11609 add : function(o) { 11610 if (!o.settings) 11611 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11612 11613 this.onAddItem.dispatch(this, o); 11614 11615 return this.items[o.id] = o; 11616 }, 11617 11618 addSeparator : function() { 11619 return this.add({separator : true}); 11620 }, 11621 11622 addMenu : function(o) { 11623 if (!o.collapse) 11624 o = this.createMenu(o); 11625 11626 this.menuCount++; 11627 11628 return this.add(o); 11629 }, 11630 11631 hasMenus : function() { 11632 return this.menuCount !== 0; 11633 }, 11634 11635 remove : function(o) { 11636 delete this.items[o.id]; 11637 }, 11638 11639 removeAll : function() { 11640 var t = this; 11641 11642 walk(t, function(o) { 11643 if (o.removeAll) 11644 o.removeAll(); 11645 else 11646 o.remove(); 11647 11648 o.destroy(); 11649 }, 'items', t); 11650 11651 t.items = {}; 11652 }, 11653 11654 createMenu : function(o) { 11655 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11656 11657 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11658 11659 return m; 11660 } 11661 }); 11662})(tinymce); 11663(function(tinymce) { 11664 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11665 11666 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11667 DropMenu : function(id, s) { 11668 s = s || {}; 11669 s.container = s.container || DOM.doc.body; 11670 s.offset_x = s.offset_x || 0; 11671 s.offset_y = s.offset_y || 0; 11672 s.vp_offset_x = s.vp_offset_x || 0; 11673 s.vp_offset_y = s.vp_offset_y || 0; 11674 11675 if (is(s.icons) && !s.icons) 11676 s['class'] += ' mceNoIcons'; 11677 11678 this.parent(id, s); 11679 this.onShowMenu = new tinymce.util.Dispatcher(this); 11680 this.onHideMenu = new tinymce.util.Dispatcher(this); 11681 this.classPrefix = 'mceMenu'; 11682 }, 11683 11684 createMenu : function(s) { 11685 var t = this, cs = t.settings, m; 11686 11687 s.container = s.container || cs.container; 11688 s.parent = t; 11689 s.constrain = s.constrain || cs.constrain; 11690 s['class'] = s['class'] || cs['class']; 11691 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11692 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11693 s.keyboard_focus = cs.keyboard_focus; 11694 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11695 11696 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11697 11698 return m; 11699 }, 11700 11701 focus : function() { 11702 var t = this; 11703 if (t.keyboardNav) {
11704 t.keyboardNav.focus(); 11705 } 11706 }, 11707 11708 update : function() { 11709 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11710 11711 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11712 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11713 11714 if (!DOM.boxModel) 11715 t.element.setStyles({width : tw + 2, height : th + 2}); 11716 else 11717 t.element.setStyles({width : tw, height : th}); 11718 11719 if (s.max_width) 11720 DOM.setStyle(co, 'width', tw); 11721 11722 if (s.max_height) { 11723 DOM.setStyle(co, 'height', th); 11724 11725 if (tb.clientHeight < s.max_height) 11726 DOM.setStyle(co, 'overflow', 'hidden'); 11727 } 11728 }, 11729 11730 showMenu : function(x, y, px) { 11731 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11732 11733 t.collapse(1); 11734 11735 if (t.isMenuVisible) 11736 return; 11737 11738 if (!t.rendered) { 11739 co = DOM.add(t.settings.container, t.renderNode()); 11740 11741 each(t.items, function(o) { 11742 o.postRender(); 11743 }); 11744 11745 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11746 } else 11747 co = DOM.get('menu_' + t.id); 11748 11749 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11750 if (!tinymce.isOpera) 11751 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11752 11753 DOM.show(co); 11754 t.update(); 11755 11756 x += s.offset_x || 0; 11757 y += s.offset_y || 0; 11758 vp.w -= 4; 11759 vp.h -= 4; 11760 11761 // Move inside viewport if not submenu 11762 if (s.constrain) { 11763 w = co.clientWidth - ot; 11764 h = co.clientHeight - ot; 11765 mx = vp.x + vp.w; 11766 my = vp.y + vp.h; 11767 11768 if ((x + s.vp_offset_x + w) > mx) 11769 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11770 11771 if ((y + s.vp_offset_y + h) > my) 11772 y = Math.max(0, (my - s.vp_offset_y) - h); 11773 } 11774 11775 DOM.setStyles(co, {left : x , top : y}); 11776 t.element.update(); 11777 11778 t.isMenuVisible = 1; 11779 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11780 var m; 11781 11782 e = e.target; 11783 11784 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11785 m = t.items[e.id]; 11786 11787 if (m.isDisabled()) 11788 return; 11789 11790 dm = t; 11791 11792 while (dm) { 11793 if (dm.hideMenu) 11794 dm.hideMenu(); 11795 11796 dm = dm.settings.parent; 11797 } 11798 11799 if (m.settings.onclick) 11800 m.settings.onclick(e); 11801 11802 return false; // Cancel to fix onbeforeunload problem 11803 } 11804 }); 11805 11806 if (t.hasMenus()) { 11807 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11808 var m, r, mi; 11809 11810 e = e.target; 11811 if (e && (e = DOM.getParent(e, 'tr'))) { 11812 m = t.items[e.id]; 11813 11814 if (t.lastMenu) 11815 t.lastMenu.collapse(1); 11816 11817 if (m.isDisabled()) 11818 return; 11819 11820 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11821 //p = DOM.getPos(s.container); 11822 r = DOM.getRect(e); 11823 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11824 t.lastMenu = m; 11825 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11826 } 11827 } 11828 }); 11829 } 11830 11831 Event.add(co, 'keydown', t._keyHandler, t); 11832 11833 t.onShowMenu.dispatch(t); 11834 11835 if (s.keyboard_focus) { 11836 t._setupKeyboardNav(); 11837 } 11838 }, 11839 11840 hideMenu : function(c) { 11841 var t = this, co = DOM.get('menu_' + t.id), e; 11842 11843 if (!t.isMenuVisible) 11844 return; 11845 11846 if (t.keyboardNav) t.keyboardNav.destroy(); 11847 Event.remove(co, 'mouseover', t.mouseOverFunc); 11848 Event.remove(co, 'click', t.mouseClickFunc); 11849 Event.remove(co, 'keydown', t._keyHandler); 11850 DOM.hide(co); 11851 t.isMenuVisible = 0; 11852 11853 if (!c) 11854 t.collapse(1); 11855 11856 if (t.element) 11857 t.element.hide(); 11858 11859 if (e = DOM.get(t.id)) 11860 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11861 11862 t.onHideMenu.dispatch(t); 11863 }, 11864 11865 add : function(o) { 11866 var t = this, co; 11867 11868 o = t.parent(o); 11869 11870 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11871 t._add(DOM.select('tbody', co)[0], o); 11872 11873 return o; 11874 }, 11875 11876 collapse : function(d) { 11877 this.parent(d); 11878 this.hideMenu(1); 11879 }, 11880 11881 remove : function(o) { 11882 DOM.remove(o.id); 11883 this.destroy(); 11884 11885 return this.parent(o); 11886 }, 11887 11888 destroy : function() { 11889 var t = this, co = DOM.get('menu_' + t.id); 11890 11891 if (t.keyboardNav) t.keyboardNav.destroy(); 11892 Event.remove(co, 'mouseover', t.mouseOverFunc);
11893 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11894 Event.remove(co, 'click', t.mouseClickFunc); 11895 Event.remove(co, 'keydown', t._keyHandler); 11896 11897 if (t.element) 11898 t.element.remove(); 11899 11900 DOM.remove(co); 11901 }, 11902 11903 renderNode : function() { 11904 var t = this, s = t.settings, n, tb, co, w; 11905 11906 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'}); 11907 if (t.settings.parent) { 11908 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11909 } 11910 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11911 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11912 11913 if (s.menu_line) 11914 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11915 11916// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11917 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11918 tb = DOM.add(n, 'tbody'); 11919 11920 each(t.items, function(o) { 11921 t._add(tb, o); 11922 }); 11923 11924 t.rendered = true; 11925 11926 return w; 11927 }, 11928 11929 // Internal functions 11930 _setupKeyboardNav : function(){ 11931 var contextMenu, menuItems, t=this; 11932 contextMenu = DOM.get('menu_' + t.id); 11933 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11934 menuItems.splice(0,0,contextMenu); 11935 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11936 root: 'menu_' + t.id, 11937 items: menuItems, 11938 onCancel: function() { 11939 t.hideMenu(); 11940 }, 11941 enableUpDown: true 11942 }); 11943 contextMenu.focus(); 11944 }, 11945 11946 _keyHandler : function(evt) { 11947 var t = this, e; 11948 switch (evt.keyCode) { 11949 case 37: // Left 11950 if (t.settings.parent) { 11951 t.hideMenu(); 11952 t.settings.parent.focus(); 11953 Event.cancel(evt); 11954 } 11955 break; 11956 case 39: // Right 11957 if (t.mouseOverFunc) 11958 t.mouseOverFunc(evt); 11959 break; 11960 } 11961 }, 11962 11963 _add : function(tb, o) { 11964 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11965 11966 if (s.separator) { 11967 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11968 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11969 11970 if (n = ro.previousSibling) 11971 DOM.addClass(n, 'mceLast'); 11972 11973 return; 11974 } 11975 11976 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11977 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11978 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11979 11980 if (s.parent) { 11981 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11982 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11983 } 11984 11985 DOM.addClass(it, s['class']); 11986// n = DOM.add(n, 'span', {'class' : 'item'}); 11987 11988 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11989 11990 if (s.icon_src) 11991 DOM.add(ic, 'img', {src : s.icon_src}); 11992 11993 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11994 11995 if (o.settings.style) { 11996 if (typeof o.settings.style == "function") 11997 o.settings.style = o.settings.style(); 11998 11999 DOM.setAttrib(n, 'style', o.settings.style); 12000 } 12001 12002 if (tb.childNodes.length == 1) 12003 DOM.addClass(ro, 'mceFirst'); 12004 12005 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 12006 DOM.addClass(ro, 'mceFirst'); 12007 12008 if (o.collapse) 12009 DOM.addClass(ro, cp + 'ItemSub'); 12010 12011 if (n = ro.previousSibling) 12012 DOM.removeClass(n, 'mceLast'); 12013 12014 DOM.addClass(ro, 'mceLast'); 12015 } 12016 }); 12017})(tinymce); 12018(function(tinymce) { 12019 var DOM = tinymce.DOM; 12020 12021 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 12022 Button : function(id, s, ed) { 12023 this.parent(id, s, ed); 12024 this.classPrefix = 'mceButton'; 12025 }, 12026 12027 renderHTML : function() { 12028 var cp = this.classPrefix, s = this.settings, h, l; 12029 12030 l = DOM.encode(s.label || ''); 12031 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) + '">'; 12032 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 12033 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>' : ''); 12034 else 12035 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 12036 12037 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">
12037' + s.title + '</span>'; 12038 h += '</a>'; 12039 return h; 12040 }, 12041 12042 postRender : function() { 12043 var t = this, s = t.settings, imgBookmark; 12044 12045 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 12046 // need to keep the selection in case the selection is lost 12047 if (tinymce.isIE && t.editor) { 12048 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 12049 var nodeName = t.editor.selection.getNode().nodeName; 12050 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 12051 }); 12052 } 12053 tinymce.dom.Event.add(t.id, 'click', function(e) { 12054 if (!t.isDisabled()) { 12055 // restore the selection in case the selection is lost in IE 12056 if (tinymce.isIE && t.editor && imgBookmark !== null) { 12057 t.editor.selection.moveToBookmark(imgBookmark); 12058 } 12059 return s.onclick.call(s.scope, e); 12060 } 12061 }); 12062 tinymce.dom.Event.add(t.id, 'keydown', function(e) { 12063 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) { 12064 tinymce.dom.Event.cancel(e); 12065 return s.onclick.call(s.scope, e); 12066 } 12067 }); 12068 } 12069 }); 12070})(tinymce); 12071 12072(function(tinymce) { 12073 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 12074 12075 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 12076 ListBox : function(id, s, ed) { 12077 var t = this; 12078 12079 t.parent(id, s, ed); 12080 12081 t.items = []; 12082 12083 t.onChange = new Dispatcher(t); 12084 12085 t.onPostRender = new Dispatcher(t); 12086 12087 t.onAdd = new Dispatcher(t); 12088 12089 t.onRenderMenu = new tinymce.util.Dispatcher(this); 12090 12091 t.classPrefix = 'mceListBox'; 12092 t.marked = {}; 12093 }, 12094 12095 select : function(va) { 12096 var t = this, fv, f; 12097 12098 t.marked = {}; 12099 12100 if (va == undef) 12101 return t.selectByIndex(-1); 12102 12103 // Is string or number make function selector 12104 if (va && typeof(va)=="function") 12105 f = va; 12106 else { 12107 f = function(v) { 12108 return v == va; 12109 }; 12110 } 12111 12112 // Do we need to do something? 12113 if (va != t.selectedValue) { 12114 // Find item 12115 each(t.items, function(o, i) { 12116 if (f(o.value)) { 12117 fv = 1; 12118 t.selectByIndex(i); 12119 return false; 12120 } 12121 }); 12122 12123 if (!fv) 12124 t.selectByIndex(-1); 12125 } 12126 }, 12127 12128 selectByIndex : function(idx) { 12129 var t = this, e, o, label; 12130 12131 t.marked = {}; 12132 12133 if (idx != t.selectedIndex) { 12134 e = DOM.get(t.id + '_text'); 12135 label = DOM.get(t.id + '_voiceDesc'); 12136 o = t.items[idx]; 12137 12138 if (o) { 12139 t.selectedValue = o.value; 12140 t.selectedIndex = idx; 12141 DOM.setHTML(e, DOM.encode(o.title)); 12142 DOM.setHTML(label, t.settings.title + " - " + o.title); 12143 DOM.removeClass(e, 'mceTitle'); 12144 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 12145 } else { 12146 DOM.setHTML(e, DOM.encode(t.settings.title)); 12147 DOM.setHTML(label, DOM.encode(t.settings.title)); 12148 DOM.addClass(e, 'mceTitle'); 12149 t.selectedValue = t.selectedIndex = null; 12150 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 12151 } 12152 e = 0; 12153 } 12154 }, 12155 12156 mark : function(value) { 12157 this.marked[value] = true; 12158 }, 12159 12160 add : function(n, v, o) { 12161 var t = this; 12162 12163 o = o || {}; 12164 o = tinymce.extend(o, { 12165 title : n, 12166 value : v 12167 }); 12168 12169 t.items.push(o); 12170 t.onAdd.dispatch(t, o); 12171 }, 12172 12173 getLength : function() { 12174 return this.items.length; 12175 }, 12176 12177 renderHTML : function() { 12178 var h = '', t = this, s = t.settings, cp = t.classPrefix; 12179
12180 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>'; 12181 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 12182 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>'; 12183 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>'; 12184 h += '</tr></tbody></table></span>'; 12185 12186 return h; 12187 }, 12188 12189 showMenu : function() { 12190 var t = this, p2, e = DOM.get(this.id), m; 12191 12192 if (t.isDisabled() || t.items.length === 0) 12193 return; 12194 12195 if (t.menu && t.menu.isMenuVisible) 12196 return t.hideMenu(); 12197 12198 if (!t.isMenuRendered) { 12199 t.renderMenu(); 12200 t.isMenuRendered = true; 12201 } 12202 12203 p2 = DOM.getPos(e); 12204 12205 m = t.menu; 12206 m.settings.offset_x = p2.x; 12207 m.settings.offset_y = p2.y; 12208 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 12209 12210 // Select in menu 12211 each(t.items, function(o) { 12212 if (m.items[o.id]) { 12213 m.items[o.id].setSelected(0); 12214 } 12215 }); 12216 12217 each(t.items, function(o) { 12218 if (m.items[o.id] && t.marked[o.value]) { 12219 m.items[o.id].setSelected(1); 12220 } 12221 12222 if (o.value === t.selectedValue) { 12223 m.items[o.id].setSelected(1); 12224 } 12225 }); 12226 12227 m.showMenu(0, e.clientHeight); 12228 12229 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12230 DOM.addClass(t.id, t.classPrefix + 'Selected'); 12231 12232 //DOM.get(t.id + '_text').focus(); 12233 }, 12234 12235 hideMenu : function(e) { 12236 var t = this; 12237 12238 if (t.menu && t.menu.isMenuVisible) { 12239 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12240 12241 // Prevent double toogles by canceling the mouse click event to the button 12242 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 12243 return; 12244 12245 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12246 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 12247 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12248 t.menu.hideMenu(); 12249 } 12250 } 12251 }, 12252 12253 renderMenu : function() { 12254 var t = this, m; 12255 12256 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12257 menu_line : 1, 12258 'class' : t.classPrefix + 'Menu mceNoIcons', 12259 max_width : 250, 12260 max_height : 150 12261 }); 12262 12263 m.onHideMenu.add(function() { 12264 t.hideMenu(); 12265 t.focus(); 12266 }); 12267 12268 m.add({ 12269 title : t.settings.title, 12270 'class' : 'mceMenuItemTitle', 12271 onclick : function() { 12272 if (t.settings.onselect('') !== false) 12273 t.select(''); // Must be runned after 12274 } 12275 }); 12276 12277 each(t.items, function(o) { 12278 // No value then treat it as a title 12279 if (o.value === undef) { 12280 m.add({ 12281 title : o.title, 12282 role : "option", 12283 'class' : 'mceMenuItemTitle', 12284 onclick : function() { 12285 if (t.settings.onselect('') !== false) 12286 t.select(''); // Must be runned after 12287 } 12288 }); 12289 } else { 12290 o.id = DOM.uniqueId(); 12291 o.role= "option"; 12292 o.onclick = function() { 12293 if (t.settings.onselect(o.value) !== false) 12294 t.select(o.value); // Must be runned after 12295 }; 12296 12297 m.add(o); 12298 } 12299 }); 12300 12301 t.onRenderMenu.dispatch(t, m); 12302 t.menu = m; 12303 }, 12304 12305 postRender : function() { 12306 var t = this, cp = t.classPrefix; 12307 12308 Event.add(t.id, 'click', t.showMenu, t); 12309 Event.add(t.id, 'keydown', function(evt) { 12310 if (evt.keyCode == 32) { // Space 12311 t.showMenu(evt); 12312 Event.cancel(evt); 12313 } 12314 }); 12315 Event.add(t.id, 'focus', function() { 12316 if (!t._focused) { 12317 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 12318 if (e.keyCode == 40) { 12319 t.showMenu(); 12320 Event.cancel(e); 12321 } 12322 }); 12323 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 12324 var v; 12325 if (e.keyCode == 13) { 12326 // Fake select on enter 12327 v = t.selectedValue; 12328 t.selectedValue = null; // Needs to be null to fake change 12329 Event.cancel(e); 12330 t.settings.onselect(v); 12331 } 12332 }); 12333 } 12334 12335 t._focused = 1; 12336 }); 12337 Event.add(t.id, 'blur', function() { 12338 Event.remove(t.id, 'keydown', t.keyDownHandler); 12339 Event.remove(t.id, 'keypress', t.keyPressHandler); 12340 t._focused = 0; 12341 }); 12342 12343 // Old IE doesn't have hover on all elements 12344 if (tinymce.isIE6 || !DOM.boxModel) { 12345 Event.add(t.id, 'mouseover', function() { 12346 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12347 DOM.addClass(t.id, cp + 'Hover'); 12348 }); 12349 12350 Event.add(t.id, 'mouseout', function() { 12351 if (!DOM.hasClass(t.id, cp + 'Disabled')) 12352 DOM.removeClass(t.id, cp + 'Hover'); 12353 }); 12354 } 12355 12356 t.onPostRender.dispatch(t, DOM.get(t.id)); 12357 }, 12358 12359 destroy : function() { 12360 this.parent(); 12361 12362 Event.clear(this.id + '_text'); 12363 Event.clear(this.id + '_open'); 12364 } 12365 }); 12366})(tinymce); 12367 12368(function(tinymce) { 12369 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 12370 12371 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 12372 NativeListBox : function(id, s) { 12373 this.parent(id, s); 12374 this.classPrefix = 'mceNativeListBox'; 12375 }, 12376 12377 setDisabled : function(s) {
12378 DOM.get(this.id).disabled = s; 12379 this.setAriaProperty('disabled', s); 12380 }, 12381 12382 isDisabled : function() { 12383 return DOM.get(this.id).disabled; 12384 }, 12385 12386 select : function(va) { 12387 var t = this, fv, f; 12388 12389 if (va == undef) 12390 return t.selectByIndex(-1); 12391 12392 // Is string or number make function selector 12393 if (va && typeof(va)=="function") 12394 f = va; 12395 else { 12396 f = function(v) { 12397 return v == va; 12398 }; 12399 } 12400 12401 // Do we need to do something? 12402 if (va != t.selectedValue) { 12403 // Find item 12404 each(t.items, function(o, i) { 12405 if (f(o.value)) { 12406 fv = 1; 12407 t.selectByIndex(i); 12408 return false; 12409 } 12410 }); 12411 12412 if (!fv) 12413 t.selectByIndex(-1); 12414 } 12415 }, 12416 12417 selectByIndex : function(idx) { 12418 DOM.get(this.id).selectedIndex = idx + 1; 12419 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12420 }, 12421 12422 add : function(n, v, a) { 12423 var o, t = this; 12424 12425 a = a || {}; 12426 a.value = v; 12427 12428 if (t.isRendered()) 12429 DOM.add(DOM.get(this.id), 'option', a, n); 12430 12431 o = { 12432 title : n, 12433 value : v, 12434 attribs : a 12435 }; 12436 12437 t.items.push(o); 12438 t.onAdd.dispatch(t, o); 12439 }, 12440 12441 getLength : function() { 12442 return this.items.length; 12443 }, 12444 12445 renderHTML : function() { 12446 var h, t = this; 12447 12448 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12449 12450 each(t.items, function(it) { 12451 h += DOM.createHTML('option', {value : it.value}, it.title); 12452 }); 12453 12454 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12455 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12456 return h; 12457 }, 12458 12459 postRender : function() { 12460 var t = this, ch, changeListenerAdded = true; 12461 12462 t.rendered = true; 12463 12464 function onChange(e) { 12465 var v = t.items[e.target.selectedIndex - 1]; 12466 12467 if (v && (v = v.value)) { 12468 t.onChange.dispatch(t, v); 12469 12470 if (t.settings.onselect) 12471 t.settings.onselect(v); 12472 } 12473 }; 12474 12475 Event.add(t.id, 'change', onChange); 12476 12477 // Accessibility keyhandler 12478 Event.add(t.id, 'keydown', function(e) { 12479 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; 12480 12481 Event.remove(t.id, 'change', ch); 12482 changeListenerAdded = false; 12483 12484 bf = Event.add(t.id, 'blur', function() { 12485 if (changeListenerAdded) return; 12486 changeListenerAdded = true; 12487 Event.add(t.id, 'change', onChange); 12488 Event.remove(t.id, 'blur', bf); 12489 }); 12490 12491 if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) { 12492 onChange(e); 12493 return Event.cancel(e); 12494 } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) { 12495 // allow native implementation (navigate select element options) 12496 e.stopImmediatePropagation(); 12497 } 12498 }); 12499 12500 t.onPostRender.dispatch(t, DOM.get(t.id)); 12501 } 12502 }); 12503})(tinymce); 12504 12505(function(tinymce) { 12506 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12507 12508 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12509 MenuButton : function(id, s, ed) { 12510 this.parent(id, s, ed); 12511 12512 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12513 12514 s.menu_container = s.menu_container || DOM.doc.body; 12515 }, 12516 12517 showMenu : function() { 12518 var t = this, p1, p2, e = DOM.get(t.id), m; 12519 12520 if (t.isDisabled()) 12521 return; 12522 12523 if (!t.isMenuRendered) { 12524 t.renderMenu(); 12525 t.isMenuRendered = true; 12526 } 12527 12528 if (t.isMenuVisible) 12529 return t.hideMenu(); 12530 12531 p1 = DOM.getPos(t.settings.menu_container); 12532 p2 = DOM.getPos(e); 12533 12534 m = t.menu; 12535 m.settings.offset_x = p2.x; 12536 m.settings.offset_y = p2.y; 12537 m.settings.vp_offset_x = p2.x; 12538 m.settings.vp_offset_y = p2.y; 12539 m.settings.keyboard_focus = t._focused; 12540 m.showMenu(0, e.firstChild.clientHeight); 12541 12542 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12543 t.setState('Selected', 1); 12544 12545 t.isMenuVisible = 1; 12546 }, 12547 12548 renderMenu : function() { 12549 var t = this, m; 12550 12551 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12552 menu_line : 1, 12553 'class' : this.classPrefix + 'Menu', 12554 icons : t.settings.icons 12555 }); 12556 12557 m.onHideMenu.add(function() { 12558 t.hideMenu(); 12559 t.focus(); 12560 }); 12561 12562 t.onRenderMenu.dispatch(t, m); 12563 t.menu = m; 12564 }, 12565 12566 hideMenu : function(e) { 12567 var t = this; 12568
12569 // Prevent double toogles by canceling the mouse click event to the button 12570 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12571 return; 12572 12573 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12574 t.setState('Selected', 0); 12575 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12576 if (t.menu) 12577 t.menu.hideMenu(); 12578 } 12579 12580 t.isMenuVisible = 0; 12581 }, 12582 12583 postRender : function() { 12584 var t = this, s = t.settings; 12585 12586 Event.add(t.id, 'click', function() { 12587 if (!t.isDisabled()) { 12588 if (s.onclick) 12589 s.onclick(t.value); 12590 12591 t.showMenu(); 12592 } 12593 }); 12594 } 12595 }); 12596})(tinymce); 12597 12598(function(tinymce) { 12599 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12600 12601 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12602 SplitButton : function(id, s, ed) { 12603 this.parent(id, s, ed); 12604 this.classPrefix = 'mceSplitButton'; 12605 }, 12606 12607 renderHTML : function() { 12608 var h, t = this, s = t.settings, h1; 12609 12610 h = '<tbody><tr>'; 12611 12612 if (s.image) 12613 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12614 else 12615 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12616 12617 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12618 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>'; 12619 12620 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12621 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>'; 12622 12623 h += '</tr></tbody>'; 12624 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12625 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12626 }, 12627 12628 postRender : function() { 12629 var t = this, s = t.settings, activate; 12630 12631 if (s.onclick) { 12632 activate = function(evt) { 12633 if (!t.isDisabled()) { 12634 s.onclick(t.value); 12635 Event.cancel(evt); 12636 } 12637 }; 12638 Event.add(t.id + '_action', 'click', activate); 12639 Event.add(t.id, ['click', 'keydown'], function(evt) { 12640 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12641 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12642 activate(); 12643 Event.cancel(evt); 12644 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12645 t.showMenu(); 12646 Event.cancel(evt); 12647 } 12648 }); 12649 } 12650 12651 Event.add(t.id + '_open', 'click', function (evt) { 12652 t.showMenu(); 12653 Event.cancel(evt); 12654 }); 12655 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12656 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12657 12658 // Old IE doesn't have hover on all elements 12659 if (tinymce.isIE6 || !DOM.boxModel) { 12660 Event.add(t.id, 'mouseover', function() { 12661 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12662 DOM.addClass(t.id, 'mceSplitButtonHover'); 12663 }); 12664 12665 Event.add(t.id, 'mouseout', function() { 12666 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12667 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12668 }); 12669 } 12670 }, 12671 12672 destroy : function() { 12673 this.parent(); 12674 12675 Event.clear(this.id + '_action'); 12676 Event.clear(this.id + '_open'); 12677 Event.clear(this.id); 12678 } 12679 }); 12680})(tinymce); 12681 12682(function(tinymce) { 12683 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12684 12685 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12686 ColorSplitButton : function(id, s, ed) { 12687 var t = this; 12688 12689 t.parent(id, s, ed); 12690 12691 t.settings = s = tinymce.extend({ 12692 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', 12693 grid_width : 8, 12694 default_color : '#888888' 12695 }, t.settings); 12696 12697 t.onShowMenu = new tinymce.util.Dispatcher(t); 12698 12699 t.onHideMenu = new tinymce.util.Dispatcher(t); 12700 12701 t.value = s.default_color; 12702 }, 12703 12704 showMenu : function() { 12705 var t = this, r, p, e, p2; 12706 12707 if (t.isDisabled()) 12708 return; 12709 12710 if (!t.isMenuRendered) { 12711 t.renderMenu(); 12712 t.isMenuRendered = true; 12713 } 12714 12715 if (t.isMenuVisible) 12716 return t.hideMenu(); 12717 12718 e = DOM.get(t.id);
12719 DOM.show(t.id + '_menu'); 12720 DOM.addClass(e, 'mceSplitButtonSelected'); 12721 p2 = DOM.getPos(e); 12722 DOM.setStyles(t.id + '_menu', { 12723 left : p2.x, 12724 top : p2.y + e.firstChild.clientHeight, 12725 zIndex : 200000 12726 }); 12727 e = 0; 12728 12729 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12730 t.onShowMenu.dispatch(t); 12731 12732 if (t._focused) { 12733 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12734 if (e.keyCode == 27) 12735 t.hideMenu(); 12736 }); 12737 12738 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12739 } 12740 12741 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12742 root: t.id + '_menu', 12743 items: DOM.select('a', t.id + '_menu'), 12744 onCancel: function() { 12745 t.hideMenu(); 12746 t.focus(); 12747 } 12748 }); 12749 12750 t.keyboardNav.focus(); 12751 t.isMenuVisible = 1; 12752 }, 12753 12754 hideMenu : function(e) { 12755 var t = this; 12756 12757 if (t.isMenuVisible) { 12758 // Prevent double toogles by canceling the mouse click event to the button 12759 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12760 return; 12761 12762 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12763 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12764 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12765 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12766 DOM.hide(t.id + '_menu'); 12767 } 12768 12769 t.isMenuVisible = 0; 12770 t.onHideMenu.dispatch(); 12771 t.keyboardNav.destroy(); 12772 } 12773 }, 12774 12775 renderMenu : function() { 12776 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12777 12778 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;'}); 12779 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12780 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12781 12782 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12783 tb = DOM.add(n, 'tbody'); 12784 12785 // Generate color grid 12786 i = 0; 12787 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12788 c = c.replace(/^#/, ''); 12789 12790 if (!i--) { 12791 tr = DOM.add(tb, 'tr'); 12792 i = s.grid_width - 1; 12793 } 12794 12795 n = DOM.add(tr, 'td'); 12796 var settings = { 12797 href : 'javascript:;', 12798 style : { 12799 backgroundColor : '#' + c 12800 }, 12801 'title': t.editor.getLang('colors.' + c, c), 12802 'data-mce-color' : '#' + c 12803 }; 12804 12805 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12806 if (!tinymce.isIE ) { 12807 settings.role = 'option'; 12808 } 12809 12810 n = DOM.add(n, 'a', settings); 12811 12812 if (t.editor.forcedHighContrastMode) { 12813 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12814 if (n.getContext && (context = n.getContext("2d"))) { 12815 context.fillStyle = '#' + c; 12816 context.fillRect(0, 0, 16, 16); 12817 } else { 12818 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12819 DOM.remove(n); 12820 } 12821 } 12822 }); 12823 12824 if (s.more_colors_func) { 12825 n = DOM.add(tb, 'tr'); 12826 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12827 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12828 12829 Event.add(n, 'click', function(e) { 12830 s.more_colors_func.call(s.more_colors_scope || this); 12831 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12832 }); 12833 } 12834 12835 DOM.addClass(m, 'mceColorSplitMenu'); 12836 12837 // Prevent IE from scrolling and hindering click to occur #4019 12838 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12839 12840 Event.add(t.id + '_menu', 'click', function(e) { 12841 var c; 12842 12843 e = DOM.getParent(e.target, 'a', tb); 12844 12845 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12846 t.setColor(c); 12847 12848 return false; // Prevent IE auto save warning 12849 }); 12850 12851 return w; 12852 }, 12853 12854 setColor : function(c) { 12855 this.displayColor(c); 12856 this.hideMenu(); 12857 this.settings.onselect(c); 12858 }, 12859 12860 displayColor : function(c) { 12861 var t = this; 12862 12863 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12864 12865 t.value = c; 12866 }, 12867 12868 postRender : function() { 12869 var t = this, id = t.id; 12870 12871 t.parent(); 12872 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12873 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12874 }, 12875 12876 destroy : function() { 12877 var self = this; 12878 12879 self.parent(); 12880 12881 Event.clear(self.id + '_menu'); 12882 Event.clear(self.id + '_more'); 12883 DOM.remove(self.id + '_menu'); 12884 12885 if (self.keyboardNav) { 12886 self.keyboardNav.destroy(); 12887 } 12888 } 12889 }); 12890})(tinymce); 12891 12892(function(tinymce) {
12893// Shorten class names 12894var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12895tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12896 renderHTML : function() { 12897 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12898 12899 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12900 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12901 h.push("<span role='application'>"); 12902 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12903 each(controls, function(toolbar) { 12904 h.push(toolbar.renderHTML()); 12905 }); 12906 h.push("</span>"); 12907 h.push('</div>'); 12908 12909 return h.join(''); 12910 }, 12911 12912 focus : function() { 12913 var t = this; 12914 dom.get(t.id).focus(); 12915 }, 12916 12917 postRender : function() { 12918 var t = this, items = []; 12919 12920 each(t.controls, function(toolbar) { 12921 each (toolbar.controls, function(control) { 12922 if (control.id) { 12923 items.push(control); 12924 } 12925 }); 12926 }); 12927 12928 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12929 root: t.id, 12930 items: items, 12931 onCancel: function() { 12932 //Move focus if webkit so that navigation back will read the item. 12933 if (tinymce.isWebKit) { 12934 dom.get(t.editor.id+"_ifr").focus(); 12935 } 12936 t.editor.focus(); 12937 }, 12938 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12939 }); 12940 }, 12941 12942 destroy : function() { 12943 var self = this; 12944 12945 self.parent(); 12946 self.keyNav.destroy(); 12947 Event.clear(self.id); 12948 } 12949}); 12950})(tinymce); 12951 12952(function(tinymce) { 12953// Shorten class names 12954var dom = tinymce.DOM, each = tinymce.each; 12955tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12956 renderHTML : function() { 12957 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12958 12959 cl = t.controls; 12960 for (i=0; i<cl.length; i++) { 12961 // Get current control, prev control, next control and if the control is a list box or not 12962 co = cl[i]; 12963 pr = cl[i - 1]; 12964 nx = cl[i + 1]; 12965 12966 // Add toolbar start 12967 if (i === 0) { 12968 c = 'mceToolbarStart'; 12969 12970 if (co.Button) 12971 c += ' mceToolbarStartButton'; 12972 else if (co.SplitButton) 12973 c += ' mceToolbarStartSplitButton'; 12974 else if (co.ListBox) 12975 c += ' mceToolbarStartListBox'; 12976 12977 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12978 } 12979 12980 // Add toolbar end before list box and after the previous button 12981 // This is to fix the o2k7 editor skins 12982 if (pr && co.ListBox) { 12983 if (pr.Button || pr.SplitButton) 12984 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12985 } 12986 12987 // Render control HTML 12988 12989 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12990 if (dom.stdMode) 12991 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12992 else 12993 h += '<td>' + co.renderHTML() + '</td>'; 12994 12995 // Add toolbar start after list box and before the next button 12996 // This is to fix the o2k7 editor skins 12997 if (nx && co.ListBox) { 12998 if (nx.Button || nx.SplitButton) 12999 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 13000 } 13001 } 13002 13003 c = 'mceToolbarEnd'; 13004 13005 if (co.Button) 13006 c += ' mceToolbarEndButton'; 13007 else if (co.SplitButton) 13008 c += ' mceToolbarEndSplitButton'; 13009 else if (co.ListBox) 13010 c += ' mceToolbarEndListBox'; 13011 13012 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 13013 13014 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>'); 13015 } 13016}); 13017})(tinymce); 13018 13019(function(tinymce) { 13020 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 13021 13022 tinymce.create('tinymce.AddOnManager', { 13023 AddOnManager : function() { 13024 var self = this; 13025 13026 self.items = []; 13027 self.urls = {}; 13028 self.lookup = {}; 13029 self.onAdd = new Dispatcher(self); 13030 }, 13031 13032 get : function(n) { 13033 if (this.lookup[n]) { 13034 return this.lookup[n].instance; 13035 } else { 13036 return undefined; 13037 } 13038 }, 13039 13040 dependencies : function(n) { 13041 var result; 13042 if (this.lookup[n]) { 13043 result = this.lookup[n].dependencies; 13044 } 13045 return result || []; 13046 }, 13047 13048 requireLangPack : function(n) { 13049 var s = tinymce.settings; 13050 13051 if (s && s.language && s.language_load !== false) 13052 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 13053 }, 13054 13055 add : function(id, o, dependencies) { 13056 this.items.push(o); 13057 this.lookup[id] = {instance:o, dependencies:dependencies}; 13058 this.onAdd.dispatch(this, id, o); 13059 13060 return o; 13061 }, 13062 createUrl: function(baseUrl, dep) { 13063 if (typeof dep === "object") { 13064 return dep 13065 } else { 13066 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 13067 } 13068 }, 13069 13070 addComponents: function(pluginName, scripts) { 13071 var pluginUrl = this.urls[pluginName]; 13072 tinymce.each(scripts, function(script){ 13073 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 13074 }); 13075 }, 13076 13077 load : function(n, u, cb, s) { 13078 var t = this, url = u; 13079 13080 function loadDependencies() { 13081 var dependencies = t.dependencies(n); 13082 tinymce.each(dependencies, function(dep) { 13083 var newUrl = t.createUrl(u, dep); 13084 t.load(newUrl.resource, newUrl, undefined, undefined); 13085 }); 13086 if (cb) { 13087 if (s) { 13088 cb.call(s); 13089 } else { 13090 cb.call(tinymce.ScriptLoader); 13091 } 13092 } 13093 } 13094 13095 if (t.urls[n]) 13096 return; 13097 if (typeof u === "object") 13098 url = u.prefix + u.resource + u.suffix; 13099 13100 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 13101 url = tinymce.baseURL + '/' + url; 13102 13103 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 13104 13105 if (t.lookup[n]) { 13106 loadDependencies(); 13107 } else { 13108 tinymce.ScriptLoader.add(url, loadDependencies, s); 13109 } 13110 } 13111 }); 13112 13113 // Create plugin and theme managers 13114 tinymce.PluginManager = new tinymce.AddOnManager(); 13115 tinymce.ThemeManager = new tinymce.AddOnManager(); 13116}(tinymce)); 13117 13118(function(tinymce) { 13119 // Shorten names 13120 var each = tinymce.each, extend = tinymce.extend, 13121 DOM = tinymce.DOM, Event = tinymce.dom.Event, 13122 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13123 explode = tinymce.explode, 13124 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 13125 13126 // Setup some URLs where the editor API is located and where the document is 13127 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 13128 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 13129 tinymce.documentBaseURL += '/'; 13130 13131 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 13132 13133 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 13134 13135 // Add before unload listener 13136 // This was required since IE was leaking memory if you added and removed beforeunload listeners 13137 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 13138 tinymce.onBeforeUnload = new Dispatcher(tinymce); 13139 13140 // Must be on window or IE will leak if the editor is placed in frame or iframe 13141 Event.add(window, 'beforeunload', function(e) { 13142 tinymce.onBeforeUnload.dispatch(tinymce, e); 13143 }); 13144 13145 tinymce.onAddEditor = new Dispatcher(tinymce); 13146 13147 tinymce.onRemoveEditor = new Dispatcher(tinymce); 13148 13149 tinymce.EditorManager = extend(tinymce, { 13150 editors : [], 13151 13152 i18n : {}, 13153 13154 activeEditor : null, 13155 13156 init : function(s) { 13157 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 13158 13159 function createId(elm) { 13160 var id = elm.id; 13161 13162 // Use element id, or unique name or generate a unique id 13163 if (!id) { 13164 id = elm.name; 13165 13166 if (id && !DOM.get(id)) { 13167 id = elm.name; 13168 } else { 13169 // Generate unique name 13170 id = DOM.uniqueId(); 13171 } 13172 13173 elm.setAttribute('id', id); 13174 } 13175 13176 return id; 13177 }; 13178 13179 function execCallback(se, n, s) { 13180 var f = se[n]; 13181 13182 if (!f) 13183 return; 13184 13185 if (tinymce.is(f, 'string')) { 13186 s = f.replace(/\.\w+$/, ''); 13187 s = s ? tinymce.resolve(s) : 0; 13188 f = tinymce.resolve(f); 13189 } 13190 13191 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 13192 }; 13193 13194 function hasClass(n, c) { 13195 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 13196 }; 13197 13198 t.settings = s; 13199 13200 // Legacy call 13201 Event.bind(window, 'ready', function() { 13202 var l, co; 13203 13204 execCallback(s, 'onpageload'); 13205 13206 switch (s.mode) { 13207 case "exact": 13208 l = s.elements || ''; 13209 13210 if(l.length > 0) { 13211 each(explode(l), function(v) { 13212 if (DOM.get(v)) { 13213 ed = new tinymce.Editor(v, s); 13214 el.push(ed); 13215 ed.render(1); 13216 } else { 13217 each(document.forms, function(f) { 13218 each(f.elements, function(e) { 13219 if (e.name === v) { 13220 v = 'mce_editor_' + instanceCounter++; 13221 DOM.setAttrib(e, 'id', v); 13222 13223 ed = new tinymce.Editor(v, s); 13224 el.push(ed); 13225 ed.render(1); 13226 } 13227 }); 13228 }); 13229 } 13230 }); 13231 } 13232 break; 13233 13234 case "textareas": 13235 case "specific_textareas": 13236 each(DOM.select('textarea'), function(elm) { 13237 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 13238 return; 13239 13240 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 13241 ed = new tinymce.Editor(createId(elm), s); 13242 el.push(ed); 13243 ed.render(1); 13244 } 13245 }); 13246 break; 13247 13248 default: 13249 if (s.types) { 13250 // Process type specific selector 13251 each(s.types, function(type) { 13252 each(DOM.select(type.selector), function(elm) { 13253 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 13254 el.push(editor); 13255 editor.render(1); 13256 }); 13257 }); 13258 } else if (s.selector) { 13259 // Process global selector 13260 each(DOM.select(s.selector), function(elm) { 13261 var editor = new tinymce.Editor(createId(elm), s); 13262 el.push(editor); 13263 editor.render(1); 13264 }); 13265 } 13266 } 13267 13268 // Call onInit when all editors are initialized 13269 if (s.oninit) { 13270 l = co = 0; 13271 13272 each(el, function(ed) { 13273 co++; 13274 13275 if (!ed.initialized) { 13276 // Wait for it 13277 ed.onInit.add(function() { 13278 l++; 13279 13280 // All done 13281 if (l == co) 13282 execCallback(s, 'oninit'); 13283 }); 13284 } else 13285 l++; 13286 13287 // All done 13288 if (l == co) 13289 execCallback(s, 'oninit'); 13290 }); 13291 } 13292 }); 13293 }, 13294 13295 get : function(id) { 13296 if (id === undef) 13297 return this.editors; 13298 13299 if (!this.editors.hasOwnProperty(id)) 13300 return undef; 13301 13302 return this.editors[id]; 13303 }, 13304 13305 getInstanceById : function(id) { 13306 return this.get(id); 13307 }, 13308 13309 add : function(editor) { 13310 var self = this, editors = self.editors; 13311 13312 // Add named and index editor instance 13313 editors[editor.id] = editor; 13314 editors.push(editor); 13315 13316 self._setActive(editor); 13317 self.onAddEditor.dispatch(self, editor); 13318 13319 13320 return editor; 13321 }, 13322 13323 remove : function(editor) { 13324 var t = this, i, editors = t.editors; 13325 13326 // Not in the collection 13327 if (!editors[editor.id]) 13328 return null; 13329 13330 delete editors[editor.id]; 13331 13332 for (i = 0; i < editors.length; i++) { 13333 if (editors[i] == editor) { 13334 editors.splice(i, 1); 13335 break; 13336 } 13337 } 13338 13339 // Select another editor since the active one was removed 13340 if (t.activeEditor == editor) 13341 t._setActive(editors[0]); 13342 13343 editor.destroy(); 13344 t.onRemoveEditor.dispatch(t, editor); 13345 13346 return editor; 13347 }, 13348 13349 execCommand : function(c, u, v) { 13350 var t = this, ed = t.get(v), w; 13351 13352 function clr() { 13353 ed.destroy(); 13354 w.detachEvent('onunload', clr); 13355 w = w.tinyMCE = w.tinymce = null; // IE leak 13356 }; 13357 13358 // Manager commands 13359 switch (c) { 13360 case "mceFocus": 13361 ed.focus(); 13362 return true; 13363 13364 case "mceAddEditor": 13365 case "mceAddControl": 13366 if (!t.get(v)) 13367 new tinymce.Editor(v, t.settings).render(); 13368 13369 return true; 13370 13371 case "mceAddFrameControl": 13372 w = v.window; 13373 13374 // Add tinyMCE global instance and tinymce namespace to specified window 13375 w.tinyMCE = tinyMCE; 13376 w.tinymce = tinymce; 13377 13378 tinymce.DOM.doc = w.document; 13379 tinymce.DOM.win = w; 13380 13381 ed = new tinymce.Editor(v.element_id, v); 13382 ed.render(); 13383 13384 // Fix IE memory leaks 13385 if (tinymce.isIE && ! tinymce.isIE11) { 13386 w.attachEvent('onunload', clr); 13387 } 13388 13389 v.page_window = null; 13390 13391 return true; 13392 13393 case "mceRemoveEditor": 13394 case "mceRemoveControl": 13395 if (ed) 13396 ed.remove(); 13397 13398 return true; 13399 13400 case 'mceToggleEditor': 13401 if (!ed) { 13402 t.execCommand('mceAddControl', 0, v); 13403 return true; 13404 } 13405 13406 if (ed.isHidden()) 13407 ed.show(); 13408 else 13409 ed.hide(); 13410 13411 return true; 13412 } 13413 13414 // Run command on active editor 13415 if (t.activeEditor) 13416 return t.activeEditor.execCommand(c, u, v); 13417 13418 return false;
13419 }, 13420 13421 execInstanceCommand : function(id, c, u, v) { 13422 var ed = this.get(id); 13423 13424 if (ed) 13425 return ed.execCommand(c, u, v); 13426 13427 return false; 13428 }, 13429 13430 triggerSave : function() { 13431 each(this.editors, function(e) { 13432 e.save(); 13433 }); 13434 }, 13435 13436 addI18n : function(p, o) { 13437 var lo, i18n = this.i18n; 13438 13439 if (!tinymce.is(p, 'string')) { 13440 each(p, function(o, lc) { 13441 each(o, function(o, g) { 13442 each(o, function(o, k) { 13443 if (g === 'common') 13444 i18n[lc + '.' + k] = o; 13445 else 13446 i18n[lc + '.' + g + '.' + k] = o; 13447 }); 13448 }); 13449 }); 13450 } else { 13451 each(o, function(o, k) { 13452 i18n[p + '.' + k] = o; 13453 }); 13454 } 13455 }, 13456 13457 // Private methods 13458 13459 _setActive : function(editor) { 13460 this.selectedInstance = this.activeEditor = editor; 13461 } 13462 }); 13463})(tinymce); 13464 13465(function(tinymce) { 13466 // Shorten these names 13467 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13468 each = tinymce.each, isGecko = tinymce.isGecko, 13469 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13470 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13471 explode = tinymce.explode; 13472 13473 tinymce.create('tinymce.Editor', { 13474 Editor : function(id, settings) { 13475 var self = this, TRUE = true; 13476 13477 self.settings = settings = extend({ 13478 id : id, 13479 language : 'en', 13480 theme : 'advanced', 13481 skin : 'default', 13482 delta_width : 0, 13483 delta_height : 0, 13484 popup_css : '', 13485 plugins : '', 13486 document_base_url : tinymce.documentBaseURL, 13487 add_form_submit_trigger : TRUE, 13488 submit_patch : TRUE, 13489 add_unload_trigger : TRUE, 13490 convert_urls : TRUE, 13491 relative_urls : TRUE, 13492 remove_script_host : TRUE, 13493 table_inline_editing : false, 13494 object_resizing : TRUE, 13495 accessibility_focus : TRUE, 13496 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13497 visual : TRUE, 13498 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13499 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 13500 apply_source_formatting : TRUE, 13501 directionality : 'ltr', 13502 forced_root_block : 'p', 13503 hidden_input : TRUE, 13504 padd_empty_editor : TRUE, 13505 render_ui : TRUE, 13506 indentation : '30px', 13507 fix_table_elements : TRUE, 13508 inline_styles : TRUE, 13509 convert_fonts_to_spans : TRUE, 13510 indent : 'simple', 13511 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', 13512 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', 13513 validate : TRUE, 13514 entity_encoding : 'named', 13515 url_converter : self.convertURL, 13516 url_converter_scope : self, 13517 ie7_compat : TRUE 13518 }, settings); 13519 13520 self.id = self.editorId = id; 13521 13522 self.isNotDirty = false; 13523 13524 self.plugins = {}; 13525 13526 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13527 base_uri : tinyMCE.baseURI 13528 }); 13529 13530 self.baseURI = tinymce.baseURI; 13531 13532 self.contentCSS = []; 13533 13534 self.contentStyles = []; 13535 13536 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13537 self.setupEvents(); 13538 13539 // Internal command handler objects 13540 self.execCommands = {}; 13541 self.queryStateCommands = {}; 13542 self.queryValueCommands = {}; 13543 13544 // Call setup 13545 self.execCallback('setup', self); 13546 }, 13547 13548 render : function(nst) { 13549 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13550 13551 // Page is not loaded yet, wait for it 13552 if (!Event.domLoaded) { 13553 Event.add(window, 'ready', function() { 13554 t.render(); 13555 }); 13556 return; 13557 } 13558 13559 tinyMCE.settings = s; 13560 13561 // Element not found, then skip initialization 13562 if (!t.getElement()) 13563 return; 13564 13565 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13566 // here since the browser says it has contentEditable support but there is no visible caret. 13567 if (tinymce.isIDevice && !tinymce.isIOS5) 13568 return; 13569 13570 // Add hidden input for non input elements inside form elements 13571 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13572 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13573 13574 // Hide target element early to prevent content flashing 13575 if (!s.content_editable) { 13576 t.orgVisibility = t.getElement().style.visibility; 13577 t.getElement().style.visibility = 'hidden'; 13578 } 13579 13580 if (tinymce.WindowManager) 13581 t.windowManager = new tinymce.WindowManager(t); 13582 13583 if (s.encoding == 'xml') { 13584 t.onGetContent.add(function(ed, o) { 13585 if (o.save) 13586 o.content = DOM.encode(o.content); 13587 }); 13588 } 13589 13590 if (s.add_form_submit_trigger) { 13591 t.onSubmit.addToTop(function() { 13592 if (t.initialized) { 13593 t.save(); 13594 t.isNotDirty = 1; 13595 } 13596 }); 13597 } 13598 13599 if (s.add_unload_trigger) { 13600 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13601 if (t.initialized && !t.destroyed && !t.isHidden()) 13602 t.save({format : 'raw', no_events : true}); 13603 }); 13604 } 13605 13606 tinymce.addUnload(t.destroy, t); 13607 13608 if (s.submit_patch) { 13609 t.onBeforeRenderUI.add(function() { 13610 var n = t.getElement().form; 13611 13612 if (!n) 13613 return; 13614 13615 // Already patched 13616 if (n._mceOldSubmit) 13617 return; 13618 13619 // Check page uses id="submit" or name="submit" for it's submit button 13620 if (!n.submit.nodeType && !n.submit.length) { 13621 t.formElement = n; 13622 n._mceOldSubmit = n.submit; 13623 n.submit = function() { 13624 // Save all instances 13625 tinymce.triggerSave(); 13626 t.isNotDirty = 1; 13627 13628 return t.formElement._mceOldSubmit(t.formElement); 13629 }; 13630 } 13631 13632 n = null; 13633 }); 13634 } 13635 13636 // Load scripts 13637 function loadScripts() { 13638 if (s.language && s.language_load !== false) 13639 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13640 13641 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13642 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13643 13644 each(explode(s.plugins), function(p) { 13645 if (p &&!PluginManager.urls[p]) { 13646 if (p.charAt(0) == '-') { 13647 p = p.substr(1, p.length); 13648 var dependencies = PluginManager.dependencies(p); 13649 each(dependencies, function(dep) { 13650 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13651 dep = PluginManager.createUrl(defaultSettings, dep); 13652 PluginManager.load(dep.resource, dep); 13653 }); 13654 } else { 13655 // Skip safari plugin, since it is removed as of 3.3b1 13656 if (p == 'safari') { 13657 return; 13658 } 13659 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13660 } 13661 } 13662 }); 13663 13664 // Init when que is loaded 13665 sl.loadQueue(function() { 13666 if (!t.removed) 13667 t.init(); 13668 }); 13669 }; 13670 13671 loadScripts(); 13672 }, 13673 13674 init : function() { 13675 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13676 13677 tinymce.add(t); 13678 13679 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13680 13681 if (s.theme) { 13682 if (typeof s.theme != "function") { 13683 s.theme = s.theme.replace(/-/, ''); 13684 o = ThemeManager.get(s.theme); 13685 t.theme = new o(); 13686 13687 if (t.theme.init) 13688 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13689 } else { 13690 t.theme = s.theme; 13691 } 13692 } 13693 13694 function initPlugin(p) { 13695 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13696 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13697 each(PluginManager.dependencies(p), function(dep){ 13698 initPlugin(dep); 13699 }); 13700 po = new c(t, u); 13701 13702 t.plugins[p] = po; 13703 13704 if (po.init) { 13705 po.init(t, u); 13706 initializedPlugins.push(p); 13707 } 13708 } 13709 } 13710 13711 // Create all plugins 13712 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13713 13714 // Setup popup CSS path(s) 13715 if (s.popup_css !== false) { 13716 if (s.popup_css) 13717 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13718 else 13719 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13720 } 13721 13722 if (s.popup_css_add) 13723 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13724 13725 t.controlManager = new tinymce.ControlManager(t); 13726 13727 // Enables users to override the control factory 13728 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13729 13730 // Measure box 13731 if (s.render_ui && t.theme) { 13732 t.orgDisplay = e.style.display; 13733 13734 if (typeof s.theme != "function") { 13735 w = s.width || e.style.width || e.offsetWidth; 13736 h = s.height || e.style.height || e.offsetHeight; 13737 mh = s.min_height || 100; 13738 re = /^[0-9\.]+(|px)$/i; 13739 13740 if (re.test('' + w)) 13741 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13742 13743 if (re.test('' + h))
13744 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13745 13746 // Render UI 13747 o = t.theme.renderUI({ 13748 targetNode : e, 13749 width : w, 13750 height : h, 13751 deltaWidth : s.delta_width, 13752 deltaHeight : s.delta_height 13753 }); 13754 13755 // Resize editor 13756 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13757 width : w, 13758 height : h 13759 }); 13760 13761 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13762 if (h < mh) 13763 h = mh; 13764 } else { 13765 o = s.theme(t, e); 13766 13767 // Convert element type to id:s 13768 if (o.editorContainer.nodeType) { 13769 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13770 } 13771 13772 // Convert element type to id:s 13773 if (o.iframeContainer.nodeType) { 13774 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13775 } 13776 13777 // Use specified iframe height or the targets offsetHeight 13778 h = o.iframeHeight || e.offsetHeight; 13779 13780 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13781 if (isIE) { 13782 t.onInit.add(function(ed) { 13783 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() { 13784 ed.bookmark = ed.selection.getBookmark(1); 13785 }); 13786 }); 13787 13788 t.onNodeChange.add(function(ed) { 13789 if (document.activeElement.id == ed.id + "_ifr") { 13790 ed.bookmark = ed.selection.getBookmark(1); 13791 } 13792 }); 13793 } 13794 } 13795 13796 t.editorContainer = o.editorContainer; 13797 } 13798 13799 // Load specified content CSS last 13800 if (s.content_css) { 13801 each(explode(s.content_css), function(u) { 13802 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13803 }); 13804 } 13805 13806 // Load specified content CSS last 13807 if (s.content_style) { 13808 t.contentStyles.push(s.content_style); 13809 } 13810 13811 // Content editable mode ends here 13812 if (s.content_editable) { 13813 e = n = o = null; // Fix IE leak 13814 return t.initContentBody(); 13815 } 13816 13817 // User specified a document.domain value 13818 if (document.domain && location.hostname != document.domain) 13819 tinymce.relaxedDomain = document.domain; 13820 13821 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13822 13823 // We only need to override paths if we have to 13824 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13825 if (s.document_base_url != tinymce.documentBaseURL) 13826 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13827 13828 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13829 if (tinymce.isIE8) { 13830 if (s.ie7_compat) 13831 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13832 else 13833 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13834 } 13835 13836 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13837 13838 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13839 for (i = 0; i < t.contentCSS.length; i++) { 13840 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13841 } 13842 13843 t.contentCSS = []; 13844 13845 bi = s.body_id || 'tinymce'; 13846 if (bi.indexOf('=') != -1) { 13847 bi = t.getParam('body_id', '', 'hash'); 13848 bi = bi[t.id] || bi; 13849 } 13850 13851 bc = s.body_class || ''; 13852 if (bc.indexOf('=') != -1) { 13853 bc = t.getParam('body_class', '', 'hash'); 13854 bc = bc[t.id] || ''; 13855 } 13856 13857 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13858 13859 // Domain relaxing enabled, then set document domain 13860 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13861 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13862 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();})()'; 13863 } 13864 13865 // Create iframe 13866 // TODO: ACC add the appropriate description on this.
13867 n = DOM.add(o.iframeContainer, 'iframe', { 13868 id : t.id + "_ifr", 13869 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13870 frameBorder : '0', 13871 allowTransparency : "true", 13872 title : s.aria_label, 13873 style : { 13874 width : '100%', 13875 height : h, 13876 display : 'block' // Important for Gecko to render the iframe correctly 13877 } 13878 }); 13879 13880 t.contentAreaContainer = o.iframeContainer; 13881 13882 if (o.editorContainer) { 13883 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13884 } 13885 13886 // Restore visibility on target element 13887 e.style.visibility = t.orgVisibility; 13888 13889 DOM.get(t.id).style.display = 'none'; 13890 DOM.setAttrib(t.id, 'aria-hidden', true); 13891 13892 if (!tinymce.relaxedDomain || !u) 13893 t.initContentBody(); 13894 13895 e = n = o = null; // Cleanup 13896 }, 13897 13898 initContentBody : function() { 13899 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13900 13901 // Setup iframe body 13902 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13903 doc.open(); 13904 doc.write(self.iframeHTML); 13905 doc.close(); 13906 13907 if (tinymce.relaxedDomain) 13908 doc.domain = tinymce.relaxedDomain; 13909 } 13910 13911 if (settings.content_editable) { 13912 DOM.addClass(targetElm, 'mceContentBody'); 13913 self.contentDocument = doc = settings.content_document || document; 13914 self.contentWindow = settings.content_window || window; 13915 self.bodyElement = targetElm; 13916 13917 // Prevent leak in IE 13918 settings.content_document = settings.content_window = null; 13919 } 13920 13921 // It will not steal focus while setting contentEditable 13922 body = self.getBody(); 13923 body.disabled = true; 13924 13925 if (!settings.readonly) 13926 body.contentEditable = self.getParam('content_editable_state', true); 13927 13928 body.disabled = false; 13929 13930 self.schema = new tinymce.html.Schema(settings); 13931 13932 self.dom = new tinymce.dom.DOMUtils(doc, { 13933 keep_values : true, 13934 url_converter : self.convertURL, 13935 url_converter_scope : self, 13936 hex_colors : settings.force_hex_style_colors, 13937 class_filter : settings.class_filter, 13938 update_styles : true, 13939 root_element : settings.content_editable ? self.id : null, 13940 schema : self.schema 13941 }); 13942 13943 self.parser = new tinymce.html.DomParser(settings, self.schema); 13944 13945 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13946 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13947 var i = nodes.length, node, dom = self.dom, value, internalName; 13948 13949 while (i--) { 13950 node = nodes[i]; 13951 value = node.attr(name); 13952 internalName = 'data-mce-' + name; 13953 13954 // Add internal attribute if we need to we don't on a refresh of the document 13955 if (!node.attributes.map[internalName]) { 13956 if (name === "style") 13957 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13958 else 13959 node.attr(internalName, self.convertURL(value, name, node.name)); 13960 } 13961 } 13962 }); 13963 13964 // Keep scripts from executing 13965 self.parser.addNodeFilter('script', function(nodes, name) { 13966 var i = nodes.length, node; 13967 13968 while (i--) { 13969 node = nodes[i]; 13970 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13971 } 13972 }); 13973 13974 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13975 var i = nodes.length, node; 13976 13977 while (i--) { 13978 node = nodes[i]; 13979 node.type = 8; 13980 node.name = '#comment'; 13981 node.value = '[CDATA[' + node.value + ']]'; 13982 } 13983 }); 13984 13985 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13986 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13987 13988 while (i--) { 13989 node = nodes[i]; 13990 13991 if (node.isEmpty(nonEmptyElements)) 13992 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13993 } 13994 }); 13995 13996 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13997 13998 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13999 14000 self.formatter = new tinymce.Formatter(self); 14001 14002 self.undoManager = new tinymce.UndoManager(self); 14003 14004 self.forceBlocks = new tinymce.ForceBlocks(self); 14005 self.enterKey = new tinymce.EnterKey(self); 14006 self.editorCommands = new tinymce.EditorCommands(self); 14007 14008 self.onExecCommand.add(function(editor, command) { 14009 // Don't refresh the select lists until caret move 14010 if (!/^(FontName|FontSize)$/.test(command)) 14011 self.nodeChanged(); 14012 }); 14013 14014 // Pass through 14015 self.serializer.onPreProcess.add(function(se, o) { 14016 return self.onPreProcess.dispatch(self, o, se); 14017 }); 14018 14019 self.serializer.onPostProcess.add(function(se, o) { 14020 return self.onPostProcess.dispatch(self, o, se); 14021 }); 14022 14023 self.onPreInit.dispatch(self); 14024
14025 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 14026 doc.body.spellcheck = false; 14027 14028 if (!settings.readonly) { 14029 self.bindNativeEvents(); 14030 } 14031 14032 self.controlManager.onPostRender.dispatch(self, self.controlManager); 14033 self.onPostRender.dispatch(self); 14034 14035 self.quirks = tinymce.util.Quirks(self); 14036 14037 if (settings.directionality) 14038 body.dir = settings.directionality; 14039 14040 if (settings.nowrap) 14041 body.style.whiteSpace = "nowrap"; 14042 14043 if (settings.protect) { 14044 self.onBeforeSetContent.add(function(ed, o) { 14045 each(settings.protect, function(pattern) { 14046 o.content = o.content.replace(pattern, function(str) { 14047 return '<!--mce:protected ' + escape(str) + '-->'; 14048 }); 14049 }); 14050 }); 14051 } 14052 14053 // Add visual aids when new contents is added 14054 self.onSetContent.add(function() { 14055 self.addVisual(self.getBody()); 14056 }); 14057 14058 // Remove empty contents 14059 if (settings.padd_empty_editor) { 14060 self.onPostProcess.add(function(ed, o) { 14061 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 14062 }); 14063 } 14064 14065 self.load({initial : true, format : 'html'}); 14066 self.startContent = self.getContent({format : 'raw'}); 14067 14068 self.initialized = true; 14069 14070 self.onInit.dispatch(self); 14071 self.execCallback('setupcontent_callback', self.id, body, doc); 14072 self.execCallback('init_instance_callback', self); 14073 self.focus(true); 14074 self.nodeChanged({initial : true}); 14075 14076 // Add editor specific CSS styles 14077 if (self.contentStyles.length > 0) { 14078 contentCssText = ''; 14079 14080 each(self.contentStyles, function(style) { 14081 contentCssText += style + "\r\n"; 14082 }); 14083 14084 self.dom.addStyle(contentCssText); 14085 } 14086 14087 // Load specified content CSS last 14088 each(self.contentCSS, function(url) { 14089 self.dom.loadCSS(url); 14090 }); 14091 14092 // Handle auto focus 14093 if (settings.auto_focus) { 14094 setTimeout(function () { 14095 var ed = tinymce.get(settings.auto_focus); 14096 14097 ed.selection.select(ed.getBody(), 1); 14098 ed.selection.collapse(1); 14099 ed.getBody().focus(); 14100 ed.getWin().focus(); 14101 }, 100); 14102 } 14103 14104 // Clean up references for IE 14105 targetElm = doc = body = null; 14106 }, 14107 14108 focus : function(skip_focus) { 14109 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 14110 14111 if (!skip_focus) { 14112 if (self.bookmark) { 14113 selection.moveToBookmark(self.bookmark); 14114 self.bookmark = null; 14115 } 14116 14117 // Get selected control element 14118 ieRng = selection.getRng(); 14119 if (ieRng.item) { 14120 controlElm = ieRng.item(0); 14121 } 14122 14123 self._refreshContentEditable(); 14124 14125 // Focus the window iframe 14126 if (!contentEditable) { 14127 self.getWin().focus(); 14128 } 14129 14130 // Focus the body as well since it's contentEditable 14131 if (tinymce.isGecko || contentEditable) { 14132 body = self.getBody(); 14133 14134 // Check for setActive since it doesn't scroll to the element 14135 if (body.setActive && ! tinymce.isIE11) { 14136 body.setActive(); 14137 } else { 14138 body.focus(); 14139 } 14140 14141 if (contentEditable) { 14142 selection.normalize(); 14143 } 14144 } 14145 14146 // Restore selected control element 14147 // This is needed when for example an image is selected within a 14148 // layer a call to focus will then remove the control selection 14149 if (controlElm && controlElm.ownerDocument == doc) { 14150 ieRng = doc.body.createControlRange(); 14151 ieRng.addElement(controlElm); 14152 ieRng.select(); 14153 } 14154 } 14155 14156 if (tinymce.activeEditor != self) { 14157 if ((oed = tinymce.activeEditor) != null) 14158 oed.onDeactivate.dispatch(oed, self); 14159 14160 self.onActivate.dispatch(self, oed); 14161 } 14162 14163 tinymce._setActive(self); 14164 }, 14165 14166 execCallback : function(n) { 14167 var t = this, f = t.settings[n], s; 14168 14169 if (!f) 14170 return; 14171 14172 // Look through lookup 14173 if (t.callbackLookup && (s = t.callbackLookup[n])) { 14174 f = s.func; 14175 s = s.scope; 14176 } 14177 14178 if (is(f, 'string')) { 14179 s = f.replace(/\.\w+$/, ''); 14180 s = s ? tinymce.resolve(s) : 0; 14181 f = tinymce.resolve(f); 14182 t.callbackLookup = t.callbackLookup || {}; 14183 t.callbackLookup[n] = {func : f, scope : s}; 14184 } 14185 14186 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 14187 }, 14188 14189 translate : function(s) { 14190 var c = this.settings.language || 'en', i18n = tinymce.i18n; 14191 14192 if (!s) 14193 return ''; 14194 14195 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 14196 return i18n[c + '.' + b] || '{#' + b + '}'; 14197 }); 14198 }, 14199 14200 getLang : function(n, dv) { 14201 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 14202 }, 14203 14204 getParam : function(n, dv, ty) { 14205 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 14206 14207 if (ty === 'hash') { 14208 o = {}; 14209 14210 if (is(v, 'string')) { 14211 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 14212 v = v.split('='); 14213 14214 if (v.length > 1) 14215 o[tr(v[0])] = tr(v[1]); 14216 else 14217 o[tr(v[0])] = tr(v); 14218 }); 14219 } else 14220 o = v; 14221 14222 return o; 14223 } 14224 14225 return v; 14226 }, 14227 14228 nodeChanged : function(o) { 14229 var self = this, selection = self.selection, node; 14230 14231 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 14232 if (self.initialized) { 14233 o = o || {}; 14234 14235 // Get start node 14236 node = selection.getStart() || self.getBody(); 14237 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 14238 14239 // Get parents and add them to object 14240 o.parents = []; 14241 self.dom.getParent(node, function(node) { 14242 if (node.nodeName == 'BODY') 14243 return true; 14244 14245 o.parents.push(node); 14246 }); 14247 14248 self.onNodeChange.dispatch( 14249 self, 14250 o ? o.controlManager || self.controlManager : self.controlManager, 14251 node, 14252 selection.isCollapsed(), 14253 o 14254 ); 14255 } 14256 }, 14257 14258 addButton : function(name, settings) { 14259 var self = this; 14260 14261 self.buttons = self.buttons || {}; 14262 self.buttons[name] = settings; 14263 }, 14264 14265 addCommand : function(name, callback, scope) { 14266 this.execCommands[name] = {func : callback, scope : scope || this}; 14267 }, 14268 14269 addQueryStateHandler : function(name, callback, scope) { 14270 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 14271 }, 14272 14273 addQueryValueHandler : function(name, callback, scope) { 14274 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 14275 }, 14276 14277 addShortcut : function(pa, desc, cmd_func, sc) { 14278 var t = this, c; 14279 14280 if (t.settings.custom_shortcuts === false) 14281 return false;
14282 14283 t.shortcuts = t.shortcuts || {}; 14284 14285 if (is(cmd_func, 'string')) { 14286 c = cmd_func; 14287 14288 cmd_func = function() { 14289 t.execCommand(c, false, null); 14290 }; 14291 } 14292 14293 if (is(cmd_func, 'object')) { 14294 c = cmd_func; 14295 14296 cmd_func = function() { 14297 t.execCommand(c[0], c[1], c[2]); 14298 }; 14299 } 14300 14301 each(explode(pa), function(pa) { 14302 var o = { 14303 func : cmd_func, 14304 scope : sc || this, 14305 desc : t.translate(desc), 14306 alt : false, 14307 ctrl : false, 14308 shift : false 14309 }; 14310 14311 each(explode(pa, '+'), function(v) { 14312 switch (v) { 14313 case 'alt': 14314 case 'ctrl': 14315 case 'shift': 14316 o[v] = true; 14317 break; 14318 14319 default: 14320 o.charCode = v.charCodeAt(0); 14321 o.keyCode = v.toUpperCase().charCodeAt(0); 14322 } 14323 }); 14324 14325 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 14326 }); 14327 14328 return true; 14329 }, 14330 14331 execCommand : function(cmd, ui, val, a) { 14332 var t = this, s = 0, o, st; 14333 14334 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 14335 t.focus(); 14336 14337 a = extend({}, a); 14338 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 14339 if (a.terminate) 14340 return false; 14341 14342 // Command callback 14343 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 14344 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14345 return true; 14346 } 14347 14348 // Registred commands 14349 if (o = t.execCommands[cmd]) { 14350 st = o.func.call(o.scope, ui, val); 14351 14352 // Fall through on true 14353 if (st !== true) { 14354 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14355 return st; 14356 } 14357 } 14358 14359 // Plugin commands 14360 each(t.plugins, function(p) { 14361 if (p.execCommand && p.execCommand(cmd, ui, val)) { 14362 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14363 s = 1; 14364 return false; 14365 } 14366 }); 14367 14368 if (s) 14369 return true; 14370 14371 // Theme commands 14372 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 14373 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14374 return true; 14375 } 14376 14377 // Editor commands 14378 if (t.editorCommands.execCommand(cmd, ui, val)) { 14379 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14380 return true; 14381 } 14382 14383 // Browser commands 14384 t.getDoc().execCommand(cmd, ui, val); 14385 t.onExecCommand.dispatch(t, cmd, ui, val, a); 14386 }, 14387 14388 queryCommandState : function(cmd) { 14389 var t = this, o, s; 14390 14391 // Is hidden then return undefined 14392 if (t._isHidden()) 14393 return; 14394 14395 // Registred commands 14396 if (o = t.queryStateCommands[cmd]) { 14397 s = o.func.call(o.scope); 14398 14399 // Fall though on true 14400 if (s !== true) 14401 return s; 14402 } 14403 14404 // Registred commands 14405 o = t.editorCommands.queryCommandState(cmd); 14406 if (o !== -1) 14407 return o; 14408 14409 // Browser commands 14410 try { 14411 return this.getDoc().queryCommandState(cmd); 14412 } catch (ex) { 14413 // Fails sometimes see bug: 1896577 14414 } 14415 }, 14416 14417 queryCommandValue : function(c) { 14418 var t = this, o, s; 14419 14420 // Is hidden then return undefined 14421 if (t._isHidden()) 14422 return; 14423 14424 // Registred commands 14425 if (o = t.queryValueCommands[c]) { 14426 s = o.func.call(o.scope); 14427 14428 // Fall though on true 14429 if (s !== true) 14430 return s; 14431 } 14432 14433 // Registred commands 14434 o = t.editorCommands.queryCommandValue(c); 14435 if (is(o)) 14436 return o; 14437 14438 // Browser commands 14439 try { 14440 return this.getDoc().queryCommandValue(c); 14441 } catch (ex) { 14442 // Fails sometimes see bug: 1896577 14443 } 14444 }, 14445 14446 show : function() { 14447 var self = this; 14448 14449 DOM.show(self.getContainer()); 14450 DOM.hide(self.id); 14451 self.load(); 14452 }, 14453 14454 hide : function() { 14455 var self = this, doc = self.getDoc(); 14456 14457 // Fixed bug where IE has a blinking cursor left from the editor 14458 if (isIE && doc) 14459 doc.execCommand('SelectAll'); 14460 14461 // We must save before we hide so Safari doesn't crash 14462 self.save(); 14463 14464 // defer the call to hide to prevent an IE9 crash #4921 14465 DOM.hide(self.getContainer()); 14466 DOM.setStyle(self.id, 'display', self.orgDisplay); 14467 }, 14468 14469 isHidden : function() { 14470 return !DOM.isHidden(this.id); 14471 }, 14472 14473 setProgressState : function(b, ti, o) { 14474 this.onSetProgressState.dispatch(this, b, ti, o); 14475 14476 return b; 14477 }, 14478 14479 load : function(o) { 14480 var t = this, e = t.getElement(), h; 14481 14482 if (e) { 14483 o = o || {}; 14484 o.load = true; 14485 14486 // Double encode existing entities in the value 14487 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14488 o.element = e; 14489 14490 if (!o.no_events) 14491 t.onLoadContent.dispatch(t, o); 14492 14493 o.element = e = null; 14494 14495 return h; 14496 } 14497 }, 14498 14499 save : function(o) { 14500 var t = this, e = t.getElement(), h, f; 14501 14502 if (!e || !t.initialized) 14503 return; 14504 14505 o = o || {}; 14506 o.save = true; 14507 14508 o.element = e; 14509 h = o.content = t.getContent(o); 14510 14511 if (!o.no_events) 14512 t.onSaveContent.dispatch(t, o); 14513
14514 h = o.content; 14515 14516 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14517 e.innerHTML = h; 14518 14519 // Update hidden form element 14520 if (f = DOM.getParent(t.id, 'form')) { 14521 each(f.elements, function(e) { 14522 if (e.name == t.id) { 14523 e.value = h; 14524 return false; 14525 } 14526 }); 14527 } 14528 } else 14529 e.value = h; 14530 14531 o.element = e = null; 14532 14533 return h; 14534 }, 14535 14536 setContent : function(content, args) { 14537 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14538 14539 // Setup args object 14540 args = args || {}; 14541 args.format = args.format || 'html'; 14542 args.set = true; 14543 args.content = content; 14544 14545 // Do preprocessing 14546 if (!args.no_events) 14547 self.onBeforeSetContent.dispatch(self, args); 14548 14549 content = args.content; 14550 14551 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14552 // It will also be impossible to place the caret in the editor unless there is a BR element present 14553 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14554 forcedRootBlockName = self.settings.forced_root_block; 14555 if (forcedRootBlockName) 14556 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14557 else 14558 content = '<br data-mce-bogus="1">'; 14559 14560 body.innerHTML = content; 14561 self.selection.select(body, true); 14562 self.selection.collapse(true); 14563 return; 14564 } 14565 14566 // Parse and serialize the html 14567 if (args.format !== 'raw') { 14568 content = new tinymce.html.Serializer({}, self.schema).serialize( 14569 self.parser.parse(content) 14570 ); 14571 } 14572 14573 // Set the new cleaned contents to the editor 14574 args.content = tinymce.trim(content); 14575 self.dom.setHTML(body, args.content); 14576 14577 // Do post processing 14578 if (!args.no_events) 14579 self.onSetContent.dispatch(self, args); 14580 14581 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14582 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14583 self.selection.normalize(); 14584 } 14585 14586 return args.content; 14587 }, 14588 14589 getContent : function(args) { 14590 var self = this, content, body = self.getBody(); 14591 14592 // Setup args object 14593 args = args || {}; 14594 args.format = args.format || 'html'; 14595 args.get = true; 14596 args.getInner = true; 14597 14598 // Do preprocessing 14599 if (!args.no_events) 14600 self.onBeforeGetContent.dispatch(self, args); 14601 14602 // Get raw contents or by default the cleaned contents 14603 if (args.format == 'raw') 14604 content = body.innerHTML; 14605 else if (args.format == 'text') 14606 content = body.innerText || body.textContent; 14607 else 14608 content = self.serializer.serialize(body, args); 14609 14610 // Trim whitespace in beginning/end of HTML 14611 if (args.format != 'text') { 14612 args.content = tinymce.trim(content); 14613 } else { 14614 args.content = content; 14615 } 14616 14617 // Do post processing 14618 if (!args.no_events) 14619 self.onGetContent.dispatch(self, args); 14620 14621 return args.content; 14622 }, 14623 14624 isDirty : function() { 14625 var self = this; 14626 14627 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14628 }, 14629 14630 getContainer : function() { 14631 var self = this; 14632 14633 if (!self.container) 14634 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14635 14636 return self.container; 14637 }, 14638 14639 getContentAreaContainer : function() { 14640 return this.contentAreaContainer; 14641 }, 14642 14643 getElement : function() { 14644 return DOM.get(this.settings.content_element || this.id); 14645 }, 14646 14647 getWin : function() { 14648 var self = this, elm; 14649 14650 if (!self.contentWindow) { 14651 elm = DOM.get(self.id + "_ifr"); 14652 14653 if (elm) 14654 self.contentWindow = elm.contentWindow; 14655 } 14656 14657 return self.contentWindow; 14658 }, 14659 14660 getDoc : function() { 14661 var self = this, win; 14662 14663 if (!self.contentDocument) { 14664 win = self.getWin(); 14665 14666 if (win) 14667 self.contentDocument = win.document; 14668 } 14669 14670 return self.contentDocument; 14671 }, 14672 14673 getBody : function() { 14674 return this.bodyElement || this.getDoc().body; 14675 }, 14676 14677 convertURL : function(url, name, elm) { 14678 var self = this, settings = self.settings; 14679 14680 // Use callback instead 14681 if (settings.urlconverter_callback) 14682 return self.execCallback('urlconverter_callback', url, elm, true, name); 14683 14684 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14685 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14686 return url; 14687 14688 // Convert to relative 14689 if (settings.relative_urls) 14690 return self.documentBaseURI.toRelative(url); 14691 14692 // Convert to absolute 14693 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14694 14695 return url; 14696 }, 14697 14698 addVisual : function(elm) { 14699 var self = this, settings = self.settings, dom = self.dom, cls; 14700 14701 elm = elm || self.getBody(); 14702 14703 if (!is(self.hasVisual)) 14704 self.hasVisual = settings.visual; 14705 14706 each(dom.select('table,a', elm), function(elm) { 14707 var value; 14708 14709 switch (elm.nodeName) { 14710 case 'TABLE': 14711 cls = settings.visual_table_class || 'mceItemTable'; 14712 value = dom.getAttrib(elm, 'border'); 14713 14714 if (!value || value == '0') { 14715 if (self.hasVisual) 14716 dom.addClass(elm, cls); 14717 else 14718 dom.removeClass(elm, cls); 14719 } 14720 14721 return; 14722 14723 case 'A': 14724 if (!dom.getAttrib(elm, 'href', false)) { 14725 value = dom.getAttrib(elm, 'name') || elm.id; 14726 cls = 'mceItemAnchor';
14727 14728 if (value) { 14729 if (self.hasVisual) 14730 dom.addClass(elm, cls); 14731 else 14732 dom.removeClass(elm, cls); 14733 } 14734 } 14735 14736 return; 14737 } 14738 }); 14739 14740 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14741 }, 14742 14743 remove : function() { 14744 var self = this, elm = self.getContainer(), doc = self.getDoc(); 14745 14746 if (!self.removed) { 14747 self.removed = 1; // Cancels post remove event execution 14748 14749 // Fixed bug where IE has a blinking cursor left from the editor 14750 if (isIE && doc) 14751 doc.execCommand('SelectAll'); 14752 14753 // We must save before we hide so Safari doesn't crash 14754 self.save(); 14755 14756 DOM.setStyle(self.id, 'display', self.orgDisplay); 14757 14758 // Don't clear the window or document if content editable 14759 // is enabled since other instances might still be present 14760 if (!self.settings.content_editable) { 14761 Event.unbind(self.getWin()); 14762 Event.unbind(self.getDoc()); 14763 } 14764 14765 Event.unbind(self.getBody()); 14766 Event.clear(elm); 14767 14768 self.execCallback('remove_instance_callback', self); 14769 self.onRemove.dispatch(self); 14770 14771 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14772 self.onExecCommand.listeners = []; 14773 14774 tinymce.remove(self); 14775 DOM.remove(elm); 14776 } 14777 }, 14778 14779 destroy : function(s) { 14780 var t = this; 14781 14782 // One time is enough 14783 if (t.destroyed) 14784 return; 14785 14786 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14787 if (isGecko) { 14788 Event.unbind(t.getDoc()); 14789 Event.unbind(t.getWin()); 14790 Event.unbind(t.getBody()); 14791 } 14792 14793 if (!s) { 14794 tinymce.removeUnload(t.destroy); 14795 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14796 14797 // Manual destroy 14798 if (t.theme && t.theme.destroy) 14799 t.theme.destroy(); 14800 14801 // Destroy controls, selection and dom 14802 t.controlManager.destroy(); 14803 t.selection.destroy(); 14804 t.dom.destroy(); 14805 } 14806 14807 if (t.formElement) { 14808 t.formElement.submit = t.formElement._mceOldSubmit; 14809 t.formElement._mceOldSubmit = null; 14810 } 14811 14812 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14813 14814 if (t.selection) 14815 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14816 14817 t.destroyed = 1; 14818 }, 14819 14820 // Internal functions 14821 14822 _refreshContentEditable : function() { 14823 var self = this, body, parent; 14824 14825 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14826 if (self._isHidden()) { 14827 body = self.getBody(); 14828 parent = body.parentNode; 14829 14830 parent.removeChild(body); 14831 parent.appendChild(body); 14832 14833 body.focus(); 14834 } 14835 }, 14836 14837 _isHidden : function() { 14838 var s; 14839 14840 if (!isGecko) 14841 return 0; 14842 14843 // Weird, wheres that cursor selection? 14844 s = this.selection.getSel(); 14845 return (!s || !s.rangeCount || s.rangeCount === 0); 14846 } 14847 }); 14848})(tinymce); 14849(function(tinymce) { 14850 var each = tinymce.each; 14851 14852 tinymce.Editor.prototype.setupEvents = function() { 14853 var self = this, settings = self.settings; 14854 14855 // Add events to the editor 14856 each([ 14857 'onPreInit', 14858 14859 'onBeforeRenderUI', 14860 14861 'onPostRender', 14862 14863 'onLoad', 14864 14865 'onInit', 14866 14867 'onRemove', 14868 14869 'onActivate', 14870 14871 'onDeactivate', 14872 14873 'onClick', 14874 14875 'onEvent', 14876 14877 'onMouseUp', 14878 14879 'onMouseDown', 14880 14881 'onDblClick', 14882 14883 'onKeyDown', 14884 14885 'onKeyUp', 14886 14887 'onKeyPress', 14888 14889 'onContextMenu', 14890 14891 'onSubmit', 14892 14893 'onReset', 14894 14895 'onPaste', 14896 14897 'onPreProcess', 14898 14899 'onPostProcess', 14900 14901 'onBeforeSetContent', 14902 14903 'onBeforeGetContent', 14904 14905 'onSetContent', 14906 14907 'onGetContent', 14908 14909 'onLoadContent', 14910 14911 'onSaveContent', 14912 14913 'onNodeChange', 14914 14915 'onChange', 14916 14917 'onBeforeExecCommand', 14918 14919 'onExecCommand', 14920 14921 'onUndo', 14922 14923 'onRedo', 14924 14925 'onVisualAid', 14926 14927 'onSetProgressState', 14928 14929 'onSetAttrib' 14930 ], function(name) { 14931 self[name] = new tinymce.util.Dispatcher(self); 14932 }); 14933 14934 // Handle legacy cleanup_callback option 14935 if (settings.cleanup_callback) { 14936 self.onBeforeSetContent.add(function(ed, o) { 14937 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14938 }); 14939 14940 self.onPreProcess.add(function(ed, o) { 14941 if (o.set) 14942 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14943 14944 if (o.get) 14945 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14946 }); 14947 14948 self.onPostProcess.add(function(ed, o) { 14949 if (o.set) 14950 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14951 14952 if (o.get) 14953 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14954 }); 14955 } 14956 14957 // Handle legacy save_callback option 14958 if (settings.save_callback) { 14959 self.onGetContent.add(function(ed, o) { 14960 if (o.save) 14961 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14962 }); 14963 } 14964 14965 // Handle legacy handle_event_callback option 14966 if (settings.handle_event_callback) { 14967 self.onEvent.add(function(ed, e, o) { 14968 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14969 e.preventDefault(); 14970 e.stopPropagation(); 14971 } 14972 }); 14973 } 14974 14975 // Handle legacy handle_node_change_callback option 14976 if (settings.handle_node_change_callback) { 14977 self.onNodeChange.add(function(ed, cm, n) { 14978 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14979 }); 14980 } 14981 14982 // Handle legacy save_callback option 14983 if (settings.save_callback) { 14984 self.onSaveContent.add(function(ed, o) { 14985 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14986 14987 if (h) 14988 o.content = h; 14989 }); 14990 } 14991 14992 // Handle legacy onchange_callback option 14993 if (settings.onchange_callback) { 14994 self.onChange.add(function(ed, l) { 14995 ed.execCallback('onchange_callback', ed, l); 14996 }); 14997 } 14998 }; 14999 15000 tinymce.Editor.prototype.bindNativeEvents = function() { 15001 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 15002 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 15003 15004 nativeToDispatcherMap = { 15005 mouseup : 'onMouseUp', 15006 mousedown : 'onMouseDown', 15007 click : 'onClick', 15008 keyup : 'onKeyUp', 15009 keydown : 'onKeyDown', 15010 keypress : 'onKeyPress', 15011 submit : 'onSubmit', 15012 reset : 'onReset', 15013 contextmenu : 'onContextMenu', 15014 dblclick : 'onDblClick', 15015 paste : 'onPaste' // Doesn't work in all browsers yet 15016 }; 15017 15018 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 15019 function eventHandler(evt, args) { 15020 var type = evt.type; 15021 15022 // Don't fire events when it's removed 15023 if (self.removed) 15024 return; 15025 15026 // Sends the native event out to a global dispatcher then to the specific event dispatcher 15027 if (self.onEvent.dispatch(self, evt, args) !== false) { 15028 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 15029 } 15030 }; 15031 15032 // Opera doesn't support focus event for contentEditable elements so we need to fake it 15033 function doOperaFocus(e) { 15034 self.focus(true); 15035 }; 15036 15037 function nodeChanged(ed, e) { 15038 // 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 15039 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 15040 self.selection.normalize(); 15041 } 15042 15043 self.nodeChanged(); 15044 } 15045 15046 // Add DOM events 15047 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 15048 var root = settings.content_editable ? self.getBody() : self.getDoc(); 15049 15050 switch (nativeName) { 15051 case 'contextmenu': 15052 dom.bind(root, nativeName, eventHandler); 15053 break; 15054
15055 case 'paste': 15056 dom.bind(self.getBody(), nativeName, eventHandler); 15057 break; 15058 15059 case 'submit': 15060 case 'reset': 15061 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 15062 break; 15063 15064 default: 15065 dom.bind(root, nativeName, eventHandler); 15066 } 15067 }); 15068 15069 // Set the editor as active when focused 15070 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 15071 self.focus(true); 15072 }); 15073 15074 if (settings.content_editable && tinymce.isOpera) { 15075 dom.bind(self.getBody(), 'click', doOperaFocus); 15076 dom.bind(self.getBody(), 'keydown', doOperaFocus); 15077 } 15078 15079 // Add node change handler 15080 self.onMouseUp.add(nodeChanged); 15081 15082 self.onKeyUp.add(function(ed, e) { 15083 var keyCode = e.keyCode; 15084 15085 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) 15086 nodeChanged(ed, e); 15087 }); 15088 15089 // Add reset handler 15090 self.onReset.add(function() { 15091 self.setContent(self.startContent, {format : 'raw'}); 15092 }); 15093 15094 // Add shortcuts 15095 function handleShortcut(e, execute) { 15096 if (e.altKey || e.ctrlKey || e.metaKey) { 15097 each(self.shortcuts, function(shortcut) { 15098 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 15099 15100 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 15101 return; 15102 15103 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 15104 e.preventDefault(); 15105 15106 if (execute) { 15107 shortcut.func.call(shortcut.scope); 15108 } 15109 15110 return true; 15111 } 15112 }); 15113 } 15114 }; 15115 15116 self.onKeyUp.add(function(ed, e) { 15117 handleShortcut(e); 15118 }); 15119 15120 self.onKeyPress.add(function(ed, e) { 15121 handleShortcut(e); 15122 }); 15123 15124 self.onKeyDown.add(function(ed, e) { 15125 handleShortcut(e, true); 15126 }); 15127 15128 if (tinymce.isOpera) { 15129 self.onClick.add(function(ed, e) { 15130 e.preventDefault(); 15131 }); 15132 } 15133 }; 15134})(tinymce); 15135(function(tinymce) { 15136 // Added for compression purposes 15137 var each = tinymce.each, undef, TRUE = true, FALSE = false; 15138 15139 tinymce.EditorCommands = function(editor) { 15140 var dom = editor.dom, 15141 selection = editor.selection, 15142 commands = {state: {}, exec : {}, value : {}}, 15143 settings = editor.settings, 15144 formatter = editor.formatter, 15145 bookmark; 15146 15147 function execCommand(command, ui, value) { 15148 var func; 15149 15150 command = command.toLowerCase(); 15151 if (func = commands.exec[command]) { 15152 func(command, ui, value); 15153 return TRUE; 15154 } 15155 15156 return FALSE; 15157 }; 15158 15159 function queryCommandState(command) { 15160 var func; 15161 15162 command = command.toLowerCase(); 15163 if (func = commands.state[command]) 15164 return func(command); 15165 15166 return -1; 15167 }; 15168 15169 function queryCommandValue(command) { 15170 var func; 15171 15172 command = command.toLowerCase(); 15173 if (func = commands.value[command]) 15174 return func(command); 15175 15176 return FALSE; 15177 }; 15178 15179 function addCommands(command_list, type) { 15180 type = type || 'exec'; 15181 15182 each(command_list, function(callback, command) { 15183 each(command.toLowerCase().split(','), function(command) { 15184 commands[type][command] = callback; 15185 }); 15186 }); 15187 }; 15188 15189 // Expose public methods 15190 tinymce.extend(this, { 15191 execCommand : execCommand, 15192 queryCommandState : queryCommandState, 15193 queryCommandValue : queryCommandValue, 15194 addCommands : addCommands 15195 }); 15196 15197 // Private methods 15198 15199 function execNativeCommand(command, ui, value) { 15200 if (ui === undef) 15201 ui = FALSE; 15202 15203 if (value === undef) 15204 value = null; 15205 15206 return editor.getDoc().execCommand(command, ui, value); 15207 }; 15208 15209 function isFormatMatch(name) { 15210 return formatter.match(name); 15211 }; 15212 15213 function toggleFormat(name, value) { 15214 formatter.toggle(name, value ? {value : value} : undef); 15215 }; 15216 15217 function storeSelection(type) { 15218 bookmark = selection.getBookmark(type); 15219 }; 15220 15221 function restoreSelection() { 15222 selection.moveToBookmark(bookmark); 15223 }; 15224 15225 // Add execCommand overrides 15226 addCommands({ 15227 // Ignore these, added for compatibility 15228 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 15229 15230 // Add undo manager logic 15231 'mceEndUndoLevel,mceAddUndoLevel' : function() { 15232 editor.undoManager.add(); 15233 }, 15234 15235 'Cut,Copy,Paste' : function(command) { 15236 var doc = editor.getDoc(), failed; 15237 15238 // Try executing the native command 15239 try { 15240 execNativeCommand(command); 15241 } catch (ex) { 15242 // Command failed 15243 failed = TRUE; 15244 } 15245 15246 // Present alert message about clipboard access not being available 15247 if (failed || !doc.queryCommandSupported(command)) { 15248 if (tinymce.isGecko) { 15249 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 15250 if (state) 15251 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 15252 }); 15253 } else 15254 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 15255 } 15256 }, 15257 15258 // Override unlink command 15259 unlink : function(command) { 15260 if (selection.isCollapsed()) 15261 selection.select(selection.getNode()); 15262 15263 execNativeCommand(command); 15264 selection.collapse(FALSE); 15265 }, 15266 15267 // Override justify commands to use the text formatter engine 15268 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15269 var align = command.substring(7); 15270 15271 // Remove all other alignments first 15272 each('left,center,right,full'.split(','), function(name) { 15273 if (align != name) 15274 formatter.remove('align' + name); 15275 }); 15276 15277 toggleFormat('align' + align); 15278 execCommand('mceRepaint'); 15279 }, 15280 15281 // Override list commands to fix WebKit bug 15282 'InsertUnorderedList,InsertOrderedList' : function(command) { 15283 var listElm, listParent; 15284 15285 execNativeCommand(command); 15286 15287 // WebKit produces lists within block elements so we need to split them 15288 // we will replace the native list creation logic to custom logic later on 15289 // TODO: Remove this when the list creation logic is removed 15290 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 15291 if (listElm) { 15292 listParent = listElm.parentNode; 15293 15294 // If list is within a text block then split that block 15295 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 15296 storeSelection(); 15297 dom.split(listParent, listElm); 15298 restoreSelection(); 15299 } 15300 } 15301 }, 15302 15303 // Override commands to use the text formatter engine 15304 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15305 toggleFormat(command); 15306 }, 15307 15308 // Override commands to use the text formatter engine 15309 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 15310 toggleFormat(command, value); 15311 }, 15312 15313 FontSize : function(command, ui, value) { 15314 var fontClasses, fontSizes; 15315 15316 // Convert font size 1-7 to styles 15317 if (value >= 1 && value <= 7) { 15318 fontSizes = tinymce.explode(settings.font_size_style_values); 15319 fontClasses = tinymce.explode(settings.font_size_classes); 15320 15321 if (fontClasses) 15322 value = fontClasses[value - 1] || value; 15323 else 15324 value = fontSizes[value - 1] || value; 15325 } 15326 15327 toggleFormat(command, value); 15328 }, 15329 15330 RemoveFormat : function(command) { 15331 formatter.remove(command); 15332 }, 15333 15334 mceBlockQuote : function(command) { 15335 toggleFormat('blockquote'); 15336 }, 15337 15338 FormatBlock : function(command, ui, value) { 15339 return toggleFormat(value || 'p'); 15340 }, 15341 15342 mceCleanup : function() { 15343 var bookmark = selection.getBookmark(); 15344 15345 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 15346 15347 selection.moveToBookmark(bookmark); 15348 }, 15349 15350 mceRemoveNode : function(command, ui, value) { 15351 var node = value || selection.getNode(); 15352 15353 // Make sure that the body node isn't removed 15354 if (node != editor.getBody()) { 15355 storeSelection(); 15356 editor.dom.remove(node, TRUE); 15357 restoreSelection(); 15358 } 15359 }, 15360 15361 mceSelectNodeDepth : function(command, ui, value) { 15362 var counter = 0; 15363 15364 dom.getParent(selection.getNode(), function(node) { 15365 if (node.nodeType == 1 && counter++ == value) { 15366 selection.select(node); 15367 return FALSE; 15368 } 15369 }, editor.getBody()); 15370 }, 15371 15372 mceSelectNode : function(command, ui, value) { 15373 selection.select(value); 15374 }, 15375 15376 mceInsertContent : function(command, ui, value) { 15377 var parser, serializer, parentNode, rootNode, fragment, args, 15378 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 15379 15380 //selection.normalize(); 15381 15382 // Setup parser and serializer 15383 parser = editor.parser; 15384 serializer = new tinymce.html.Serializer({}, editor.schema); 15385 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 15386 15387 // Run beforeSetContent handlers on the HTML to be inserted
15388 args = {content: value, format: 'html'}; 15389 selection.onBeforeSetContent.dispatch(selection, args); 15390 value = args.content; 15391 15392 // Add caret at end of contents if it's missing 15393 if (value.indexOf('{$caret}') == -1) 15394 value += '{$caret}'; 15395 15396 // Replace the caret marker with a span bookmark element 15397 value = value.replace(/\{\$caret\}/, bookmarkHtml); 15398 15399 // Insert node maker where we will insert the new HTML and get it's parent 15400 if (!selection.isCollapsed()) 15401 editor.getDoc().execCommand('Delete', false, null); 15402 15403 parentNode = selection.getNode(); 15404 15405 // Parse the fragment within the context of the parent node 15406 args = {context : parentNode.nodeName.toLowerCase()}; 15407 fragment = parser.parse(value, args); 15408 15409 // Move the caret to a more suitable location 15410 node = fragment.lastChild; 15411 if (node.attr('id') == 'mce_marker') { 15412 marker = node; 15413 15414 for (node = node.prev; node; node = node.walk(true)) { 15415 if (node.type == 3 || !dom.isBlock(node.name)) { 15416 node.parent.insert(marker, node, node.name === 'br'); 15417 break; 15418 } 15419 } 15420 } 15421 15422 // If parser says valid we can insert the contents into that parent 15423 if (!args.invalid) { 15424 value = serializer.serialize(fragment); 15425 15426 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 15427 node = parentNode.firstChild; 15428 node2 = parentNode.lastChild; 15429 if (!node || (node === node2 && node.nodeName === 'BR')) 15430 dom.setHTML(parentNode, value); 15431 else 15432 selection.setContent(value); 15433 } else {
15434 // If the fragment was invalid within that context then we need 15435 // to parse and process the parent it's inserted into 15436 15437 // Insert bookmark node and get the parent 15438 selection.setContent(bookmarkHtml); 15439 parentNode = selection.getNode(); 15440 rootNode = editor.getBody(); 15441 15442 // Opera will return the document node when selection is in root 15443 if (parentNode.nodeType == 9) 15444 parentNode = node = rootNode; 15445 else 15446 node = parentNode; 15447 15448 // Find the ancestor just before the root element 15449 while (node !== rootNode) { 15450 parentNode = node; 15451 node = node.parentNode; 15452 } 15453 15454 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15455 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15456 value = serializer.serialize( 15457 parser.parse( 15458 // Need to replace by using a function since $ in the contents would otherwise be a problem 15459 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15460 return serializer.serialize(fragment); 15461 }) 15462 ) 15463 ); 15464 15465 // Set the inner/outer HTML depending on if we are in the root or not 15466 if (parentNode == rootNode) 15467 dom.setHTML(rootNode, value); 15468 else 15469 dom.setOuterHTML(parentNode, value); 15470 } 15471 15472 marker = dom.get('mce_marker'); 15473 15474 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15475 nodeRect = dom.getRect(marker); 15476 viewPortRect = dom.getViewPort(editor.getWin()); 15477 15478 // Check if node is out side the viewport if it is then scroll to it 15479 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15480 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15481 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15482 viewportBodyElement.scrollLeft = nodeRect.x; 15483 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15484 } 15485 15486 // Move selection before marker and remove it 15487 rng = dom.createRng(); 15488 15489 // If previous sibling is a text node set the selection to the end of that node 15490 node = marker.previousSibling; 15491 if (node && node.nodeType == 3) { 15492 rng.setStart(node, node.nodeValue.length); 15493 } else { 15494 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15495 rng.setStartBefore(marker); 15496 rng.setEndBefore(marker); 15497 } 15498 15499 // Remove the marker node and set the new range 15500 dom.remove(marker); 15501 selection.setRng(rng); 15502 15503 // Dispatch after event and add any visual elements needed 15504 selection.onSetContent.dispatch(selection, args); 15505 editor.addVisual(); 15506 }, 15507 15508 mceInsertRawHTML : function(command, ui, value) { 15509 selection.setContent('tiny_mce_marker'); 15510 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15511 }, 15512 15513 mceToggleFormat : function(command, ui, value) { 15514 toggleFormat(value); 15515 }, 15516 15517 mceSetContent : function(command, ui, value) { 15518 editor.setContent(value); 15519 }, 15520 15521 'Indent,Outdent' : function(command) { 15522 var intentValue, indentUnit, value; 15523 15524 // Setup indent level 15525 intentValue = settings.indentation; 15526 indentUnit = /[a-z%]+$/i.exec(intentValue); 15527 intentValue = parseInt(intentValue); 15528 15529 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15530 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15531 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15532 formatter.apply('div'); 15533 } 15534 15535 each(selection.getSelectedBlocks(), function(element) { 15536 if (command == 'outdent') { 15537 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15538 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15539 } else 15540 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15541 }); 15542 } else 15543 execNativeCommand(command); 15544 }, 15545 15546 mceRepaint : function() { 15547 var bookmark; 15548 15549 if (tinymce.isGecko) { 15550 try { 15551 storeSelection(TRUE); 15552 15553 if (selection.getSel()) 15554 selection.getSel().selectAllChildren(editor.getBody()); 15555 15556 selection.collapse(TRUE); 15557 restoreSelection(); 15558 } catch (ex) { 15559 // Ignore 15560 } 15561 } 15562 }, 15563 15564 mceToggleFormat : function(command, ui, value) { 15565 formatter.toggle(value); 15566 }, 15567 15568 InsertHorizontalRule : function() { 15569 editor.execCommand('mceInsertContent', false, '<hr />'); 15570 }, 15571 15572 mceToggleVisualAid : function() { 15573 editor.hasVisual = !editor.hasVisual; 15574 editor.addVisual(); 15575 }, 15576 15577 mceReplaceContent : function(command, ui, value) { 15578 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15579 }, 15580 15581 mceInsertLink : function(command, ui, value) { 15582 var anchor; 15583 15584 if (typeof(value) == 'string') 15585 value = {href : value}; 15586 15587 anchor = dom.getParent(selection.getNode(), 'a'); 15588 15589 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15590 value.href = value.href.replace(' ', '%20'); 15591 15592 // Remove existing links if there could be child links or that the href isn't specified 15593 if (!anchor || !value.href) { 15594 formatter.remove('link'); 15595 } 15596 15597 // Apply new link to selection 15598 if (value.href) { 15599 formatter.apply('link', value, anchor); 15600 } 15601 }, 15602 15603 selectAll : function() { 15604 var root = dom.getRoot(), rng = dom.createRng(); 15605 15606 // Old IE does a better job with selectall than new versions 15607 if (selection.getRng().setStart) { 15608 rng.setStart(root, 0); 15609 rng.setEnd(root, root.childNodes.length); 15610 15611 selection.setRng(rng); 15612 } else { 15613 execNativeCommand('SelectAll'); 15614 } 15615 } 15616 }); 15617 15618 // Add queryCommandState overrides 15619 addCommands({ 15620 // Override justify commands 15621 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15622 var name = 'align' + command.substring(7); 15623 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15624 var matches = tinymce.map(nodes, function(node) { 15625 return !!formatter.matchNode(node, name); 15626 }); 15627 return tinymce.inArray(matches, TRUE) !== -1; 15628 }, 15629 15630 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15631 return isFormatMatch(command); 15632 }, 15633 15634 mceBlockQuote : function() { 15635 return isFormatMatch('blockquote'); 15636 }, 15637 15638 Outdent : function() { 15639 var node; 15640 15641 if (settings.inline_styles) { 15642 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15643 return TRUE; 15644 15645 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15646 return TRUE; 15647 } 15648 15649 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15650 }, 15651 15652 'InsertUnorderedList,InsertOrderedList' : function(command) { 15653 var list = dom.getParent(selection.getNode(), 'ul,ol'); 15654 return list && 15655 (command === 'insertunorderedlist' && list.tagName === 'UL' 15656 || command === 'insertorderedlist' && list.tagName === 'OL'); 15657 } 15658 }, 'state'); 15659 15660 // Add queryCommandValue overrides 15661 addCommands({ 15662 'FontSize,FontName' : function(command) { 15663 var value = 0, parent; 15664 15665 if (parent = dom.getParent(selection.getNode(), 'span')) { 15666 if (command == 'fontsize') 15667 value = parent.style.fontSize; 15668 else 15669 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15670 } 15671 15672 return value; 15673 } 15674 }, 'value'); 15675 15676 // Add undo manager logic 15677 addCommands({ 15678 Undo : function() { 15679 editor.undoManager.undo(); 15680 }, 15681 15682 Redo : function() { 15683 editor.undoManager.redo(); 15684 } 15685 }); 15686 }; 15687})(tinymce); 15688 15689(function(tinymce) { 15690 var Dispatcher = tinymce.util.Dispatcher; 15691 15692 tinymce.UndoManager = function(editor) { 15693 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15694 15695 function getContent() { 15696 // Remove whitespace before/after and remove pure bogus nodes 15697 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15698 }; 15699 15700 function addNonTypingUndoLevel() { 15701 self.typing = false;
15702 self.add(); 15703 }; 15704 15705 // Create event instances 15706 onBeforeAdd = new Dispatcher(self); 15707 onAdd = new Dispatcher(self); 15708 onUndo = new Dispatcher(self); 15709 onRedo = new Dispatcher(self); 15710 15711 // Pass though onAdd event from UndoManager to Editor as onChange 15712 onAdd.add(function(undoman, level) { 15713 if (undoman.hasUndo()) 15714 return editor.onChange.dispatch(editor, level, undoman); 15715 }); 15716 15717 // Pass though onUndo event from UndoManager to Editor 15718 onUndo.add(function(undoman, level) { 15719 return editor.onUndo.dispatch(editor, level, undoman); 15720 }); 15721 15722 // Pass though onRedo event from UndoManager to Editor 15723 onRedo.add(function(undoman, level) { 15724 return editor.onRedo.dispatch(editor, level, undoman); 15725 }); 15726 15727 // Add initial undo level when the editor is initialized 15728 editor.onInit.add(function() { 15729 self.add(); 15730 }); 15731 15732 // Get position before an execCommand is processed 15733 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15734 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15735 self.beforeChange(); 15736 } 15737 }); 15738 15739 // Add undo level after an execCommand call was made 15740 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15741 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15742 self.add(); 15743 } 15744 }); 15745 15746 // Add undo level on save contents, drag end and blur/focusout 15747 editor.onSaveContent.add(addNonTypingUndoLevel); 15748 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15749 editor.dom.bind(editor.getBody(), 'focusout', function(e) { 15750 if (!editor.removed && self.typing) { 15751 addNonTypingUndoLevel(); 15752 } 15753 }); 15754 15755 editor.onKeyUp.add(function(editor, e) { 15756 var keyCode = e.keyCode; 15757 15758 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15759 addNonTypingUndoLevel(); 15760 } 15761 }); 15762 15763 editor.onKeyDown.add(function(editor, e) { 15764 var keyCode = e.keyCode; 15765 15766 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15767 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15768 if (self.typing) { 15769 addNonTypingUndoLevel(); 15770 } 15771 15772 return; 15773 } 15774 15775 // If key isn't shift,ctrl,alt,capslock,metakey 15776 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15777 self.beforeChange(); 15778 self.typing = true; 15779 self.add(); 15780 } 15781 }); 15782 15783 editor.onMouseDown.add(function(editor, e) { 15784 if (self.typing) { 15785 addNonTypingUndoLevel(); 15786 } 15787 }); 15788 15789 // Add keyboard shortcuts for undo/redo keys 15790 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15791 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15792 15793 self = { 15794 // Explose for debugging reasons 15795 data : data, 15796 15797 typing : false, 15798 15799 onBeforeAdd: onBeforeAdd, 15800 15801 onAdd : onAdd, 15802 15803 onUndo : onUndo, 15804 15805 onRedo : onRedo, 15806 15807 beforeChange : function() { 15808 beforeBookmark = editor.selection.getBookmark(2, true); 15809 }, 15810 15811 add : function(level) { 15812 var i, settings = editor.settings, lastLevel; 15813 15814 level = level || {}; 15815 level.content = getContent(); 15816 15817 self.onBeforeAdd.dispatch(self, level); 15818 15819 // Add undo level if needed 15820 lastLevel = data[index]; 15821 if (lastLevel && lastLevel.content == level.content) 15822 return null; 15823 15824 // Set before bookmark on previous level 15825 if (data[index]) 15826 data[index].beforeBookmark = beforeBookmark; 15827 15828 // Time to compress 15829 if (settings.custom_undo_redo_levels) { 15830 if (data.length > settings.custom_undo_redo_levels) { 15831 for (i = 0; i < data.length - 1; i++) 15832 data[i] = data[i + 1]; 15833 15834 data.length--; 15835 index = data.length; 15836 } 15837 } 15838 15839 // Get a non intrusive normalized bookmark 15840 level.bookmark = editor.selection.getBookmark(2, true); 15841 15842 // Crop array if needed 15843 if (index < data.length - 1) 15844 data.length = index + 1; 15845 15846 data.push(level); 15847 index = data.length - 1; 15848 15849 self.onAdd.dispatch(self, level); 15850 editor.isNotDirty = 0; 15851 15852 return level; 15853 }, 15854 15855 undo : function() { 15856 var level, i; 15857 15858 if (self.typing) { 15859 self.add(); 15860 self.typing = false; 15861 } 15862 15863 if (index > 0) { 15864 level = data[--index]; 15865 15866 editor.setContent(level.content, {format : 'raw'}); 15867 editor.selection.moveToBookmark(level.beforeBookmark); 15868 15869 self.onUndo.dispatch(self, level); 15870 } 15871 15872 return level; 15873 }, 15874 15875 redo : function() { 15876 var level; 15877 15878 if (index < data.length - 1) { 15879 level = data[++index]; 15880 15881 editor.setContent(level.content, {format : 'raw'}); 15882 editor.selection.moveToBookmark(level.bookmark); 15883 15884 self.onRedo.dispatch(self, level); 15885 } 15886 15887 return level; 15888 }, 15889 15890 clear : function() { 15891 data = []; 15892 index = 0; 15893 self.typing = false; 15894 }, 15895 15896 hasUndo : function() { 15897 return index > 0 || this.typing; 15898 }, 15899 15900 hasRedo : function() { 15901 return index < data.length - 1 && !this.typing; 15902 } 15903 }; 15904 15905 return self; 15906 }; 15907})(tinymce); 15908 15909tinymce.ForceBlocks = function(editor) { 15910 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15911 15912 function addRootBlocks() { 15913 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15914 15915 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15916 return; 15917 15918 // Check if node is wrapped in block 15919 while (node && node != rootNode) { 15920 if (blockElements[node.nodeName]) 15921 return; 15922 15923 node = node.parentNode; 15924 } 15925 15926 // Get current selection 15927 rng = selection.getRng(); 15928 if (rng.setStart) { 15929 startContainer = rng.startContainer; 15930 startOffset = rng.startOffset; 15931 endContainer = rng.endContainer; 15932 endOffset = rng.endOffset; 15933 } else { 15934 // Force control range into text range 15935 if (rng.item) { 15936 node = rng.item(0); 15937 rng = editor.getDoc().body.createTextRange(); 15938 rng.moveToElementText(node); 15939 } 15940 15941 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15942 tmpRng = rng.duplicate(); 15943 tmpRng.collapse(true); 15944 startOffset = tmpRng.move('character', offset) * -1; 15945 15946 if (!tmpRng.collapsed) { 15947 tmpRng = rng.duplicate(); 15948 tmpRng.collapse(false); 15949 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15950 } 15951 } 15952 15953 // Wrap non block elements and text nodes 15954 node = rootNode.firstChild; 15955 while (node) { 15956 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15957 // Remove empty text nodes 15958 if (node.nodeType === 3 && node.nodeValue.length == 0) { 15959 tempNode = node; 15960 node = node.nextSibling; 15961 dom.remove(tempNode); 15962 continue; 15963 } 15964 15965 if (!rootBlockNode) {
15966 rootBlockNode = dom.create(settings.forced_root_block); 15967 node.parentNode.insertBefore(rootBlockNode, node); 15968 wrapped = true; 15969 } 15970 15971 tempNode = node; 15972 node = node.nextSibling; 15973 rootBlockNode.appendChild(tempNode); 15974 } else { 15975 rootBlockNode = null; 15976 node = node.nextSibling; 15977 } 15978 } 15979 15980 if (wrapped) { 15981 if (rng.setStart) { 15982 rng.setStart(startContainer, startOffset); 15983 rng.setEnd(endContainer, endOffset); 15984 selection.setRng(rng); 15985 } else { 15986 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15987 if (isInEditorDocument) { 15988 try { 15989 rng = editor.getDoc().body.createTextRange(); 15990 rng.moveToElementText(rootNode); 15991 rng.collapse(true); 15992 rng.moveStart('character', startOffset); 15993 15994 if (endOffset > 0) 15995 rng.moveEnd('character', endOffset); 15996 15997 rng.select(); 15998 } catch (ex) { 15999 // Ignore 16000 } 16001 } 16002 } 16003 16004 editor.nodeChanged(); 16005 } 16006 }; 16007 16008 // Force root blocks 16009 if (settings.forced_root_block) { 16010 editor.onKeyUp.add(addRootBlocks); 16011 editor.onNodeChange.add(addRootBlocks); 16012 } 16013}; 16014 16015(function(tinymce) { 16016 // Shorten names 16017 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 16018 16019 tinymce.create('tinymce.ControlManager', { 16020 ControlManager : function(ed, s) { 16021 var t = this, i; 16022 16023 s = s || {}; 16024 t.editor = ed; 16025 t.controls = {}; 16026 t.onAdd = new tinymce.util.Dispatcher(t); 16027 t.onPostRender = new tinymce.util.Dispatcher(t); 16028 t.prefix = s.prefix || ed.id + '_'; 16029 t._cls = {}; 16030 16031 t.onPostRender.add(function() { 16032 each(t.controls, function(c) { 16033 c.postRender(); 16034 }); 16035 }); 16036 }, 16037 16038 get : function(id) { 16039 return this.controls[this.prefix + id] || this.controls[id]; 16040 }, 16041 16042 setActive : function(id, s) { 16043 var c = null; 16044 16045 if (c = this.get(id)) 16046 c.setActive(s); 16047 16048 return c; 16049 }, 16050 16051 setDisabled : function(id, s) { 16052 var c = null; 16053 16054 if (c = this.get(id)) 16055 c.setDisabled(s); 16056 16057 return c; 16058 }, 16059 16060 add : function(c) { 16061 var t = this; 16062 16063 if (c) { 16064 t.controls[c.id] = c; 16065 t.onAdd.dispatch(c, t); 16066 } 16067 16068 return c; 16069 }, 16070 16071 createControl : function(name) { 16072 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 16073 16074 // Build control factory cache 16075 if (!self.controlFactories) { 16076 self.controlFactories = []; 16077 each(editor.plugins, function(plugin) { 16078 if (plugin.createControl) { 16079 self.controlFactories.push(plugin); 16080 } 16081 }); 16082 } 16083 16084 // Create controls by asking cached factories 16085 factories = self.controlFactories; 16086 for (i = 0, l = factories.length; i < l; i++) { 16087 ctrl = factories[i].createControl(name, self); 16088 16089 if (ctrl) { 16090 return self.add(ctrl); 16091 } 16092 } 16093 16094 // Create sepearator 16095 if (name === "|" || name === "separator") { 16096 return self.createSeparator(); 16097 } 16098 16099 // Create control from button collection 16100 if (editor.buttons && (ctrl = editor.buttons[name])) { 16101 return self.createButton(name, ctrl); 16102 } 16103 16104 return self.add(ctrl); 16105 }, 16106 16107 createDropMenu : function(id, s, cc) { 16108 var t = this, ed = t.editor, c, bm, v, cls; 16109 16110 s = extend({ 16111 'class' : 'mceDropDown', 16112 constrain : ed.settings.constrain_menus 16113 }, s); 16114 16115 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 16116 if (v = ed.getParam('skin_variant')) 16117 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 16118 16119 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 16120 16121 id = t.prefix + id; 16122 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 16123 c = t.controls[id] = new cls(id, s); 16124 c.onAddItem.add(function(c, o) { 16125 var s = o.settings; 16126 16127 s.title = ed.getLang(s.title, s.title); 16128 16129 if (!s.onclick) { 16130 s.onclick = function(v) { 16131 if (s.cmd) 16132 ed.execCommand(s.cmd, s.ui || false, s.value); 16133 }; 16134 } 16135 }); 16136 16137 ed.onRemove.add(function() { 16138 c.destroy(); 16139 }); 16140 16141 // Fix for bug #1897785, #1898007 16142 if (tinymce.isIE) { 16143 c.onShowMenu.add(function() { 16144 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16145 ed.focus(); 16146 16147 bm = ed.selection.getBookmark(1); 16148 }); 16149 16150 c.onHideMenu.add(function() { 16151 if (bm) { 16152 ed.selection.moveToBookmark(bm); 16153 bm = 0; 16154 } 16155 }); 16156 } 16157 16158 return t.add(c); 16159 }, 16160 16161 createListBox : function(id, s, cc) { 16162 var t = this, ed = t.editor, cmd, c, cls; 16163 16164 if (t.get(id)) 16165 return null; 16166 16167 s.title = ed.translate(s.title); 16168 s.scope = s.scope || ed; 16169 16170 if (!s.onselect) { 16171 s.onselect = function(v) { 16172 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16173 }; 16174 } 16175 16176 s = extend({ 16177 title : s.title, 16178 'class' : 'mce_' + id, 16179 scope : s.scope, 16180 control_manager : t 16181 }, s); 16182 16183 id = t.prefix + id; 16184 16185 16186 function useNativeListForAccessibility(ed) { 16187 return ed.settings.use_accessible_selects && !tinymce.isGecko 16188 } 16189 16190 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 16191 c = new tinymce.ui.NativeListBox(id, s); 16192 else { 16193 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 16194 c = new cls(id, s, ed); 16195 } 16196 16197 t.controls[id] = c; 16198 16199 // Fix focus problem in Safari 16200 if (tinymce.isWebKit) { 16201 c.onPostRender.add(function(c, n) { 16202 // Store bookmark on mousedown 16203 Event.add(n, 'mousedown', function() { 16204 ed.bookmark = ed.selection.getBookmark(1); 16205 }); 16206 16207 // Restore on focus, since it might be lost 16208 Event.add(n, 'focus', function() { 16209 ed.selection.moveToBookmark(ed.bookmark); 16210 ed.bookmark = null; 16211 }); 16212 }); 16213 } 16214 16215 if (c.hideMenu) 16216 ed.onMouseDown.add(c.hideMenu, c); 16217 16218 return t.add(c); 16219 }, 16220 16221 createButton : function(id, s, cc) { 16222 var t = this, ed = t.editor, o, c, cls; 16223 16224 if (t.get(id)) 16225 return null; 16226 16227 s.title = ed.translate(s.title); 16228 s.label = ed.translate(s.label); 16229 s.scope = s.scope || ed; 16230 16231 if (!s.onclick && !s.menu_button) { 16232 s.onclick = function() { 16233 ed.execCommand(s.cmd, s.ui || false, s.value); 16234 }; 16235 } 16236 16237 s = extend({ 16238 title : s.title, 16239 'class' : 'mce_' + id, 16240 unavailable_prefix : ed.getLang('unavailable', ''), 16241 scope : s.scope, 16242 control_manager : t 16243 }, s); 16244 16245 id = t.prefix + id; 16246 16247 if (s.menu_button) { 16248 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 16249 c = new cls(id, s, ed); 16250 ed.onMouseDown.add(c.hideMenu, c); 16251 } else { 16252 cls = t._cls.button || tinymce.ui.Button; 16253 c = new cls(id, s, ed); 16254 } 16255 16256 return t.add(c); 16257 }, 16258 16259 createMenuButton : function(id, s, cc) { 16260 s = s || {}; 16261 s.menu_button = 1; 16262 16263 return this.createButton(id, s, cc); 16264 }, 16265 16266 createSplitButton : function(id, s, cc) { 16267 var t = this, ed = t.editor, cmd, c, cls; 16268 16269 if (t.get(id)) 16270 return null; 16271 16272 s.title = ed.translate(s.title); 16273 s.scope = s.scope || ed; 16274 16275 if (!s.onclick) { 16276 s.onclick = function(v) { 16277 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16278 }; 16279 } 16280 16281 if (!s.onselect) { 16282 s.onselect = function(v) { 16283 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16284 }; 16285 } 16286 16287 s = extend({ 16288 title : s.title, 16289 'class' : 'mce_' + id, 16290 scope : s.scope, 16291 control_manager : t 16292 }, s); 16293 16294 id = t.prefix + id; 16295 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 16296 c = t.add(new cls(id, s, ed)); 16297 ed.onMouseDown.add(c.hideMenu, c); 16298 16299 return c; 16300 }, 16301 16302 createColorSplitButton : function(id, s, cc) { 16303 var t = this, ed = t.editor, cmd, c, cls, bm; 16304 16305 if (t.get(id)) 16306 return null; 16307 16308 s.title = ed.translate(s.title); 16309 s.scope = s.scope || ed; 16310 16311 if (!s.onclick) { 16312 s.onclick = function(v) { 16313 if (tinymce.isIE) 16314 bm = ed.selection.getBookmark(1); 16315 16316 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16317 }; 16318 } 16319 16320 if (!s.onselect) { 16321 s.onselect = function(v) { 16322 ed.execCommand(s.cmd, s.ui || false, v || s.value); 16323 }; 16324 } 16325 16326 s = extend({ 16327 title : s.title, 16328 'class' : 'mce_' + id, 16329 'menu_class' : ed.getParam('skin') + 'Skin', 16330 scope : s.scope, 16331 more_colors_title : ed.getLang('more_colors') 16332 }, s); 16333 16334 id = t.prefix + id; 16335 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 16336 c = new cls(id, s, ed); 16337 ed.onMouseDown.add(c.hideMenu, c); 16338 16339 // Remove the menu element when the editor is removed 16340 ed.onRemove.add(function() { 16341 c.destroy(); 16342 }); 16343 16344 // Fix for bug #1897785, #1898007 16345 if (tinymce.isIE) { 16346 c.onShowMenu.add(function() { 16347 // IE 8 needs focus in order to store away a range with the current collapsed caret location 16348 ed.focus(); 16349 bm = ed.selection.getBookmark(1); 16350 }); 16351 16352 c.onHideMenu.add(function() { 16353 if (bm) { 16354 ed.selection.moveToBookmark(bm); 16355 bm = 0; 16356 } 16357 }); 16358 } 16359 16360 return t.add(c); 16361 }, 16362 16363 createToolbar : function(id, s, cc) { 16364 var c, t = this, cls; 16365 16366 id = t.prefix + id; 16367 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 16368 c = new cls(id, s, t.editor); 16369 16370 if (t.get(id)) 16371 return null; 16372 16373 return t.add(c); 16374 }, 16375 16376 createToolbarGroup : function(id, s, cc) { 16377 var c, t = this, cls; 16378 id = t.prefix + id; 16379 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 16380 c = new cls(id, s, t.editor); 16381 16382 if (t.get(id)) 16383 return null; 16384 16385 return t.add(c); 16386 }, 16387 16388 createSeparator : function(cc) { 16389 var cls = cc || this._cls.separator || tinymce.ui.Separator; 16390 16391 return new cls(); 16392 }, 16393 16394 setControlType : function(n, c) { 16395 return this._cls[n.toLowerCase()] = c; 16396 }, 16397 16398 destroy : function() { 16399 each(this.controls, function(c) { 16400 c.destroy(); 16401 }); 16402 16403 this.controls = null; 16404 } 16405 }); 16406})(tinymce); 16407 16408(function(tinymce) { 16409 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 16410 16411 tinymce.create('tinymce.WindowManager', { 16412 WindowManager : function(ed) { 16413 var t = this; 16414 16415 t.editor = ed; 16416 t.onOpen = new Dispatcher(t); 16417 t.onClose = new Dispatcher(t); 16418 t.params = {}; 16419 t.features = {}; 16420 }, 16421 16422 open : function(s, p) { 16423 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 16424 16425 // Default some options 16426 s = s || {}; 16427 p = p || {}; 16428 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 16429 sh = isOpera ? vp.h : screen.height; 16430 s.name = s.name || 'mc_' + new Date().getTime(); 16431 s.width = parseInt(s.width || 320); 16432 s.height = parseInt(s.height || 240); 16433 s.resizable = true; 16434 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 16435 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 16436 p.inline = false;
16437 p.mce_width = s.width; 16438 p.mce_height = s.height; 16439 p.mce_auto_focus = s.auto_focus; 16440 16441 if (mo) { 16442 if (isIE) { 16443 s.center = true; 16444 s.help = false; 16445 s.dialogWidth = s.width + 'px'; 16446 s.dialogHeight = s.height + 'px'; 16447 s.scroll = s.scrollbars || false; 16448 } 16449 } 16450 16451 // Build features string 16452 each(s, function(v, k) { 16453 if (tinymce.is(v, 'boolean')) 16454 v = v ? 'yes' : 'no'; 16455 16456 if (!/^(name|url)$/.test(k)) { 16457 if (isIE && mo) 16458 f += (f ? ';' : '') + k + ':' + v; 16459 else 16460 f += (f ? ',' : '') + k + '=' + v; 16461 } 16462 }); 16463 16464 t.features = s; 16465 t.params = p; 16466 t.onOpen.dispatch(t, s, p); 16467 16468 u = s.url || s.file; 16469 u = tinymce._addVer(u); 16470 16471 try { 16472 if (isIE && mo) { 16473 w = 1; 16474 window.showModalDialog(u, window, f); 16475 } else 16476 w = window.open(u, s.name, f); 16477 } catch (ex) { 16478 // Ignore 16479 } 16480 16481 if (!w) 16482 alert(t.editor.getLang('popup_blocked')); 16483 }, 16484 16485 close : function(w) { 16486 w.close(); 16487 this.onClose.dispatch(this); 16488 }, 16489 16490 createInstance : function(cl, a, b, c, d, e) { 16491 var f = tinymce.resolve(cl); 16492 16493 return new f(a, b, c, d, e); 16494 }, 16495 16496 confirm : function(t, cb, s, w) { 16497 w = w || window; 16498 16499 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16500 }, 16501 16502 alert : function(tx, cb, s, w) { 16503 var t = this; 16504 16505 w = w || window; 16506 w.alert(t._decode(t.editor.getLang(tx, tx))); 16507 16508 if (cb) 16509 cb.call(s || t); 16510 }, 16511 16512 resizeBy : function(dw, dh, win) { 16513 win.resizeBy(dw, dh); 16514 }, 16515 16516 // Internal functions 16517 16518 _decode : function(s) {
16519 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16520 } 16521 }); 16522}(tinymce)); 16523(function(tinymce) { 16524 tinymce.Formatter = function(ed) { 16525 var formats = {}, 16526 each = tinymce.each, 16527 dom = ed.dom, 16528 selection = ed.selection, 16529 TreeWalker = tinymce.dom.TreeWalker, 16530 rangeUtils = new tinymce.dom.RangeUtils(dom), 16531 isValid = ed.schema.isValidChild, 16532 isArray = tinymce.isArray, 16533 isBlock = dom.isBlock, 16534 forcedRootBlock = ed.settings.forced_root_block, 16535 nodeIndex = dom.nodeIndex, 16536 INVISIBLE_CHAR = '\uFEFF', 16537 MCE_ATTR_RE = /^(src|href|style)$/, 16538 FALSE = false, 16539 TRUE = true, 16540 formatChangeData, 16541 undef, 16542 getContentEditable = dom.getContentEditable; 16543 16544 function isTextBlock(name) { 16545 if (name.nodeType) { 16546 name = name.nodeName; 16547 } 16548 16549 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 16550 } 16551 16552 function getParents(node, selector) { 16553 return dom.getParents(node, selector, dom.getRoot()); 16554 }; 16555 16556 function isCaretNode(node) { 16557 return node.nodeType === 1 && node.id === '_mce_caret'; 16558 }; 16559 16560 function defaultFormats() { 16561 register({ 16562 alignleft : [ 16563 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16564 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16565 ], 16566 16567 aligncenter : [ 16568 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16569 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16570 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16571 ], 16572 16573 alignright : [ 16574 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16575 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16576 ], 16577 16578 alignfull : [ 16579 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16580 ], 16581 16582 bold : [ 16583 {inline : 'strong', remove : 'all'}, 16584 {inline : 'span', styles : {fontWeight : 'bold'}}, 16585 {inline : 'b', remove : 'all'} 16586 ], 16587 16588 italic : [ 16589 {inline : 'em', remove : 'all'}, 16590 {inline : 'span', styles : {fontStyle : 'italic'}}, 16591 {inline : 'i', remove : 'all'} 16592 ], 16593 16594 underline : [ 16595 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16596 {inline : 'u', remove : 'all'} 16597 ], 16598 16599 strikethrough : [ 16600 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16601 {inline : 'strike', remove : 'all'} 16602 ], 16603 16604 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16605 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16606 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16607 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16608 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16609 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16610 subscript : {inline : 'sub'}, 16611 superscript : {inline : 'sup'}, 16612 16613 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16614 onmatch : function(node) { 16615 return true; 16616 }, 16617 16618 onformat : function(elm, fmt, vars) { 16619 each(vars, function(value, key) { 16620 dom.setAttrib(elm, key, value); 16621 }); 16622 } 16623 }, 16624 16625 removeformat : [ 16626 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16627 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16628 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16629 ] 16630 }); 16631 16632 // Register default block formats 16633 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16634 register(name, {block : name, remove : 'all'}); 16635 }); 16636 16637 // Register user defined formats 16638 register(ed.settings.formats); 16639 }; 16640 16641 function addKeyboardShortcuts() { 16642 // Add some inline shortcuts 16643 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16644 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16645 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16646 16647 // BlockFormat shortcuts keys 16648 for (var i = 1; i <= 6; i++) { 16649 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16650 } 16651 16652 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16653 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16654 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16655 }; 16656 16657 // Public functions 16658 16659 function get(name) { 16660 return name ? formats[name] : formats; 16661 }; 16662 16663 function register(name, format) { 16664 if (name) { 16665 if (typeof(name) !== 'string') { 16666 each(name, function(format, name) { 16667 register(name, format); 16668 }); 16669 } else { 16670 // Force format into array and add it to internal collection 16671 format = format.length ? format : [format]; 16672 16673 each(format, function(format) { 16674 // Set deep to false by default on selector formats this to avoid removing 16675 // alignment on images inside paragraphs when alignment is changed on paragraphs 16676 if (format.deep === undef) 16677 format.deep = !format.selector; 16678 16679 // Default to true 16680 if (format.split === undef) 16681 format.split = !format.selector || format.inline; 16682 16683 // Default to true 16684 if (format.remove === undef && format.selector && !format.inline) 16685 format.remove = 'none'; 16686 16687 // Mark format as a mixed format inline + block level 16688 if (format.selector && format.inline) { 16689 format.mixed = true; 16690 format.block_expand = true; 16691 } 16692 16693 // Split classes if needed 16694 if (typeof(format.classes) === 'string') 16695 format.classes = format.classes.split(/\s+/); 16696 }); 16697 16698 formats[name] = format; 16699 } 16700 } 16701 }; 16702 16703 var getTextDecoration = function(node) { 16704 var decoration; 16705 16706 ed.dom.getParent(node, function(n) { 16707 decoration = ed.dom.getStyle(n, 'text-decoration'); 16708 return decoration && decoration !== 'none'; 16709 }); 16710 16711 return decoration; 16712 }; 16713 16714 var processUnderlineAndColor = function(node) { 16715 var textDecoration; 16716 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16717 textDecoration = getTextDecoration(node.parentNode); 16718 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16719 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16720 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16721 ed.dom.setStyle(node, 'text-decoration', null); 16722 } 16723 } 16724 }; 16725 16726 function apply(name, vars, node) { 16727 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16728 16729 function setElementFormat(elm, fmt) { 16730 fmt = fmt || format; 16731 16732 if (elm) { 16733 if (fmt.onformat) { 16734 fmt.onformat(elm, fmt, vars, node); 16735 } 16736 16737 each(fmt.styles, function(value, name) { 16738 dom.setStyle(elm, name, replaceVars(value, vars)); 16739 }); 16740 16741 each(fmt.attributes, function(value, name) { 16742 dom.setAttrib(elm, name, replaceVars(value, vars)); 16743 }); 16744 16745 each(fmt.classes, function(value) { 16746 value = replaceVars(value, vars); 16747 if (!dom.hasClass(elm, value)) 16748 dom.addClass(elm, value); 16749 }); 16750 } 16751 }; 16752 function adjustSelectionToVisibleSelection() { 16753 function findSelectionEnd(start, end) { 16754 var walker = new TreeWalker(end); 16755 for (node = walker.current(); node; node = walker.prev()) { 16756 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16757 return node; 16758 } 16759 } 16760 }; 16761 16762 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16763 // as this isn't visible to the user. 16764 var rng = ed.selection.getRng(); 16765 var start = rng.startContainer; 16766 var end = rng.endContainer; 16767 16768 if (start != end && rng.endOffset === 0) { 16769 var newEnd = findSelectionEnd(start, end); 16770 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16771 16772 rng.setEnd(newEnd, endOffset); 16773 } 16774 16775 return rng; 16776 } 16777 16778 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16779 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16780 16781 // find the index of the first child list. 16782 each(node.childNodes, function(n, index) { 16783 if (n.nodeName === "UL" || n.nodeName === "OL") { 16784 listIndex = index; 16785 list = n; 16786 return false;
16787 } 16788 }); 16789 16790 // get the index of the bookmarks 16791 each(node.childNodes, function(n, index) { 16792 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16793 if (n.id == bookmark.id + "_start") { 16794 startIndex = index; 16795 } else if (n.id == bookmark.id + "_end") { 16796 endIndex = index; 16797 } 16798 } 16799 }); 16800 16801 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16802 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16803 each(tinymce.grep(node.childNodes), process); 16804 return 0; 16805 } else { 16806 currentWrapElm = dom.clone(wrapElm, FALSE); 16807 16808 // create a list of the nodes on the same side of the list as the selection 16809 each(tinymce.grep(node.childNodes), function(n, index) { 16810 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16811 nodes.push(n); 16812 n.parentNode.removeChild(n); 16813 } 16814 }); 16815 16816 // insert the wrapping element either before or after the list. 16817 if (startIndex < listIndex) { 16818 node.insertBefore(currentWrapElm, list); 16819 } else if (startIndex > listIndex) { 16820 node.insertBefore(currentWrapElm, list.nextSibling); 16821 } 16822 16823 // add the new nodes to the list. 16824 newWrappers.push(currentWrapElm); 16825 16826 each(nodes, function(node) { 16827 currentWrapElm.appendChild(node); 16828 }); 16829 16830 return currentWrapElm; 16831 } 16832 }; 16833 16834 function applyRngStyle(rng, bookmark, node_specific) { 16835 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16836 16837 // Setup wrapper element 16838 wrapName = format.inline || format.block; 16839 wrapElm = dom.create(wrapName); 16840 setElementFormat(wrapElm); 16841 16842 rangeUtils.walk(rng, function(nodes) { 16843 var currentWrapElm; 16844 16845 function process(node) { 16846 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16847 16848 lastContentEditable = contentEditable; 16849 nodeName = node.nodeName.toLowerCase(); 16850 parentName = node.parentNode.nodeName.toLowerCase(); 16851 16852 // Node has a contentEditable value 16853 if (node.nodeType === 1 && getContentEditable(node)) { 16854 lastContentEditable = contentEditable; 16855 contentEditable = getContentEditable(node) === "true"; 16856 hasContentEditableState = true; // We don't want to wrap the container only it's children 16857 } 16858 16859 // Stop wrapping on br elements 16860 if (isEq(nodeName, 'br')) { 16861 currentWrapElm = 0; 16862 16863 // Remove any br elements when we wrap things 16864 if (format.block) 16865 dom.remove(node); 16866 16867 return; 16868 } 16869 16870 // If node is wrapper type 16871 if (format.wrapper && matchNode(node, name, vars)) { 16872 currentWrapElm = 0; 16873 return; 16874 } 16875 16876 // Can we rename the block 16877 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16878 node = dom.rename(node, wrapName); 16879 setElementFormat(node); 16880 newWrappers.push(node); 16881 currentWrapElm = 0; 16882 return; 16883 } 16884 16885 // Handle selector patterns 16886 if (format.selector) { 16887 // Look for matching formats 16888 each(formatList, function(format) { 16889 // Check collapsed state if it exists 16890 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16891 return; 16892 } 16893 16894 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16895 setElementFormat(node, format); 16896 found = true; 16897 } 16898 }); 16899 16900 // Continue processing if a selector match wasn't found and a inline element is defined 16901 if (!format.inline || found) { 16902 currentWrapElm = 0; 16903 return; 16904 } 16905 } 16906 16907 // Is it valid to wrap this item 16908 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16909 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) { 16910 // Start wrapping 16911 if (!currentWrapElm) { 16912 // Wrap the node 16913 currentWrapElm = dom.clone(wrapElm, FALSE); 16914 node.parentNode.insertBefore(currentWrapElm, node); 16915 newWrappers.push(currentWrapElm); 16916 } 16917 16918 currentWrapElm.appendChild(node); 16919 } else if (nodeName == 'li' && bookmark) { 16920 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16921 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16922 } else { 16923 // Start a new wrapper for possible children 16924 currentWrapElm = 0; 16925 16926 each(tinymce.grep(node.childNodes), process); 16927 16928 if (hasContentEditableState) {
16929 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16930 } 16931 16932 // End the last wrapper 16933 currentWrapElm = 0; 16934 } 16935 }; 16936 16937 // Process siblings from range 16938 each(nodes, process); 16939 }); 16940 16941 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16942 if (format.wrap_links === false) { 16943 each(newWrappers, function(node) { 16944 function process(node) { 16945 var i, currentWrapElm, children; 16946 16947 if (node.nodeName === 'A') { 16948 currentWrapElm = dom.clone(wrapElm, FALSE); 16949 newWrappers.push(currentWrapElm); 16950 16951 children = tinymce.grep(node.childNodes); 16952 for (i = 0; i < children.length; i++) 16953 currentWrapElm.appendChild(children[i]); 16954 16955 node.appendChild(currentWrapElm); 16956 } 16957 16958 each(tinymce.grep(node.childNodes), process); 16959 }; 16960 16961 process(node); 16962 }); 16963 } 16964 16965 // Cleanup 16966 16967 each(newWrappers, function(node) { 16968 var childCount; 16969 16970 function getChildCount(node) { 16971 var count = 0; 16972 16973 each(node.childNodes, function(node) { 16974 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16975 count++; 16976 }); 16977 16978 return count; 16979 }; 16980 16981 function mergeStyles(node) { 16982 var child, clone; 16983 16984 each(node.childNodes, function(node) { 16985 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16986 child = node; 16987 return FALSE; // break loop 16988 } 16989 }); 16990 16991 // If child was found and of the same type as the current node 16992 if (child && matchName(child, format)) { 16993 clone = dom.clone(child, FALSE); 16994 setElementFormat(clone); 16995 16996 dom.replace(clone, node, TRUE); 16997 dom.remove(child, 1); 16998 } 16999 17000 return clone || node; 17001 }; 17002 17003 childCount = getChildCount(node); 17004 17005 // Remove empty nodes but only if there is multiple wrappers and they are not block 17006 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 17007 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 17008 dom.remove(node, 1); 17009 return; 17010 } 17011 17012 if (format.inline || format.wrapper) { 17013 // Merges the current node with it's children of similar type to reduce the number of elements 17014 if (!format.exact && childCount === 1) 17015 node = mergeStyles(node); 17016 17017 // Remove/merge children 17018 each(formatList, function(format) { 17019 // Merge all children of similar type will move styles from child to parent 17020 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 17021 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 17022 each(dom.select(format.inline, node), function(child) { 17023 var parent; 17024 17025 // When wrap_links is set to false we don't want 17026 // to remove the format on children within links 17027 if (format.wrap_links === false) { 17028 parent = child.parentNode; 17029 17030 do { 17031 if (parent.nodeName === 'A') 17032 return; 17033 } while (parent = parent.parentNode); 17034 } 17035 17036 removeFormat(format, vars, child, format.exact ? child : null); 17037 }); 17038 }); 17039 17040 // Remove child if direct parent is of same type 17041 if (matchNode(node.parentNode, name, vars)) { 17042 dom.remove(node, 1); 17043 node = 0; 17044 return TRUE; 17045 } 17046 17047 // Look for parent with similar style format 17048 if (format.merge_with_parents) { 17049 dom.getParent(node.parentNode, function(parent) { 17050 if (matchNode(parent, name, vars)) { 17051 dom.remove(node, 1); 17052 node = 0; 17053 return TRUE; 17054 } 17055 }); 17056 } 17057 17058 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 17059 if (node && format.merge_siblings !== false) { 17060 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 17061 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 17062 } 17063 } 17064 }); 17065 }; 17066 17067 if (format) { 17068 if (node) { 17069 if (node.nodeType) { 17070 rng = dom.createRng(); 17071 rng.setStartBefore(node); 17072 rng.setEndAfter(node); 17073 applyRngStyle(expandRng(rng, formatList), null, true); 17074 } else { 17075 applyRngStyle(node, null, true); 17076 } 17077 } else { 17078 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 17079 // Obtain selection node before selection is unselected by applyRngStyle() 17080 var curSelNode = ed.selection.getNode(); 17081 17082 // 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 17083 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 17084 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 17085 apply(formatList[0].defaultBlock); 17086 } 17087 17088 // Apply formatting to selection 17089 ed.selection.setRng(adjustSelectionToVisibleSelection()); 17090 bookmark = selection.getBookmark(); 17091 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 17092 17093 // Colored nodes should be underlined so that the color of the underline matches the text color. 17094 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 17095 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
17096 processUnderlineAndColor(curSelNode); 17097 } 17098 17099 selection.moveToBookmark(bookmark); 17100 moveStart(selection.getRng(TRUE)); 17101 ed.nodeChanged(); 17102 } else 17103 performCaretAction('apply', name, vars); 17104 } 17105 } 17106 }; 17107 17108 function remove(name, vars, node) { 17109 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 17110 17111 // Merges the styles for each node 17112 function process(node) { 17113 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 17114 17115 // Skip on text nodes as they have neither format to remove nor children 17116 if (node.nodeType === 3) { 17117 return; 17118 } 17119 17120 // Node has a contentEditable value 17121 if (node.nodeType === 1 && getContentEditable(node)) { 17122 lastContentEditable = contentEditable; 17123 contentEditable = getContentEditable(node) === "true"; 17124 hasContentEditableState = true; // We don't want to wrap the container only it's children 17125 } 17126 17127 // Grab the children first since the nodelist might be changed 17128 children = tinymce.grep(node.childNodes); 17129 // Process current node 17130 if (contentEditable && !hasContentEditableState) { 17131 for (i = 0, l = formatList.length; i < l; i++) { 17132 if (removeFormat(formatList[i], vars, node, node)) 17133 break; 17134 } 17135 } 17136 17137 // Process the children 17138 if (format.deep) { 17139 if (children.length) { 17140 for (i = 0, l = children.length; i < l; i++) 17141 process(children[i]); 17142 17143 if (hasContentEditableState) { 17144 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 17145 } 17146 } 17147 } 17148 }; 17149 17150 function findFormatRoot(container) { 17151 var formatRoot; 17152 17153 // Find format root 17154 each(getParents(container.parentNode).reverse(), function(parent) { 17155 var format; 17156 17157 // Find format root element 17158 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 17159 // Is the node matching the format we are looking for 17160 format = matchNode(parent, name, vars); 17161 if (format && format.split !== false) 17162 formatRoot = parent; 17163 } 17164 }); 17165 17166 return formatRoot; 17167 }; 17168 17169 function wrapAndSplit(format_root, container, target, split) { 17170 var parent, clone, lastClone, firstClone, i, formatRootParent; 17171 17172 // Format root found then clone formats and split it 17173 if (format_root) { 17174 formatRootParent = format_root.parentNode; 17175 17176 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 17177 clone = dom.clone(parent, FALSE); 17178 17179 for (i = 0; i < formatList.length; i++) { 17180 if (removeFormat(formatList[i], vars, clone, clone)) { 17181 clone = 0; 17182 break; 17183 } 17184 } 17185 17186 // Build wrapper node 17187 if (clone) { 17188 if (lastClone) 17189 clone.appendChild(lastClone); 17190 17191 if (!firstClone) 17192 firstClone = clone; 17193 17194 lastClone = clone; 17195 } 17196 } 17197 17198 // Never split block elements if the format is mixed 17199 if (split && (!format.mixed || !isBlock(format_root))) 17200 container = dom.split(format_root, container); 17201 17202 // Wrap container in cloned formats 17203 if (lastClone) { 17204 target.parentNode.insertBefore(lastClone, target); 17205 firstClone.appendChild(target); 17206 } 17207 } 17208 17209 return container; 17210 }; 17211 17212 function splitToFormatRoot(container) { 17213 return wrapAndSplit(findFormatRoot(container), container, container, true); 17214 }; 17215 17216 function unwrap(start) { 17217 var node = dom.get(start ? '_start' : '_end'), 17218 out = node[start ? 'firstChild' : 'lastChild']; 17219 17220 // If the end is placed within the start the result will be removed 17221 // So this checks if the out node is a bookmark node if it is it 17222 // checks for another more suitable node 17223 if (isBookmarkNode(out)) 17224 out = out[start ? 'firstChild' : 'lastChild']; 17225 17226 dom.remove(node, true); 17227 17228 return out; 17229 }; 17230 17231 function removeRngStyle(rng) { 17232 var startContainer, endContainer, node; 17233 17234 rng = expandRng(rng, formatList, TRUE); 17235 17236 if (format.split) { 17237 startContainer = getContainer(rng, TRUE); 17238 endContainer = getContainer(rng); 17239 17240 if (startContainer != endContainer) { 17241 // 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 17242 // This will happen if you tripple click a table cell and use remove formatting 17243 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 17244 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 17245 } 17246 17247 // Wrap start/end nodes in span element since these might be cloned/moved 17248 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
17249 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 17250 17251 // Split start/end 17252 splitToFormatRoot(startContainer); 17253 splitToFormatRoot(endContainer); 17254 17255 // Unwrap start/end to get real elements again 17256 startContainer = unwrap(TRUE); 17257 endContainer = unwrap(); 17258 } else 17259 startContainer = endContainer = splitToFormatRoot(startContainer); 17260 17261 // Update range positions since they might have changed after the split operations 17262 rng.startContainer = startContainer.parentNode; 17263 rng.startOffset = nodeIndex(startContainer); 17264 rng.endContainer = endContainer.parentNode; 17265 rng.endOffset = nodeIndex(endContainer) + 1; 17266 } 17267 17268 // Remove items between start/end 17269 rangeUtils.walk(rng, function(nodes) { 17270 each(nodes, function(node) { 17271 process(node); 17272 17273 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 17274 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 17275 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 17276 } 17277 }); 17278 }); 17279 }; 17280 17281 // Handle node 17282 if (node) { 17283 if (node.nodeType) { 17284 rng = dom.createRng(); 17285 rng.setStartBefore(node); 17286 rng.setEndAfter(node); 17287 removeRngStyle(rng); 17288 } else { 17289 removeRngStyle(node); 17290 } 17291 17292 return; 17293 } 17294 17295 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 17296 bookmark = selection.getBookmark(); 17297 removeRngStyle(selection.getRng(TRUE)); 17298 selection.moveToBookmark(bookmark); 17299 17300 // 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 17301 if (format.inline && match(name, vars, selection.getStart())) { 17302 moveStart(selection.getRng(true)); 17303 } 17304 17305 ed.nodeChanged(); 17306 } else 17307 performCaretAction('remove', name, vars); 17308 }; 17309 17310 function toggle(name, vars, node) { 17311 var fmt = get(name); 17312 17313 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 17314 remove(name, vars, node); 17315 else 17316 apply(name, vars, node); 17317 }; 17318 17319 function matchNode(node, name, vars, similar) { 17320 var formatList = get(name), format, i, classes; 17321 17322 function matchItems(node, format, item_name) { 17323 var key, value, items = format[item_name], i; 17324 17325 // Custom match 17326 if (format.onmatch) { 17327 return format.onmatch(node, format, item_name); 17328 } 17329 17330 // Check all items 17331 if (items) { 17332 // Non indexed object 17333 if (items.length === undef) { 17334 for (key in items) { 17335 if (items.hasOwnProperty(key)) { 17336 if (item_name === 'attributes') 17337 value = dom.getAttrib(node, key); 17338 else 17339 value = getStyle(node, key); 17340 17341 if (similar && !value && !format.exact) 17342 return; 17343 17344 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 17345 return; 17346 } 17347 } 17348 } else { 17349 // Only one match needed for indexed arrays 17350 for (i = 0; i < items.length; i++) { 17351 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 17352 return format; 17353 } 17354 } 17355 } 17356 17357 return format; 17358 }; 17359 17360 if (formatList && node) { 17361 // Check each format in list 17362 for (i = 0; i < formatList.length; i++) { 17363 format = formatList[i]; 17364 17365 // Name name, attributes, styles and classes 17366 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 17367 // Match classes 17368 if (classes = format.classes) { 17369 for (i = 0; i < classes.length; i++) { 17370 if (!dom.hasClass(node, classes[i])) 17371 return; 17372 } 17373 } 17374 17375 return format; 17376 } 17377 } 17378 } 17379 }; 17380 17381 function match(name, vars, node) { 17382 var startNode; 17383 17384 function matchParents(node) { 17385 // Find first node with similar format settings 17386 node = dom.getParent(node, function(node) { 17387 return !!matchNode(node, name, vars, true); 17388 }); 17389 17390 // Do an exact check on the similar format element 17391 return matchNode(node, name, vars); 17392 }; 17393
17394 // Check specified node 17395 if (node) 17396 return matchParents(node); 17397 17398 // Check selected node 17399 node = selection.getNode(); 17400 if (matchParents(node)) 17401 return TRUE; 17402 17403 // Check start node if it's different 17404 startNode = selection.getStart(); 17405 if (startNode != node) { 17406 if (matchParents(startNode)) 17407 return TRUE; 17408 } 17409 17410 return FALSE; 17411 }; 17412 17413 function matchAll(names, vars) { 17414 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 17415 17416 // Check start of selection for formats 17417 startElement = selection.getStart(); 17418 dom.getParent(startElement, function(node) { 17419 var i, name; 17420 17421 for (i = 0; i < names.length; i++) { 17422 name = names[i]; 17423 17424 if (!checkedMap[name] && matchNode(node, name, vars)) { 17425 checkedMap[name] = true; 17426 matchedFormatNames.push(name); 17427 } 17428 } 17429 }, dom.getRoot()); 17430 17431 return matchedFormatNames; 17432 }; 17433 17434 function canApply(name) { 17435 var formatList = get(name), startNode, parents, i, x, selector; 17436 17437 if (formatList) { 17438 startNode = selection.getStart(); 17439 parents = getParents(startNode); 17440 17441 for (x = formatList.length - 1; x >= 0; x--) { 17442 selector = formatList[x].selector; 17443 17444 // Format is not selector based, then always return TRUE 17445 if (!selector) 17446 return TRUE; 17447 17448 for (i = parents.length - 1; i >= 0; i--) { 17449 if (dom.is(parents[i], selector)) 17450 return TRUE; 17451 } 17452 } 17453 } 17454 17455 return FALSE; 17456 }; 17457 17458 function formatChanged(formats, callback, similar) { 17459 var currentFormats; 17460 17461 // Setup format node change logic 17462 if (!formatChangeData) { 17463 formatChangeData = {}; 17464 currentFormats = {}; 17465 17466 ed.onNodeChange.addToTop(function(ed, cm, node) { 17467 var parents = getParents(node), matchedFormats = {}; 17468 17469 // Check for new formats 17470 each(formatChangeData, function(callbacks, format) { 17471 each(parents, function(node) { 17472 if (matchNode(node, format, {}, callbacks.similar)) { 17473 if (!currentFormats[format]) { 17474 // Execute callbacks 17475 each(callbacks, function(callback) { 17476 callback(true, {node: node, format: format, parents: parents}); 17477 }); 17478 17479 currentFormats[format] = callbacks; 17480 } 17481 17482 matchedFormats[format] = callbacks; 17483 return false; 17484 } 17485 }); 17486 }); 17487 17488 // Check if current formats still match 17489 each(currentFormats, function(callbacks, format) { 17490 if (!matchedFormats[format]) { 17491 delete currentFormats[format]; 17492 17493 each(callbacks, function(callback) { 17494 callback(false, {node: node, format: format, parents: parents}); 17495 }); 17496 } 17497 }); 17498 }); 17499 } 17500 17501 // Add format listeners 17502 each(formats.split(','), function(format) { 17503 if (!formatChangeData[format]) { 17504 formatChangeData[format] = []; 17505 formatChangeData[format].similar = similar; 17506 } 17507 17508 formatChangeData[format].push(callback); 17509 }); 17510 17511 return this; 17512 }; 17513 17514 // Expose to public 17515 tinymce.extend(this, { 17516 get : get, 17517 register : register, 17518 apply : apply, 17519 remove : remove, 17520 toggle : toggle, 17521 match : match, 17522 matchAll : matchAll, 17523 matchNode : matchNode, 17524 canApply : canApply, 17525 formatChanged: formatChanged 17526 }); 17527 17528 // Initialize 17529 defaultFormats(); 17530 addKeyboardShortcuts(); 17531 17532 // Private functions 17533 17534 function matchName(node, format) { 17535 // Check for inline match 17536 if (isEq(node, format.inline)) 17537 return TRUE; 17538 17539 // Check for block match 17540 if (isEq(node, format.block)) 17541 return TRUE; 17542 17543 // Check for selector match 17544 if (format.selector) 17545 return dom.is(node, format.selector); 17546 }; 17547 17548 function isEq(str1, str2) { 17549 str1 = str1 || ''; 17550 str2 = str2 || ''; 17551 17552 str1 = '' + (str1.nodeName || str1); 17553 str2 = '' + (str2.nodeName || str2); 17554 17555 return str1.toLowerCase() == str2.toLowerCase(); 17556 }; 17557 17558 function getStyle(node, name) { 17559 var styleVal = dom.getStyle(node, name); 17560 17561 // Force the format to hex 17562 if (name == 'color' || name == 'backgroundColor') 17563 styleVal = dom.toHex(styleVal); 17564 17565 // Opera will return bold as 700 17566 if (name == 'fontWeight' && styleVal == 700) 17567 styleVal = 'bold'; 17568 17569 return '' + styleVal; 17570 }; 17571 17572 function replaceVars(value, vars) { 17573 if (typeof(value) != "string") 17574 value = value(vars); 17575 else if (vars) { 17576 value = value.replace(/%(\w+)/g, function(str, name) { 17577 return vars[name] || str; 17578 }); 17579 } 17580 17581 return value; 17582 }; 17583 17584 function isWhiteSpaceNode(node) { 17585 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17586 }; 17587 17588 function wrap(node, name, attrs) { 17589 var wrapper = dom.create(name, attrs); 17590 17591 node.parentNode.insertBefore(wrapper, node); 17592 wrapper.appendChild(node); 17593 17594 return wrapper; 17595 }; 17596 17597 function expandRng(rng, format, remove) { 17598 var sibling, lastIdx, leaf, endPoint, 17599 startContainer = rng.startContainer, 17600 startOffset = rng.startOffset, 17601 endContainer = rng.endContainer, 17602 endOffset = rng.endOffset; 17603 17604 // This function walks up the tree if there is no siblings before/after the node 17605 function findParentContainer(start) { 17606 var container, parent, child, sibling, siblingName, root; 17607 17608 container = parent = start ? startContainer : endContainer; 17609 siblingName = start ? 'previousSibling' : 'nextSibling'; 17610 root = dom.getRoot(); 17611 17612 function isBogusBr(node) { 17613 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 17614 }; 17615 17616 // If it's a text node and the offset is inside the text 17617 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17618 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17619 return container; 17620 } 17621 } 17622 17623 for (;;) { 17624 // Stop expanding on block elements 17625 if (!format[0].block_expand && isBlock(parent)) 17626 return parent; 17627 17628 // Walk left/right 17629 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17630 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 17631 return parent; 17632 } 17633 } 17634 17635 // Check if we can move up are we at root level or body level 17636 if (parent.parentNode == root) { 17637 container = parent; 17638 break; 17639 } 17640 17641 parent = parent.parentNode; 17642 } 17643 17644 return container; 17645 }; 17646 17647 // This function walks down the tree to find the leaf at the selection. 17648 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17649 function findLeaf(node, offset) { 17650 if (offset === undef) 17651 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17652 while (node && node.hasChildNodes()) { 17653 node = node.childNodes[offset]; 17654 if (node) 17655 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17656 } 17657 return { node: node, offset: offset }; 17658 } 17659 17660 // If index based start position then resolve it 17661 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17662 lastIdx = startContainer.childNodes.length - 1; 17663 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17664 17665 if (startContainer.nodeType == 3) 17666 startOffset = 0; 17667 } 17668 17669 // If index based end position then resolve it 17670 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17671 lastIdx = endContainer.childNodes.length - 1; 17672 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17673 17674 if (endContainer.nodeType == 3) 17675 endOffset = endContainer.nodeValue.length; 17676 } 17677 17678 // Expands the node to the closes contentEditable false element if it exists 17679 function findParentContentEditable(node) { 17680 var parent = node; 17681 17682 while (parent) { 17683 if (parent.nodeType === 1 && getContentEditable(parent)) {
17684 return getContentEditable(parent) === "false" ? parent : node; 17685 } 17686 17687 parent = parent.parentNode; 17688 } 17689 17690 return node; 17691 }; 17692 17693 function findWordEndPoint(container, offset, start) { 17694 var walker, node, pos, lastTextNode; 17695 17696 function findSpace(node, offset) { 17697 var pos, pos2, str = node.nodeValue; 17698 17699 if (typeof(offset) == "undefined") { 17700 offset = start ? str.length : 0; 17701 } 17702 17703 if (start) { 17704 pos = str.lastIndexOf(' ', offset); 17705 pos2 = str.lastIndexOf('\u00a0', offset); 17706 pos = pos > pos2 ? pos : pos2; 17707 17708 // Include the space on remove to avoid tag soup 17709 if (pos !== -1 && !remove) { 17710 pos++; 17711 } 17712 } else { 17713 pos = str.indexOf(' ', offset); 17714 pos2 = str.indexOf('\u00a0', offset); 17715 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17716 } 17717 17718 return pos; 17719 }; 17720 17721 if (container.nodeType === 3) { 17722 pos = findSpace(container, offset); 17723 17724 if (pos !== -1) { 17725 return {container : container, offset : pos}; 17726 } 17727 17728 lastTextNode = container; 17729 } 17730 17731 // Walk the nodes inside the block 17732 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17733 while (node = walker[start ? 'prev' : 'next']()) { 17734 if (node.nodeType === 3) { 17735 lastTextNode = node; 17736 pos = findSpace(node); 17737 17738 if (pos !== -1) { 17739 return {container : node, offset : pos}; 17740 } 17741 } else if (isBlock(node)) { 17742 break; 17743 } 17744 } 17745 17746 if (lastTextNode) { 17747 if (start) { 17748 offset = 0; 17749 } else { 17750 offset = lastTextNode.length; 17751 } 17752 17753 return {container: lastTextNode, offset: offset}; 17754 } 17755 }; 17756 17757 function findSelectorEndPoint(container, sibling_name) { 17758 var parents, i, y, curFormat; 17759 17760 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17761 container = container[sibling_name]; 17762 17763 parents = getParents(container); 17764 for (i = 0; i < parents.length; i++) { 17765 for (y = 0; y < format.length; y++) { 17766 curFormat = format[y]; 17767 17768 // If collapsed state is set then skip formats that doesn't match that 17769 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17770 continue; 17771 17772 if (dom.is(parents[i], curFormat.selector)) 17773 return parents[i]; 17774 } 17775 } 17776 17777 return container; 17778 }; 17779 17780 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17781 var node; 17782 17783 // Expand to block of similar type 17784 if (!format[0].wrapper) 17785 node = dom.getParent(container, format[0].block); 17786 17787 // Expand to first wrappable block element or any block element 17788 if (!node) 17789 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock); 17790 17791 // Exclude inner lists from wrapping 17792 if (node && format[0].wrapper) 17793 node = getParents(node, 'ul,ol').reverse()[0] || node; 17794 17795 // Didn't find a block element look for first/last wrappable element 17796 if (!node) { 17797 node = container; 17798 17799 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17800 node = node[sibling_name]; 17801 17802 // Break on BR but include it will be removed later on 17803 // we can't remove it now since we need to check if it can be wrapped 17804 if (isEq(node, 'br')) 17805 break; 17806 } 17807 } 17808 17809 return node || container; 17810 }; 17811 17812 // Expand to closest contentEditable element 17813 startContainer = findParentContentEditable(startContainer); 17814 endContainer = findParentContentEditable(endContainer); 17815 17816 // Exclude bookmark nodes if possible 17817 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17818 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17819 startContainer = startContainer.nextSibling || startContainer; 17820 17821 if (startContainer.nodeType == 3) 17822 startOffset = 0; 17823 } 17824 17825 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17826 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17827 endContainer = endContainer.previousSibling || endContainer; 17828 17829 if (endContainer.nodeType == 3) 17830 endOffset = endContainer.length; 17831 } 17832 17833 if (format[0].inline) { 17834 if (rng.collapsed) { 17835 // Expand left to closest word boundery 17836 endPoint = findWordEndPoint(startContainer, startOffset, true); 17837 if (endPoint) { 17838 startContainer = endPoint.container; 17839 startOffset = endPoint.offset; 17840 } 17841 17842 // Expand right to closest word boundery 17843 endPoint = findWordEndPoint(endContainer, endOffset); 17844 if (endPoint) { 17845 endContainer = endPoint.container; 17846 endOffset = endPoint.offset; 17847 } 17848 } 17849 17850 // Avoid applying formatting to a trailing space. 17851 leaf = findLeaf(endContainer, endOffset); 17852 if (leaf.node) { 17853 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17854 leaf = findLeaf(leaf.node.previousSibling); 17855 17856 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17857 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17858 17859 if (leaf.offset > 1) { 17860 endContainer = leaf.node;
17861 endContainer.splitText(leaf.offset - 1); 17862 } 17863 } 17864 } 17865 } 17866 17867 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17868 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17869 // This will reduce the number of wrapper elements that needs to be created 17870 // Move start point up the tree 17871 if (format[0].inline || format[0].block_expand) { 17872 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17873 startContainer = findParentContainer(true); 17874 } 17875 17876 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17877 endContainer = findParentContainer(); 17878 } 17879 } 17880 17881 // Expand start/end container to matching selector 17882 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17883 // Find new startContainer/endContainer if there is better one 17884 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17885 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17886 } 17887 17888 // Expand start/end container to matching block element or text node 17889 if (format[0].block || format[0].selector) { 17890 // Find new startContainer/endContainer if there is better one 17891 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17892 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17893 17894 // Non block element then try to expand up the leaf 17895 if (format[0].block) { 17896 if (!isBlock(startContainer)) 17897 startContainer = findParentContainer(true); 17898 17899 if (!isBlock(endContainer)) 17900 endContainer = findParentContainer(); 17901 } 17902 } 17903 17904 // Setup index for startContainer 17905 if (startContainer.nodeType == 1) { 17906 startOffset = nodeIndex(startContainer); 17907 startContainer = startContainer.parentNode; 17908 } 17909 17910 // Setup index for endContainer 17911 if (endContainer.nodeType == 1) { 17912 endOffset = nodeIndex(endContainer) + 1; 17913 endContainer = endContainer.parentNode; 17914 } 17915 17916 // Return new range like object 17917 return { 17918 startContainer : startContainer, 17919 startOffset : startOffset, 17920 endContainer : endContainer, 17921 endOffset : endOffset 17922 }; 17923 } 17924 17925 function removeFormat(format, vars, node, compare_node) { 17926 var i, attrs, stylesModified; 17927 17928 // Check if node matches format 17929 if (!matchName(node, format)) 17930 return FALSE; 17931 17932 // Should we compare with format attribs and styles 17933 if (format.remove != 'all') { 17934 // Remove styles 17935 each(format.styles, function(value, name) { 17936 value = replaceVars(value, vars); 17937 17938 // Indexed array 17939 if (typeof(name) === 'number') { 17940 name = value; 17941 compare_node = 0; 17942 } 17943 17944 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17945 dom.setStyle(node, name, ''); 17946 17947 stylesModified = 1; 17948 }); 17949 17950 // Remove style attribute if it's empty 17951 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17952 node.removeAttribute('style'); 17953 node.removeAttribute('data-mce-style'); 17954 } 17955 17956 // Remove attributes 17957 each(format.attributes, function(value, name) { 17958 var valueOut; 17959 17960 value = replaceVars(value, vars); 17961 17962 // Indexed array 17963 if (typeof(name) === 'number') { 17964 name = value; 17965 compare_node = 0; 17966 } 17967 17968 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17969 // Keep internal classes 17970 if (name == 'class') { 17971 value = dom.getAttrib(node, name); 17972 if (value) { 17973 // Build new class value where everything is removed except the internal prefixed classes 17974 valueOut = ''; 17975 each(value.split(/\s+/), function(cls) { 17976 if (/mce\w+/.test(cls)) 17977 valueOut += (valueOut ? ' ' : '') + cls; 17978 }); 17979 17980 // We got some internal classes left 17981 if (valueOut) { 17982 dom.setAttrib(node, name, valueOut); 17983 return; 17984 } 17985 } 17986 } 17987 17988 // IE6 has a bug where the attribute doesn't get removed correctly 17989 if (name == "class") 17990 node.removeAttribute('className'); 17991 17992 // Remove mce prefixed attributes 17993 if (MCE_ATTR_RE.test(name)) 17994 node.removeAttribute('data-mce-' + name); 17995 17996 node.removeAttribute(name); 17997 } 17998 }); 17999 18000 // Remove classes 18001 each(format.classes, function(value) { 18002 value = replaceVars(value, vars); 18003 18004 if (!compare_node || dom.hasClass(compare_node, value)) 18005 dom.removeClass(node, value); 18006 }); 18007 18008 // Check for non internal attributes 18009 attrs = dom.getAttribs(node); 18010 for (i = 0; i < attrs.length; i++) { 18011 if (attrs[i].nodeName.indexOf('_') !== 0) 18012 return FALSE; 18013 } 18014 } 18015 18016 // Remove the inline child if it's empty for example <b> or <span> 18017 if (format.remove != 'none') { 18018 removeNode(node, format); 18019 return TRUE; 18020 } 18021 }; 18022 18023 function removeNode(node, format) { 18024 var parentNode = node.parentNode, rootBlockElm; 18025 18026 function find(node, next, inc) { 18027 node = getNonWhiteSpaceSibling(node, next, inc); 18028 18029 return !node || (node.nodeName == 'BR' || isBlock(node)); 18030 }; 18031 18032 if (format.block) { 18033 if (!forcedRootBlock) { 18034 // Append BR elements if needed before we remove the block 18035 if (isBlock(node) && !isBlock(parentNode)) { 18036 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 18037 node.insertBefore(dom.create('br'), node.firstChild); 18038 18039 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 18040 node.appendChild(dom.create('br')); 18041 } 18042 } else { 18043 // Wrap the block in a forcedRootBlock if we are at the root of document 18044 if (parentNode == dom.getRoot()) { 18045 if (!format.list_block || !isEq(node, format.list_block)) { 18046 each(tinymce.grep(node.childNodes), function(node) { 18047 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 18048 if (!rootBlockElm) 18049 rootBlockElm = wrap(node, forcedRootBlock); 18050 else 18051 rootBlockElm.appendChild(node); 18052 } else 18053 rootBlockElm = 0; 18054 }); 18055 } 18056 } 18057 } 18058 } 18059 18060 // Never remove nodes that isn't the specified inline element if a selector is specified too 18061 if (format.selector && format.inline && !isEq(format.inline, node)) 18062 return; 18063 18064 dom.remove(node, 1); 18065 }; 18066 18067 function getNonWhiteSpaceSibling(node, next, inc) { 18068 if (node) { 18069 next = next ? 'nextSibling' : 'previousSibling'; 18070 18071 for (node = inc ? node : node[next]; node; node = node[next]) { 18072 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 18073 return node; 18074 } 18075 } 18076 }; 18077 18078 function isBookmarkNode(node) { 18079 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 18080 }; 18081 18082 function mergeSiblings(prev, next) { 18083 var marker, sibling, tmpSibling; 18084 18085 function compareElements(node1, node2) { 18086 // Not the same name 18087 if (node1.nodeName != node2.nodeName) 18088 return FALSE; 18089 18090 function getAttribs(node) { 18091 var attribs = {}; 18092 18093 each(dom.getAttribs(node), function(attr) { 18094 var name = attr.nodeName.toLowerCase(); 18095 18096 // Don't compare internal attributes or style 18097 if (name.indexOf('_') !== 0 && name !== 'style') 18098 attribs[name] = dom.getAttrib(node, name); 18099 }); 18100 18101 return attribs; 18102 }; 18103 18104 function compareObjects(obj1, obj2) { 18105 var value, name; 18106 18107 for (name in obj1) { 18108 // Obj1 has item obj2 doesn't have 18109 if (obj1.hasOwnProperty(name)) { 18110 value = obj2[name]; 18111 18112 // Obj2 doesn't have obj1 item 18113 if (value === undef) 18114 return FALSE; 18115 18116 // Obj2 item has a different value 18117 if (obj1[name] != value) 18118 return FALSE; 18119 18120 // Delete similar value 18121 delete obj2[name]; 18122 } 18123 } 18124 18125 // Check if obj 2 has something obj 1 doesn't have 18126 for (name in obj2) { 18127 // Obj2 has item obj1 doesn't have 18128 if (obj2.hasOwnProperty(name)) 18129 return FALSE; 18130 } 18131 18132 return TRUE; 18133 }; 18134 18135 // Attribs are not the same 18136 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 18137 return FALSE; 18138 18139 // Styles are not the same 18140 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 18141 return FALSE; 18142 18143 return TRUE; 18144 }; 18145 18146 function findElementSibling(node, sibling_name) { 18147 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 18148 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 18149 return node; 18150 18151 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 18152 return sibling; 18153 } 18154 18155 return node; 18156 }; 18157 18158 // Check if next/prev exists and that they are elements 18159 if (prev && next) { 18160 // If previous sibling is empty then jump over it 18161 prev = findElementSibling(prev, 'previousSibling'); 18162 next = findElementSibling(next, 'nextSibling'); 18163 18164 // Compare next and previous nodes 18165 if (compareElements(prev, next)) { 18166 // Append nodes between 18167 for (sibling = prev.nextSibling; sibling && sibling != next;) { 18168 tmpSibling = sibling; 18169 sibling = sibling.nextSibling; 18170 prev.appendChild(tmpSibling); 18171 } 18172 18173 // Remove next node 18174 dom.remove(next); 18175 18176 // Move children into prev node 18177 each(tinymce.grep(next.childNodes), function(node) { 18178 prev.appendChild(node); 18179 }); 18180 18181 return prev; 18182 } 18183 } 18184 18185 return next; 18186 }; 18187 18188 function getContainer(rng, start) { 18189 var container, offset, lastIdx, walker; 18190 18191 container = rng[start ? 'startContainer' : 'endContainer']; 18192 offset = rng[start ? 'startOffset' : 'endOffset']; 18193 18194 if (container.nodeType == 1) { 18195 lastIdx = container.childNodes.length - 1; 18196 18197 if (!start && offset) 18198 offset--; 18199 18200 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 18201 } 18202 18203 // If start text node is excluded then walk to the next node 18204 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 18205 container = new TreeWalker(container, ed.getBody()).next() || container; 18206 } 18207 18208 // If end text node is excluded then walk to the previous node 18209 if (container.nodeType === 3 && !start && offset === 0) { 18210 container = new TreeWalker(container, ed.getBody()).prev() || container; 18211 } 18212 18213 return container; 18214 }; 18215 18216 function performCaretAction(type, name, vars) { 18217 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 18218 18219 // Creates a caret container bogus element 18220 function createCaretContainer(fill) { 18221 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 18222 18223 if (fill) { 18224 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 18225 } 18226 18227 return caretContainer; 18228 }; 18229 18230 function isCaretContainerEmpty(node, nodes) { 18231 while (node) { 18232 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 18233 return false;
18234 } 18235 18236 // Collect nodes 18237 if (nodes && node.nodeType === 1) { 18238 nodes.push(node); 18239 } 18240 18241 node = node.firstChild; 18242 } 18243 18244 return true; 18245 }; 18246 18247 // Returns any parent caret container element 18248 function getParentCaretContainer(node) { 18249 while (node) { 18250 if (node.id === caretContainerId) { 18251 return node; 18252 } 18253 18254 node = node.parentNode; 18255 } 18256 }; 18257 18258 // Finds the first text node in the specified node 18259 function findFirstTextNode(node) { 18260 var walker; 18261 18262 if (node) { 18263 walker = new TreeWalker(node, node); 18264 18265 for (node = walker.current(); node; node = walker.next()) { 18266 if (node.nodeType === 3) { 18267 return node; 18268 } 18269 } 18270 } 18271 }; 18272 18273 // Removes the caret container for the specified node or all on the current document 18274 function removeCaretContainer(node, move_caret) { 18275 var child, rng; 18276 18277 if (!node) { 18278 node = getParentCaretContainer(selection.getStart()); 18279 18280 if (!node) { 18281 while (node = dom.get(caretContainerId)) { 18282 removeCaretContainer(node, false); 18283 } 18284 } 18285 } else { 18286 rng = selection.getRng(true); 18287 18288 if (isCaretContainerEmpty(node)) { 18289 if (move_caret !== false) { 18290 rng.setStartBefore(node); 18291 rng.setEndBefore(node); 18292 } 18293 18294 dom.remove(node); 18295 } else { 18296 child = findFirstTextNode(node); 18297 18298 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 18299 child = child.deleteData(0, 1); 18300 } 18301 18302 dom.remove(node, 1); 18303 } 18304 18305 selection.setRng(rng); 18306 } 18307 }; 18308 18309 // Applies formatting to the caret postion 18310 function applyCaretFormat() { 18311 var rng, caretContainer, textNode, offset, bookmark, container, text; 18312 18313 rng = selection.getRng(true); 18314 offset = rng.startOffset; 18315 container = rng.startContainer; 18316 text = container.nodeValue; 18317 18318 caretContainer = getParentCaretContainer(selection.getStart()); 18319 if (caretContainer) { 18320 textNode = findFirstTextNode(caretContainer); 18321 } 18322 18323 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 18324 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 18325 // Get bookmark of caret position 18326 bookmark = selection.getBookmark(); 18327 18328 // Collapse bookmark range (WebKit) 18329 rng.collapse(true); 18330 18331 // Expand the range to the closest word and split it at those points 18332 rng = expandRng(rng, get(name)); 18333 rng = rangeUtils.split(rng); 18334 18335 // Apply the format to the range 18336 apply(name, vars, rng); 18337 18338 // Move selection back to caret position 18339 selection.moveToBookmark(bookmark); 18340 } else { 18341 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 18342 caretContainer = createCaretContainer(true); 18343 textNode = caretContainer.firstChild; 18344 18345 rng.insertNode(caretContainer); 18346 offset = 1; 18347 18348 apply(name, vars, caretContainer); 18349 } else { 18350 apply(name, vars, caretContainer); 18351 } 18352 18353 // Move selection to text node 18354 selection.setCursorLocation(textNode, offset); 18355 } 18356 }; 18357 18358 function removeCaretFormat() { 18359 var rng = selection.getRng(true), container, offset, bookmark, 18360 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 18361 18362 container = rng.startContainer; 18363 offset = rng.startOffset; 18364 node = container; 18365 18366 if (container.nodeType == 3) { 18367 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 18368 hasContentAfter = true; 18369 } 18370 18371 node = node.parentNode; 18372 } 18373 18374 while (node) { 18375 if (matchNode(node, name, vars)) { 18376 formatNode = node; 18377 break; 18378 } 18379 18380 if (node.nextSibling) { 18381 hasContentAfter = true; 18382 } 18383 18384 parents.push(node); 18385 node = node.parentNode; 18386 } 18387 18388 // Node doesn't have the specified format 18389 if (!formatNode) { 18390 return; 18391 } 18392 18393 // Is there contents after the caret then remove the format on the element 18394 if (hasContentAfter) { 18395 // Get bookmark of caret position 18396 bookmark = selection.getBookmark(); 18397 18398 // Collapse bookmark range (WebKit) 18399 rng.collapse(true); 18400 18401 // Expand the range to the closest word and split it at those points 18402 rng = expandRng(rng, get(name), true); 18403 rng = rangeUtils.split(rng); 18404 18405 // Remove the format from the range 18406 remove(name, vars, rng); 18407 18408 // Move selection back to caret position 18409 selection.moveToBookmark(bookmark); 18410 } else { 18411 caretContainer = createCaretContainer(); 18412 18413 node = caretContainer; 18414 for (i = parents.length - 1; i >= 0; i--) { 18415 node.appendChild(dom.clone(parents[i], false)); 18416 node = node.firstChild; 18417 } 18418 18419 // Insert invisible character into inner most format element 18420 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 18421 node = node.firstChild; 18422 18423 var block = dom.getParent(formatNode, isTextBlock); 18424 18425 if (block && dom.isEmpty(block)) { 18426 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 18427 formatNode.parentNode.replaceChild(caretContainer, formatNode); 18428 } else { 18429 // Insert caret container after the formated node 18430 dom.insertAfter(caretContainer, formatNode); 18431 } 18432 18433 // Move selection to text node 18434 selection.setCursorLocation(node, 1); 18435 18436 // If the formatNode is empty, we can remove it safely. 18437 if (dom.isEmpty(formatNode)) { 18438 dom.remove(formatNode); 18439 } 18440 } 18441 }; 18442 18443 // Checks if the parent caret container node isn't empty if that is the case it 18444 // will remove the bogus state on all children that isn't empty 18445 function unmarkBogusCaretParents() { 18446 var i, caretContainer, node; 18447 18448 caretContainer = getParentCaretContainer(selection.getStart()); 18449 if (caretContainer && !dom.isEmpty(caretContainer)) { 18450 tinymce.walk(caretContainer, function(node) { 18451 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 18452 dom.setAttrib(node, 'data-mce-bogus', null); 18453 } 18454 }, 'childNodes'); 18455 } 18456 }; 18457 18458 // Only bind the caret events once 18459 if (!self._hasCaretEvents) { 18460 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 18461 ed.onBeforeGetContent.addToTop(function() { 18462 var nodes = [], i; 18463 18464 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 18465 // Mark children 18466 i = nodes.length; 18467 while (i--) { 18468 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 18469 } 18470 } 18471 }); 18472 18473 // Remove caret container on mouse up and on key up 18474 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 18475 ed[name].addToTop(function() { 18476 removeCaretContainer(); 18477 unmarkBogusCaretParents(); 18478 }); 18479 }); 18480 18481 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18482 ed.onKeyDown.addToTop(function(ed, e) { 18483 var keyCode = e.keyCode; 18484 18485 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18486 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18487 } 18488 18489 unmarkBogusCaretParents(); 18490 }); 18491 18492 // Remove bogus state if they got filled by contents using editor.selection.setContent 18493 selection.onSetContent.add(unmarkBogusCaretParents); 18494 18495 self._hasCaretEvents = true; 18496 } 18497 18498 // Do apply or remove caret format 18499 if (type == "apply") { 18500 applyCaretFormat(); 18501 } else { 18502 removeCaretFormat(); 18503 } 18504 }; 18505 18506 function moveStart(rng) { 18507 var container = rng.startContainer, 18508 offset = rng.startOffset, isAtEndOfText, 18509 walker, node, nodes, tmpNode; 18510 18511 // Convert text node into index if possible 18512 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18513 // Get the parent container location and walk from there 18514 offset = nodeIndex(container); 18515 container = container.parentNode; 18516 isAtEndOfText = true; 18517 } 18518 18519 // Move startContainer/startOffset in to a suitable node 18520 if (container.nodeType == 1) { 18521 nodes = container.childNodes; 18522 container = nodes[Math.min(offset, nodes.length - 1)]; 18523 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18524 18525 // If offset is at end of the parent node walk to the next one 18526 if (offset >
18526 nodes.length - 1 || isAtEndOfText) 18527 walker.next(); 18528 18529 for (node = walker.current(); node; node = walker.next()) { 18530 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18531 // IE has a "neat" feature where it moves the start node into the closest element 18532 // we can avoid this by inserting an element before it and then remove it after we set the selection 18533 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18534 node.parentNode.insertBefore(tmpNode, node); 18535 18536 // Set selection and remove tmpNode 18537 rng.setStart(node, 0); 18538 selection.setRng(rng); 18539 dom.remove(tmpNode); 18540 18541 return; 18542 } 18543 } 18544 } 18545 }; 18546 }; 18547})(tinymce); 18548 18549tinymce.onAddEditor.add(function(tinymce, ed) { 18550 var filters, fontSizes, dom, settings = ed.settings; 18551 18552 function replaceWithSpan(node, styles) { 18553 tinymce.each(styles, function(value, name) { 18554 if (value) 18555 dom.setStyle(node, name, value); 18556 }); 18557 18558 dom.rename(node, 'span'); 18559 }; 18560 18561 function convert(editor, params) { 18562 dom = editor.dom; 18563 18564 if (settings.convert_fonts_to_spans) { 18565 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18566 filters[node.nodeName.toLowerCase()](ed.dom, node); 18567 }); 18568 } 18569 }; 18570 18571 if (settings.inline_styles) { 18572 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18573 18574 filters = { 18575 font : function(dom, node) { 18576 replaceWithSpan(node, { 18577 backgroundColor : node.style.backgroundColor, 18578 color : node.color, 18579 fontFamily : node.face, 18580 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18581 }); 18582 }, 18583 18584 u : function(dom, node) { 18585 replaceWithSpan(node, { 18586 textDecoration : 'underline' 18587 }); 18588 }, 18589 18590 strike : function(dom, node) { 18591 replaceWithSpan(node, { 18592 textDecoration : 'line-through' 18593 }); 18594 } 18595 }; 18596 18597 ed.onPreProcess.add(convert); 18598 ed.onSetContent.add(convert); 18599 18600 ed.onInit.add(function() { 18601 ed.selection.onSetContent.add(convert); 18602 }); 18603 } 18604}); 18605 18606(function(tinymce) { 18607 var TreeWalker = tinymce.dom.TreeWalker; 18608 18609 tinymce.EnterKey = function(editor) { 18610 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18611 18612 function handleEnterKey(evt) { 18613 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 18614 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18615 18616 // Returns true if the block can be split into two blocks or not 18617 function canSplitBlock(node) { 18618 return node && 18619 dom.isBlock(node) && 18620 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18621 !/^(fixed|absolute)/i.test(node.style.position) && 18622 dom.getContentEditable(node) !== "true"; 18623 }; 18624 18625 // Renders empty block on IE 18626 function renderBlockOnIE(block) { 18627 var oldRng; 18628 18629 if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) { 18630 oldRng = selection.getRng(); 18631 block.appendChild(dom.create('span', null, '\u00a0')); 18632 selection.select(block); 18633 block.lastChild.outerHTML = ''; 18634 selection.setRng(oldRng); 18635 } 18636 }; 18637 18638 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18639 function trimInlineElementsOnLeftSideOfBlock(block) { 18640 var node = block, firstChilds = [], i; 18641 18642 // Find inner most first child ex: <p><i><b>*</b></i></p> 18643 while (node = node.firstChild) { 18644 if (dom.isBlock(node)) { 18645 return; 18646 } 18647 18648 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18649 firstChilds.push(node); 18650 } 18651 } 18652 18653 i = firstChilds.length; 18654 while (i--) { 18655 node = firstChilds[i]; 18656 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18657 dom.remove(node); 18658 } else { 18659 // Remove <a> </a> see #5381 18660 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 18661 dom.remove(node); 18662 } 18663 } 18664 } 18665 }; 18666 18667 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18668 function moveToCaretPosition(root) { 18669 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18670 18671 rng = dom.createRng(); 18672 18673 if (root.hasChildNodes()) { 18674 walker = new TreeWalker(root, root); 18675 18676 while (node = walker.current()) { 18677 if (node.nodeType == 3) {
18678 rng.setStart(node, 0); 18679 rng.setEnd(node, 0); 18680 break; 18681 } 18682 18683 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18684 rng.setStartBefore(node); 18685 rng.setEndBefore(node); 18686 break; 18687 } 18688 18689 lastNode = node; 18690 node = walker.next(); 18691 } 18692 18693 if (!node) { 18694 rng.setStart(lastNode, 0); 18695 rng.setEnd(lastNode, 0); 18696 } 18697 } else { 18698 if (root.nodeName == 'BR') { 18699 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18700 // Trick on older IE versions to render the caret before the BR between two lists 18701 if (!documentMode || documentMode < 9) { 18702 tempElm = dom.create('br'); 18703 root.parentNode.insertBefore(tempElm, root); 18704 } 18705 18706 rng.setStartBefore(root); 18707 rng.setEndBefore(root); 18708 } else { 18709 rng.setStartAfter(root); 18710 rng.setEndAfter(root); 18711 } 18712 } else { 18713 rng.setStart(root, 0); 18714 rng.setEnd(root, 0); 18715 } 18716 } 18717 18718 selection.setRng(rng); 18719 18720 // Remove tempElm created for old IE:s 18721 dom.remove(tempElm); 18722 18723 viewPort = dom.getViewPort(editor.getWin()); 18724 18725 // 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 18726 y = dom.getPos(root).y; 18727 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18728 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 18729 } 18730 }; 18731 18732 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18733 // This function will also copy any text formatting from the parent block and add it to the new one 18734 function createNewBlock(name) { 18735 var node = container, block, clonedNode, caretNode; 18736 18737 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18738 caretNode = block; 18739 18740 // Clone any parent styles 18741 if (settings.keep_styles !== false) { 18742 do { 18743 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18744 // Never clone a caret containers 18745 if (node.id == '_mce_caret') { 18746 continue; 18747 } 18748 18749 clonedNode = node.cloneNode(false); 18750 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18751 18752 if (block.hasChildNodes()) { 18753 clonedNode.appendChild(block.firstChild); 18754 block.appendChild(clonedNode); 18755 } else { 18756 caretNode = clonedNode; 18757 block.appendChild(clonedNode); 18758 } 18759 } 18760 } while (node = node.parentNode); 18761 } 18762 18763 // BR is needed in empty blocks on non IE browsers 18764 if (!tinymce.isIE || tinymce.isIE11) { 18765 caretNode.innerHTML = '<br data-mce-bogus="1">'; 18766 } 18767 18768 return block; 18769 }; 18770 18771 // Returns true/false if the caret is at the start/end of the parent block element 18772 function isCaretAtStartOrEndOfBlock(start) { 18773 var walker, node, name; 18774 18775 // Caret is in the middle of a text node like "a|b" 18776 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18777 return false; 18778 } 18779 18780 // If after the last element in block node edge case for #5091 18781 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18782 return true; 18783 } 18784 18785 // If the caret if before the first element in parentBlock 18786 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18787 return true; 18788 } 18789 18790 // Caret can be before/after a table 18791 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18792 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18793 } 18794 18795 // Walk the DOM and look for text nodes or non empty elements 18796 walker = new TreeWalker(container, parentBlock); 18797 18798 // If caret is in beginning or end of a text block then jump to the next/previous node 18799 if (container.nodeType == 3) { 18800 if (start && offset == 0) { 18801 walker.prev(); 18802 } else if (!start && offset == container.nodeValue.length) { 18803 walker.next(); 18804 } 18805 } 18806 18807 while (node = walker.current()) { 18808 if (node.nodeType === 1) { 18809 // Ignore bogus elements 18810 if (!node.getAttribute('data-mce-bogus')) { 18811 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18812 name = node.nodeName.toLowerCase(); 18813 if (nonEmptyElementsMap[name] && name !== 'br') { 18814 return false; 18815 } 18816 } 18817 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18818 return false; 18819 } 18820 18821 if (start) { 18822 walker.prev(); 18823 } else { 18824 walker.next(); 18825 } 18826 } 18827 18828 return true; 18829 }; 18830 18831 // Wraps any text nodes or inline elements in the specified forced root block name 18832 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18833 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18834 18835 // Not in a block element or in a table cell or caption 18836 parentBlock = dom.getParent(container, dom.isBlock); 18837 if (!parentBlock || !canSplitBlock(parentBlock)) { 18838 parentBlock = parentBlock || editableRoot; 18839 18840 if (!parentBlock.hasChildNodes()) { 18841 newBlock = dom.create(blockName); 18842 parentBlock.appendChild(newBlock); 18843 rng.setStart(newBlock, 0); 18844 rng.setEnd(newBlock, 0); 18845 return newBlock; 18846 } 18847 18848 // Find parent that is the first child of parentBlock 18849 node = container; 18850 while (node.parentNode != parentBlock) { 18851 node = node.parentNode; 18852 } 18853 18854 // Loop left to find start node start wrapping at 18855 while (node && !dom.isBlock(node)) { 18856 startNode = node; 18857 node = node.previousSibling; 18858 } 18859 18860 if (startNode) { 18861 newBlock = dom.create(blockName); 18862 startNode.parentNode.insertBefore(newBlock, startNode); 18863 18864 // Start wrapping until we hit a block 18865 node = startNode; 18866 while (node && !dom.isBlock(node)) { 18867 next = node.nextSibling; 18868 newBlock.appendChild(node); 18869 node = next; 18870 } 18871 18872 // Restore range to it's past location 18873 rng.setStart(container, offset); 18874 rng.setEnd(container, offset); 18875 } 18876 } 18877 18878 return container; 18879 }; 18880 18881 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18882 function handleEmptyListItem() { 18883 function isFirstOrLastLi(first) { 18884 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18885 18886 // Find first/last element since there might be whitespace there 18887 while (node) { 18888 if (node.nodeType == 1) { 18889 break; 18890 } 18891 18892 node = node[first ? 'nextSibling' : 'previousSibling']; 18893 } 18894 18895 return node === parentBlock; 18896 }; 18897 18898 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18899 18900 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18901 // Is first and last list item then replace the OL/UL with a text block 18902 dom.replace(newBlock, containerBlock); 18903 } else if (isFirstOrLastLi(true)) { 18904 // First LI in list then remove LI and add text block before list 18905 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18906 } else if (isFirstOrLastLi()) { 18907 // Last LI in list then temove LI and add text block after list 18908 dom.insertAfter(newBlock, containerBlock); 18909 renderBlockOnIE(newBlock); 18910 } else {
18911 // Middle LI in list the split the list and insert a text block in the middle 18912 // Extract after fragment and insert it after the current block 18913 tmpRng = rng.cloneRange(); 18914 tmpRng.setStartAfter(parentBlock); 18915 tmpRng.setEndAfter(containerBlock); 18916 fragment = tmpRng.extractContents(); 18917 dom.insertAfter(fragment, containerBlock); 18918 dom.insertAfter(newBlock, containerBlock); 18919 } 18920 18921 dom.remove(parentBlock); 18922 moveToCaretPosition(newBlock); 18923 undoManager.add(); 18924 }; 18925 18926 // Walks the parent block to the right and look for any contents 18927 function hasRightSideContent() { 18928 var walker = new TreeWalker(container, parentBlock), node; 18929 18930 while (node = walker.next()) { 18931 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 18932 return true; 18933 } 18934 } 18935 } 18936 18937 // Inserts a BR element if the forced_root_block option is set to false or empty string 18938 function insertBr() { 18939 var brElm, extraBr, marker; 18940 18941 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18942 // Insert extra BR element at the end block elements 18943 if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) { 18944 brElm = dom.create('br'); 18945 rng.insertNode(brElm); 18946 rng.setStartAfter(brElm); 18947 rng.setEndAfter(brElm); 18948 extraBr = true; 18949 } 18950 } 18951 18952 brElm = dom.create('br'); 18953 rng.insertNode(brElm); 18954 18955 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18956 if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18957 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18958 } 18959 18960 // Insert temp marker and scroll to that 18961 marker = dom.create('span', {}, ' '); 18962 brElm.parentNode.insertBefore(marker, brElm); 18963 selection.scrollIntoView(marker); 18964 dom.remove(marker); 18965 18966 if (!extraBr) { 18967 rng.setStartAfter(brElm); 18968 rng.setEndAfter(brElm); 18969 } else { 18970 rng.setStartBefore(brElm); 18971 rng.setEndBefore(brElm); 18972 } 18973 18974 selection.setRng(rng); 18975 undoManager.add(); 18976 }; 18977 18978 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18979 function trimLeadingLineBreaks(node) { 18980 do { 18981 if (node.nodeType === 3) { 18982 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18983 } 18984 18985 node = node.firstChild; 18986 } while (node); 18987 }; 18988 18989 function getEditableRoot(node) { 18990 var root = dom.getRoot(), parent, editableRoot; 18991 18992 // Get all parents until we hit a non editable parent or the root 18993 parent = node; 18994 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18995 if (dom.getContentEditable(parent) === "true") { 18996 editableRoot = parent; 18997 } 18998 18999 parent = parent.parentNode; 19000 } 19001 19002 return parent !== root ? editableRoot : root; 19003 }; 19004 19005 // 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 19006 function addBrToBlockIfNeeded(block) { 19007 var lastChild; 19008 19009 // IE will render the blocks correctly other browsers needs a BR 19010 if (!tinymce.isIE || tinymce.isIE11) { 19011 block.normalize(); // Remove empty text nodes that got left behind by the extract 19012 19013 // Check if the block is empty or contains a floated last child 19014 lastChild = block.lastChild; 19015 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 19016 dom.add(block, 'br'); 19017 } 19018 } 19019 }; 19020 19021 // Delete any selected contents 19022 if (!rng.collapsed) { 19023 editor.execCommand('Delete'); 19024 return; 19025 } 19026 19027 // Event is blocked by some other handler for example the lists plugin 19028 if (evt.isDefaultPrevented()) { 19029 return; 19030 } 19031 19032 // Setup range items and newBlockName 19033 container = rng.startContainer; 19034 offset = rng.startOffset; 19035 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 19036 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 19037 documentMode = dom.doc.documentMode; 19038 shiftKey = evt.shiftKey; 19039 19040 // Resolve node index 19041 if (container.nodeType == 1 && container.hasChildNodes()) { 19042 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 19043 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 19044 if (isAfterLastNodeInContainer && container.nodeType == 3) { 19045 offset = container.nodeValue.length; 19046 } else { 19047 offset = 0; 19048 } 19049 } 19050 19051 // Get editable root node normaly the body element but sometimes a div or span 19052 editableRoot = getEditableRoot(container); 19053 19054 // If there is no editable root then enter is done inside a contentEditable false element 19055 if (!editableRoot) { 19056 return; 19057 } 19058 19059 undoManager.beforeChange(); 19060 19061 // If editable root isn't block nor the root of the editor
19062 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 19063 if (!newBlockName || shiftKey) { 19064 insertBr(); 19065 } 19066 19067 return; 19068 } 19069 19070 // Wrap the current node and it's sibling in a default block if it's needed. 19071 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 19072 // This won't happen if root blocks are disabled or the shiftKey is pressed 19073 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 19074 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 19075 } 19076 19077 // Find parent block and setup empty block paddings 19078 parentBlock = dom.getParent(container, dom.isBlock); 19079 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 19080 19081 // Setup block names 19082 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 19083 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 19084 19085 // Enter inside block contained within a LI then split or insert before/after LI 19086 if (containerBlockName == 'LI' && !evt.ctrlKey) { 19087 parentBlock = containerBlock; 19088 parentBlockName = containerBlockName; 19089 } 19090 19091 // Handle enter in LI 19092 if (parentBlockName == 'LI') { 19093 if (!newBlockName && shiftKey) { 19094 insertBr(); 19095 return; 19096 } 19097 19098 // Handle enter inside an empty list item 19099 if (dom.isEmpty(parentBlock)) { 19100 // Let the list plugin or browser handle nested lists for now 19101 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 19102 return false; 19103 } 19104 19105 handleEmptyListItem(); 19106 return; 19107 } 19108 } 19109 19110 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 19111 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 19112 if (!shiftKey) { 19113 insertBr(); 19114 return; 19115 } 19116 } else { 19117 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 19118 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 19119 insertBr(); 19120 return; 19121 } 19122 } 19123 19124 // Default block name if it's not configured 19125 newBlockName = newBlockName || 'P'; 19126 19127 // Insert new block before/after the parent block depending on caret location 19128 if (isCaretAtStartOrEndOfBlock()) { 19129 // 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 19130 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 19131 newBlock = createNewBlock(newBlockName); 19132 } else { 19133 newBlock = createNewBlock(); 19134 } 19135 19136 // Split the current container block element if enter is pressed inside an empty inner block element 19137 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 19138 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 19139 newBlock = dom.split(containerBlock, parentBlock); 19140 } else { 19141 dom.insertAfter(newBlock, parentBlock); 19142 } 19143 19144 moveToCaretPosition(newBlock); 19145 } else if (isCaretAtStartOrEndOfBlock(true)) { 19146 // Insert new block before 19147 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 19148 renderBlockOnIE(newBlock); 19149 } else { 19150 // Extract after fragment and insert it after the current block 19151 tmpRng = rng.cloneRange(); 19152 tmpRng.setEndAfter(parentBlock); 19153 fragment = tmpRng.extractContents(); 19154 trimLeadingLineBreaks(fragment); 19155 newBlock = fragment.firstChild; 19156 dom.insertAfter(fragment, parentBlock); 19157 trimInlineElementsOnLeftSideOfBlock(newBlock); 19158 addBrToBlockIfNeeded(parentBlock); 19159 moveToCaretPosition(newBlock); 19160 } 19161 19162 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 19163 undoManager.add(); 19164 } 19165 19166 editor.onKeyDown.add(function(ed, evt) { 19167 if (evt.keyCode == 13) { 19168 if (handleEnterKey(evt) !== false) { 19169 evt.preventDefault(); 19170 } 19171 } 19172 }); 19173 }; 19174})(tinymce); 19175
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.